Skip to content

Commit 3854494

Browse files
Fix unchanged string mutants (#437)
* Add test for github issue 435 * Filter out identical string mutants with different values As at the status quo, operator_string took the prefix of the node's value, trimming that off the rest to leave an approximation. This worked well enough until some wombat (hi) fed in a regex string consisting only of escape sequences and whitespace. For whatever reason, the node represented that value as r'(rest of text...) which the status quo dutifully trimmed to (rest of text...). As the two text strings didn't match, status quo dutifully let the mutant through despite it being a no-op. This commit adds an additional check to catch the _unmodified_ node value being equal to the mutated node value, skipping those candidate mutants.
1 parent 9f672be commit 3854494

File tree

2 files changed

+27
-0
lines changed

2 files changed

+27
-0
lines changed

mutmut/node_mutation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def operator_string(
3131
) -> Iterable[cst.BaseString]:
3232
if isinstance(node, cst.SimpleString):
3333
value = node.value
34+
old_value = value
3435
prefix = value[
3536
: min([x for x in [value.find('"'), value.find("'")] if x != -1])
3637
]
@@ -52,6 +53,8 @@ def operator_string(
5253
new_value = f"{prefix}{value[0]}{mut_func(value[1:-1])}{value[-1]}"
5354
if new_value == value:
5455
continue
56+
if new_value == old_value:
57+
continue
5558
yield node.with_changes(value=new_value)
5659

5760

tests/test_mutation.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,30 @@ def test_bug_github_issue_77():
418418
assert mutants_for_source('') == []
419419

420420

421+
def test_bug_github_issue_435():
422+
source = """
423+
def parse(self, text: str) -> tuple[Tree[Token], str]:
424+
text = re.sub(r'[\w\-] [\w\-]', dashrepl, text)
425+
426+
return self.parser.parse(text), text
427+
""".strip()
428+
429+
mutants = mutants_for_source(source)
430+
431+
expected = [
432+
'def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = None\n\n return self.parser.parse(text), text',
433+
'def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(None, dashrepl, text)\n\n return self.parser.parse(text), text',
434+
"def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(r'[\\w\\-] [\\w\\-]', None, text)\n\n return self.parser.parse(text), text",
435+
"def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(r'[\\w\\-] [\\w\\-]', dashrepl, None)\n\n return self.parser.parse(text), text",
436+
'def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(dashrepl, text)\n\n return self.parser.parse(text), text',
437+
"def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(r'[\\w\\-] [\\w\\-]', text)\n\n return self.parser.parse(text), text",
438+
"def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(r'[\\w\\-] [\\w\\-]', dashrepl, )\n\n return self.parser.parse(text), text",
439+
"def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(r'XX[\\w\\-] [\\w\\-]XX', dashrepl, text)\n\n return self.parser.parse(text), text",
440+
"def parse(self, text: str) -> tuple[Tree[Token], str]:\n text = re.sub(r'[\\w\\-] [\\w\\-]', dashrepl, text)\n\n return self.parser.parse(None), text"
441+
]
442+
assert sorted(mutants) == sorted(expected)
443+
444+
421445
def test_multiline_dunder_whitelist():
422446
source = """
423447
__all__ = [

0 commit comments

Comments
 (0)