Skip to content

Commit e1110ff

Browse files
authored
Merge pull request #386 from CHB-0r1s/main
Add str methods call mutations and tests
2 parents b7b4f90 + e3736b1 commit e1110ff

File tree

2 files changed

+63
-4
lines changed

2 files changed

+63
-4
lines changed

mutmut/node_mutation.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,36 @@ def operator_arg_removal(
9696
yield node.with_changes(args=[*node.args[:i], *node.args[i + 1 :]])
9797

9898

99+
supported_str_methods_swap = [
100+
("lower", "upper"),
101+
("upper", "lower"),
102+
("lstrip", "rstrip"),
103+
("rstrip", "lstrip"),
104+
("find", "rfind"),
105+
("rfind", "find"),
106+
("ljust", "rjust"),
107+
("rjust", "ljust"),
108+
("index", "rindex"),
109+
("rindex", "index"),
110+
("split", "rsplit"),
111+
("rsplit", "split"),
112+
("removeprefix", "removesuffix"),
113+
("removesuffix", "removeprefix"),
114+
("partition", "rpartition"),
115+
("rpartition", "partition")
116+
]
117+
118+
def operator_string_methods_swap(
119+
node: cst.Call
120+
) -> Iterable[cst.Call]:
121+
"""try to swap string method to opposite e.g. a.lower() -> a.upper()"""
122+
123+
for old_call, new_call in supported_str_methods_swap:
124+
if m.matches(node.func, m.Attribute(value=m.DoNotCare(), attr=m.Name(value=old_call))):
125+
func_name = cst.ensure_type(node.func, cst.Attribute).attr
126+
yield node.with_deep_changes(func_name, value=new_call)
127+
128+
99129
def operator_remove_unary_ops(
100130
node: cst.UnaryOperation
101131
) -> Iterable[cst.BaseExpression]:
@@ -208,6 +238,7 @@ def operator_match(node: cst.Match) -> Iterable[cst.CSTNode]:
208238
(cst.UnaryOperation, operator_remove_unary_ops),
209239
(cst.Call, operator_dict_arguments),
210240
(cst.Call, operator_arg_removal),
241+
(cst.Call, operator_string_methods_swap),
211242
(cst.Lambda, operator_lambda),
212243
(cst.CSTNode, operator_keywords),
213244
(cst.CSTNode, operator_swap_op),

tests/test_mutation.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,34 @@ def mutated_module(source: str) -> str:
4040
# ('break', 'continue'), # probably a bad idea. Can introduce infinite loops.
4141
('break', 'return'),
4242
('continue', 'break'),
43+
('a.lower()', 'a.upper()'),
44+
('a.upper()', 'a.lower()'),
45+
('a.b.lower()', 'a.b.upper()'),
46+
('a.b.upper()', 'a.b.lower()'),
47+
('a.lstrip("!")', ['a.rstrip("!")', 'a.lstrip("XX!XX")', 'a.lstrip(None)']),
48+
('a.rstrip("!")', ['a.lstrip("!")', 'a.rstrip("XX!XX")', 'a.rstrip(None)']),
49+
('a.find("!")', ['a.rfind("!")', 'a.find("XX!XX")', 'a.find(None)']),
50+
('a.rfind("!")', ['a.find("!")', 'a.rfind("XX!XX")', 'a.rfind(None)']),
51+
('a.ljust(10, "+")', [
52+
'a.ljust("+")', 'a.ljust(10, "XX+XX")',
53+
'a.ljust(10, )', 'a.ljust(10, None)',
54+
'a.ljust(11, "+")', 'a.ljust(None, "+")',
55+
'a.rjust(10, "+")'
56+
]),
57+
('a.rjust(10, "+")', [
58+
'a.ljust(10, "+")', 'a.rjust("+")',
59+
'a.rjust(10, "XX+XX")', 'a.rjust(10, )',
60+
'a.rjust(10, None)', 'a.rjust(11, "+")',
61+
'a.rjust(None, "+")'
62+
]),
63+
('a.index("+")', ['a.rindex("+")', 'a.index("XX+XX")', 'a.index(None)']),
64+
('a.rindex("+")', ['a.index("+")', 'a.rindex("XX+XX")', 'a.rindex(None)']),
65+
('a.split()', 'a.rsplit()'),
66+
('a.rsplit()', 'a.split()'),
67+
('a.removeprefix("+")', ['a.removesuffix("+")', 'a.removeprefix("XX+XX")', 'a.removeprefix(None)']),
68+
('a.removesuffix("+")', ['a.removeprefix("+")', 'a.removesuffix("XX+XX")', 'a.removesuffix(None)']),
69+
('a.partition("++")', ['a.rpartition("++")', 'a.partition("XX++XX")', 'a.partition(None)']),
70+
('a.rpartition("++")', ['a.partition("++")', 'a.rpartition("XX++XX")', 'a.rpartition(None)']),
4371
('a(b)', 'a(None)'),
4472
("dict(a=None)", ["dict(aXX=None)"]),
4573
("dict(a=b)", ["dict(aXX=b)", 'dict(a=None)']),
@@ -235,15 +263,15 @@ def xǁFooǁmember__mutmut_1(self):
235263

236264

237265
def test_function_with_annotation():
238-
source = "def capitalize(s : str):\n return s[0].upper() + s[1:] if s else s\n".strip()
266+
source = "def capitalize(s : str):\n return s[0].title() + s[1:] if s else s\n".strip()
239267

240268
mutated_code = mutated_module(source)
241269
print(mutated_code)
242270

243271
expected_defs = [
244-
'def x_capitalize__mutmut_1(s : str):\n return s[1].upper() + s[1:] if s else s',
245-
'def x_capitalize__mutmut_2(s : str):\n return s[0].upper() - s[1:] if s else s',
246-
'def x_capitalize__mutmut_3(s : str):\n return s[0].upper() + s[2:] if s else s',
272+
'def x_capitalize__mutmut_1(s : str):\n return s[1].title() + s[1:] if s else s',
273+
'def x_capitalize__mutmut_2(s : str):\n return s[0].title() - s[1:] if s else s',
274+
'def x_capitalize__mutmut_3(s : str):\n return s[0].title() + s[2:] if s else s',
247275
]
248276

249277
for expected in expected_defs:

0 commit comments

Comments
 (0)