Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions mutmut/node_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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),
Expand Down
34 changes: 30 additions & 4 deletions tests/test_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)']),
Expand Down Expand Up @@ -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)

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:
Expand Down