diff --git a/mutmut/node_mutation.py b/mutmut/node_mutation.py index 84a69c0d..a9218c87 100644 --- a/mutmut/node_mutation.py +++ b/mutmut/node_mutation.py @@ -96,6 +96,35 @@ def operator_arg_removal( yield node.with_changes(args=[*node.args[:i], *node.args[i + 1 :]]) +supported_str_methods_swap = [ + ("lower", "upper"), + ("upper", "lower"), + ("lstrip", "rstrip"), + ("rstrip", "lstrip"), + ("find", "rfind"), + ("rfind", "find"), + ("ljust", "rjust"), + ("rjust", "ljust"), + ("index", "rindex"), + ("rindex", "index"), + ("split", "rsplit"), + ("rsplit", "split"), + ("removeprefix", "removesuffix"), + ("removesuffix", "removeprefix"), + ("partition", "rpartition"), + ("rpartition", "partition") + ] + +def operator_string_methods_swap( + node: cst.Call +) -> Iterable[cst.Call]: + """try to swap string method to opposite e.g. a.lower() -> a.upper()""" + + for old_call, new_call in supported_str_methods_swap: + if m.matches(node.func, m.Attribute(value=m.DoNotCare(), attr=m.Name(value=old_call))): + yield node.with_changes(func=cst.Attribute(value=node.func.value, attr=cst.Name(value=new_call))) + + def operator_remove_unary_ops( node: cst.UnaryOperation ) -> Iterable[cst.BaseExpression]: @@ -208,6 +237,7 @@ def operator_match(node: cst.Match) -> Iterable[cst.CSTNode]: (cst.UnaryOperation, operator_remove_unary_ops), (cst.Call, operator_dict_arguments), (cst.Call, operator_arg_removal), + (cst.Call, operator_string_methods_swap), (cst.Lambda, operator_lambda), (cst.CSTNode, operator_keywords), (cst.CSTNode, operator_swap_op), diff --git a/tests/test_mutation.py b/tests/test_mutation.py index 5644137c..fe3e1bb1 100644 --- a/tests/test_mutation.py +++ b/tests/test_mutation.py @@ -40,6 +40,32 @@ def mutated_module(source: str) -> str: # ('break', 'continue'), # probably a bad idea. Can introduce infinite loops. ('break', 'return'), ('continue', 'break'), + ('a.lower()', 'a.upper()'), + ('a.upper()', 'a.lower()'), + ('a.lstrip("!")', ['a.rstrip("!")', 'a.lstrip("XX!XX")', 'a.lstrip(None)']), + ('a.rstrip("!")', ['a.lstrip("!")', 'a.rstrip("XX!XX")', 'a.rstrip(None)']), + ('a.find("!")', ['a.rfind("!")', 'a.find("XX!XX")', 'a.find(None)']), + ('a.rfind("!")', ['a.find("!")', 'a.rfind("XX!XX")', 'a.rfind(None)']), + ('a.ljust(10, "+")', [ + 'a.ljust("+")', 'a.ljust(10, "XX+XX")', + 'a.ljust(10, )', 'a.ljust(10, None)', + 'a.ljust(11, "+")', 'a.ljust(None, "+")', + 'a.rjust(10, "+")' + ]), + ('a.rjust(10, "+")', [ + 'a.ljust(10, "+")', 'a.rjust("+")', + 'a.rjust(10, "XX+XX")', 'a.rjust(10, )', + 'a.rjust(10, None)', 'a.rjust(11, "+")', + 'a.rjust(None, "+")' + ]), + ('a.index("+")', ['a.rindex("+")', 'a.index("XX+XX")', 'a.index(None)']), + ('a.rindex("+")', ['a.index("+")', 'a.rindex("XX+XX")', 'a.rindex(None)']), + ('a.split()', 'a.rsplit()'), + ('a.rsplit()', 'a.split()'), + ('a.removeprefix("+")', ['a.removesuffix("+")', 'a.removeprefix("XX+XX")', 'a.removeprefix(None)']), + ('a.removesuffix("+")', ['a.removeprefix("+")', 'a.removesuffix("XX+XX")', 'a.removesuffix(None)']), + ('a.partition("++")', ['a.rpartition("++")', 'a.partition("XX++XX")', 'a.partition(None)']), + ('a.rpartition("++")', ['a.partition("++")', 'a.rpartition("XX++XX")', 'a.rpartition(None)']), ('a(b)', 'a(None)'), ("dict(a=None)", ["dict(aXX=None)"]), ("dict(a=b)", ["dict(aXX=b)", 'dict(a=None)']), @@ -235,15 +261,15 @@ def xǁFooǁmember__mutmut_1(self): def test_function_with_annotation(): - source = "def capitalize(s : str):\n return s[0].upper() + s[1:] if s else s\n".strip() + source = "def capitalize(s : str):\n return s[0].title() + s[1:] if s else s\n".strip() mutated_code = mutated_module(source) - print(mutated_code) + print(mutated_code, open("aboba", "a")) expected_defs = [ - 'def x_capitalize__mutmut_1(s : str):\n return s[1].upper() + s[1:] if s else s', - 'def x_capitalize__mutmut_2(s : str):\n return s[0].upper() - s[1:] if s else s', - 'def x_capitalize__mutmut_3(s : str):\n return s[0].upper() + s[2:] if s else s', + 'def x_capitalize__mutmut_1(s : str):\n return s[1].title() + s[1:] if s else s', + 'def x_capitalize__mutmut_2(s : str):\n return s[0].title() - s[1:] if s else s', + 'def x_capitalize__mutmut_3(s : str):\n return s[0].title() + s[2:] if s else s', ] for expected in expected_defs: