Skip to content

Commit 5688c67

Browse files
authored
Fix split / rsplit collision (#394)
Only mutate split to rsplit if the `maxsplit` arg is provided. Otherwise it would be an equivalent mutation.
1 parent 15edd77 commit 5688c67

File tree

2 files changed

+67
-9
lines changed

2 files changed

+67
-9
lines changed

mutmut/node_mutation.py

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

9898

99-
supported_str_methods_swap = [
99+
supported_symmetric_str_methods_swap = [
100100
("lower", "upper"),
101101
("upper", "lower"),
102102
("lstrip", "rstrip"),
@@ -107,24 +107,42 @@ def operator_arg_removal(
107107
("rjust", "ljust"),
108108
("index", "rindex"),
109109
("rindex", "index"),
110-
("split", "rsplit"),
111-
("rsplit", "split"),
112110
("removeprefix", "removesuffix"),
113111
("removesuffix", "removeprefix"),
114112
("partition", "rpartition"),
115113
("rpartition", "partition")
116-
]
114+
]
115+
116+
supported_unsymmetrical_str_methods_swap = [
117+
("split", "rsplit"),
118+
("rsplit", "split")
119+
]
117120

118-
def operator_string_methods_swap(
121+
def operator_symmetric_string_methods_swap(
119122
node: cst.Call
120123
) -> Iterable[cst.Call]:
121124
"""try to swap string method to opposite e.g. a.lower() -> a.upper()"""
122125

123-
for old_call, new_call in supported_str_methods_swap:
126+
for old_call, new_call in supported_symmetric_str_methods_swap:
124127
if m.matches(node.func, m.Attribute(value=m.DoNotCare(), attr=m.Name(value=old_call))):
125128
func_name = cst.ensure_type(node.func, cst.Attribute).attr
126129
yield node.with_deep_changes(func_name, value=new_call)
127130

131+
def operator_unsymmetrical_string_methods_swap(
132+
node: cst.Call
133+
) -> Iterable[cst.Call]:
134+
"""Try to handle specific mutations of string, which useful only in specific args combination."""
135+
for old_call, new_call in supported_unsymmetrical_str_methods_swap:
136+
if m.matches(node.func, m.Attribute(attr=m.Name(value=old_call))):
137+
if old_call in {"split", "rsplit"}:
138+
# The logic of this "if" operator described here:
139+
# https://github.com/boxed/mutmut/pull/394#issuecomment-2977890188
140+
key_args: set[str] = {a.keyword.value for a in node.args if a.keyword} # sep or maxsplit or nothing
141+
if len(node.args) == 2 or "maxsplit" in key_args:
142+
func_name = cst.ensure_type(node.func, cst.Attribute).attr
143+
yield node.with_deep_changes(func_name, value=new_call)
144+
145+
128146

129147
def operator_remove_unary_ops(
130148
node: cst.UnaryOperation
@@ -238,7 +256,8 @@ def operator_match(node: cst.Match) -> Iterable[cst.CSTNode]:
238256
(cst.UnaryOperation, operator_remove_unary_ops),
239257
(cst.Call, operator_dict_arguments),
240258
(cst.Call, operator_arg_removal),
241-
(cst.Call, operator_string_methods_swap),
259+
(cst.Call, operator_symmetric_string_methods_swap),
260+
(cst.Call, operator_unsymmetrical_string_methods_swap),
242261
(cst.Lambda, operator_lambda),
243262
(cst.CSTNode, operator_keywords),
244263
(cst.CSTNode, operator_swap_op),

tests/test_mutation.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,47 @@ def mutated_module(source: str) -> str:
6262
]),
6363
('a.index("+")', ['a.rindex("+")', 'a.index("XX+XX")', 'a.index(None)']),
6464
('a.rindex("+")', ['a.index("+")', 'a.rindex("XX+XX")', 'a.rindex(None)']),
65-
('a.split()', 'a.rsplit()'),
66-
('a.rsplit()', 'a.split()'),
65+
('a.split()', []),
66+
('a.rsplit()', []),
67+
('a.split(" ")', ['a.split("XX XX")', 'a.split(None)']),
68+
('a.rsplit(" ")', ['a.rsplit("XX XX")', 'a.rsplit(None)']),
69+
('a.split(sep="")', ['a.split(sep="XXXX")', 'a.split(sep=None)']),
70+
('a.rsplit(sep="")', ['a.rsplit(sep="XXXX")', 'a.rsplit(sep=None)']),
71+
('a.split(maxsplit=-1)', [
72+
'a.rsplit(maxsplit=-1)', 'a.split(maxsplit=+1)', 'a.split(maxsplit=-2)', 'a.split(maxsplit=None)'
73+
]),
74+
('a.rsplit(maxsplit=-1)', [
75+
'a.split(maxsplit=-1)', 'a.rsplit(maxsplit=+1)', 'a.rsplit(maxsplit=-2)', 'a.rsplit(maxsplit=None)'
76+
]),
77+
('a.split(" ", maxsplit=-1)', [
78+
'a.split(" ", )', 'a.split(" ", maxsplit=+1)', 'a.split(" ", maxsplit=-2)',
79+
'a.split(" ", maxsplit=None)', 'a.split("XX XX", maxsplit=-1)', 'a.split(None, maxsplit=-1)',
80+
'a.split(maxsplit=-1)', 'a.rsplit(" ", maxsplit=-1)'
81+
]),
82+
('a.rsplit(" ", maxsplit=-1)', [
83+
'a.rsplit(" ", )', 'a.rsplit(" ", maxsplit=+1)', 'a.rsplit(" ", maxsplit=-2)',
84+
'a.rsplit(" ", maxsplit=None)', 'a.rsplit("XX XX", maxsplit=-1)', 'a.rsplit(None, maxsplit=-1)',
85+
'a.rsplit(maxsplit=-1)', 'a.split(" ", maxsplit=-1)'
86+
]),
87+
('a.split(maxsplit=1)', ['a.split(maxsplit=2)', 'a.split(maxsplit=None)', 'a.rsplit(maxsplit=1)']),
88+
('a.rsplit(maxsplit=1)', ['a.rsplit(maxsplit=2)', 'a.rsplit(maxsplit=None)', 'a.split(maxsplit=1)']),
89+
('a.split(" ", 1)', [
90+
'a.rsplit(" ", 1)', 'a.split(" ", )', 'a.split(" ", 2)', 'a.split(" ", None)',
91+
'a.split("XX XX", 1)', 'a.split(1)', 'a.split(None, 1)'
92+
]),
93+
('a.rsplit(" ", 1)', [
94+
'a.rsplit(" ", )', 'a.rsplit(" ", 2)', 'a.rsplit(" ", None)', 'a.rsplit("XX XX", 1)',
95+
'a.rsplit(1)', 'a.rsplit(None, 1)', 'a.split(" ", 1)'
96+
]),
97+
('a.split(" ", maxsplit=1)', [
98+
'a.rsplit(" ", maxsplit=1)', 'a.split(" ", )', 'a.split(" ", maxsplit=2)', 'a.split(" ", maxsplit=None)',
99+
'a.split("XX XX", maxsplit=1)', 'a.split(None, maxsplit=1)', 'a.split(maxsplit=1)'
100+
]),
101+
('a.rsplit(" ", maxsplit=1)', [
102+
'a.rsplit(" ", )', 'a.rsplit(" ", maxsplit=2)', 'a.rsplit(" ", maxsplit=None)',
103+
'a.rsplit("XX XX", maxsplit=1)', 'a.rsplit(None, maxsplit=1)', 'a.rsplit(maxsplit=1)',
104+
'a.split(" ", maxsplit=1)'
105+
]),
67106
('a.removeprefix("+")', ['a.removesuffix("+")', 'a.removeprefix("XX+XX")', 'a.removeprefix(None)']),
68107
('a.removesuffix("+")', ['a.removeprefix("+")', 'a.removesuffix("XX+XX")', 'a.removesuffix(None)']),
69108
('a.partition("++")', ['a.rpartition("++")', 'a.partition("XX++XX")', 'a.partition(None)']),

0 commit comments

Comments
 (0)