From 33147d29e79d6834c85d8c30e1de718ed6be8e5f Mon Sep 17 00:00:00 2001 From: William Orr Date: Thu, 4 Oct 2018 12:00:51 -0700 Subject: [PATCH] Handle annotated assignment in 3.6 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. --- mutmut/__init__.py | 12 ++++++++++-- tests/test_main.py | 6 ++++-- tests/test_mutation.py | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/mutmut/__init__.py b/mutmut/__init__.py index a45a23e3..f044a9e4 100644 --- a/mutmut/__init__.py +++ b/mutmut/__init__.py @@ -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: @@ -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 @@ -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 diff --git a/tests/test_main.py b/tests/test_main.py index b08577ad..6dd1fb46 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -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' @@ -27,6 +28,7 @@ def test_foo(): assert e == 1 assert f == 3 assert d == dict(e=f) + assert g == 2 ''' @@ -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') @@ -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') diff --git a/tests/test_mutation.py b/tests/test_mutation.py index 766c2915..e2e83c5e 100644 --- a/tests/test_mutation.py +++ b/tests/test_mutation.py @@ -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):