Skip to content

Commit

Permalink
Handle annotated assignment in 3.6
Browse files Browse the repository at this point in the history
In 3.6, annotated assignment was added, which takes the form of `a: int = 0`.
This would cause errors, since `:` is an operator, and its child would contain
nodes of type `annassign`.

We handle these the same way that we handle normal assignments, but by dropping
into the assignment logic at an offset in the expression we're handling.
  • Loading branch information
worr committed Oct 4, 2018
1 parent 5ec6889 commit 33147d2
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 4 deletions.
12 changes: 10 additions & 2 deletions mutmut/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ def and_or_test_mutation(children, node, **_):


def expression_mutation(children, **_):
assert children[1].type == 'operator'
if children[1].value == '=':
def handle_assignment(children):
if getattr(children[2], 'value', '---') != 'None':
x = ' None'
else:
Expand All @@ -179,6 +178,14 @@ def expression_mutation(children, **_):
from parso.python.tree import Name
children[2] = Name(value=x, start_pos=children[2].start_pos)

return children

if children[0].type == 'operator' and children[0].value == ':':
if children[2].value == '=':
children[1:] = handle_assignment(children[1:])
elif children[1].type == 'operator' and children[1].value == '=':
children = handle_assignment(children)

return children


Expand Down Expand Up @@ -206,6 +213,7 @@ def decorator_mutation(children, **_):
'lambdef': dict(children=lambda_mutation),
'expr_stmt': dict(children=expression_mutation),
'decorator': dict(children=decorator_mutation),
'annassign': dict(children=expression_mutation),
}

# TODO: detect regexes and mutate them in nasty ways? Maybe mutate all strings as if they are regexes
Expand Down
6 changes: 4 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"e = 1",
"f = 3",
"d = dict(e=f)",
"g: int = 2",
]
file_to_mutate_contents = '\n'.join(file_to_mutate_lines) + '\n'

Expand All @@ -27,6 +28,7 @@ def test_foo():
assert e == 1
assert f == 3
assert d == dict(e=f)
assert g == 2
'''


Expand Down Expand Up @@ -58,7 +60,7 @@ def test_simple_apply():
def test_full_run_no_surviving_mutants():
result = CliRunner().invoke(main, ['foo.py'], catch_exceptions=False)
print(repr(result.output))
assert result.output == u'Running tests without mutations... Done\n--- starting mutation ---\n\r0 out of 7 (foo.py)\r1 out of 7 (foo.py return a < b⤑0)\r2 out of 7 (foo.py e = 1⤑0) \r3 out of 7 (foo.py e = 1⤑1)\r4 out of 7 (foo.py f = 3⤑0)\r5 out of 7 (foo.py f = 3⤑1)\r6 out of 7 (foo.py d = dict(e=f)⤑0)\r7 out of 7 (foo.py d = dict(e=f)⤑1)'
assert result.output == u'Running tests without mutations... Done\n--- starting mutation ---\n\r0 out of 8 (foo.py)\r1 out of 8 (foo.py return a < b⤑0)\r2 out of 8 (foo.py e = 1⤑0) \r3 out of 8 (foo.py e = 1⤑1)\r4 out of 8 (foo.py f = 3⤑0)\r5 out of 8 (foo.py f = 3⤑1)\r6 out of 8 (foo.py d = dict(e=f)⤑0)\r7 out of 8 (foo.py d = dict(e=f)⤑1)\r8 out of 8 (foo.py g: int = 2⤑0) '


@pytest.mark.usefixtures('filesystem')
Expand All @@ -68,7 +70,7 @@ def test_full_run_one_surviving_mutant():

result = CliRunner().invoke(main, ['foo.py'], catch_exceptions=False)
print(repr(result.output))
assert result.output == u'Running tests without mutations... Done\n--- starting mutation ---\n\r0 out of 7 (foo.py) \r1 out of 7 (foo.py return a < b⤑0)\r \rFAILED: mutmut foo.py --apply --mutation " return a < b⤑0"\n\r2 out of 7 (foo.py e = 1⤑0)\r3 out of 7 (foo.py e = 1⤑1)\r4 out of 7 (foo.py f = 3⤑0)\r5 out of 7 (foo.py f = 3⤑1)\r6 out of 7 (foo.py d = dict(e=f)⤑0)\r7 out of 7 (foo.py d = dict(e=f)⤑1)'
assert result.output == u'Running tests without mutations... Done\n--- starting mutation ---\n\r0 out of 8 (foo.py) \r1 out of 8 (foo.py return a < b⤑0)\r \rFAILED: mutmut foo.py --apply --mutation " return a < b⤑0"\n\r2 out of 8 (foo.py e = 1⤑0)\r3 out of 8 (foo.py e = 1⤑1)\r4 out of 8 (foo.py f = 3⤑0)\r5 out of 8 (foo.py f = 3⤑1)\r6 out of 8 (foo.py d = dict(e=f)⤑0)\r7 out of 8 (foo.py d = dict(e=f)⤑1)\r8 out of 8 (foo.py g: int = 2⤑0) '


@pytest.mark.usefixtures('filesystem')
Expand Down
2 changes: 2 additions & 0 deletions tests/test_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
('a = {x for x in y}', 'a = None'),
('a = None', 'a = 7'),
('break', 'continue'),
('a: int = 1', 'a: int = None'),
('a: Optional[int] = None', 'a: Optional[int] = 7'),
]
)
def test_basic_mutations(original, expected):
Expand Down

0 comments on commit 33147d2

Please sign in to comment.