Repository: boxed/mutmut

Issue:
Bug with --disable-mutation-types and --enable-mutation-types
Hi I have been testing out the new commands since they are quite useful. However I found some bugs related to them. 

Here is a testProgram for mutation purpose 

```
def testfunction(): 
    array['a'] = 123
```

I run this command after building v2.3 `mutmut run --enable-mutation-types=number --paths-to-mutate testFunction.py` 
followed by 
`mutmut show all`

and here is the output:

```
To apply a mutant on disk:
    mutmut apply <id>

To show a mutant:
    mutmut show <id>


Survived � (1)

---- testFunction.py (1) ----

# mutant 1
--- testFunction.py
+++ testFunction.py
@@ -1,3 +1,3 @@

 def testfunction():
-    array['a'] = 123
+    array['XXaXX'] = 123
```

This seems like a string mutation and not a number mutation, the correct number mutation after taking a brief look at the source code shoule be something like `array['a'] = 124`

You can get a similar "wrong" output when you run `--disable-mutation-types=string`

I am not very familar with the code base but I think this bug has something to do with overwriting mutations? see these lines https://github.com/boxed/mutmut/blob/cc7e7825a8a2ca0d5abb5f4748aa8c8ccea7f0f7/mutmut/__init__.py#L629-L643


Discussion:

Patch:
diff --git a/mutmut/__init__.py b/mutmut/__init__.py
index 003b15a..a736caf 100644
--- a/mutmut/__init__.py
+++ b/mutmut/__init__.py
@@ -533,7 +533,9 @@ class Context(object):
             }
         return self._pragma_no_mutate_lines
 
-    def should_mutate(self):
+    def should_mutate(self, node):
+        if self.config and node.type not in self.config.mutation_types_to_apply:
+            return False
         if self.mutation_id == ALL:
             return True
         return self.mutation_id in (ALL, self.mutation_id_of_current_index)
@@ -599,9 +601,6 @@ def mutate_node(node, context):
             if context.performed_mutation_ids and context.mutation_id != ALL:
                 return
 
-        if context.config and node.type not in context.config.mutation_types_to_apply:
-            return
-
         mutation = mutations_by_type.get(node.type)
 
         if mutation is None:
@@ -634,7 +633,7 @@ def mutate_node(node, context):
                 if new is not None and new != old:
                     if hasattr(mutmut_config, 'pre_mutation_ast'):
                         mutmut_config.pre_mutation_ast(context=context)
-                    if context.should_mutate():
+                    if context.should_mutate(node):
                         context.performed_mutation_ids.append(context.mutation_id_of_current_index)
                         setattr(node, key, new)
                     context.index += 1


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index 586900d..9fa8575 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -101,6 +101,14 @@ def test_nothing(): assert True
 
     create_filesystem(tmpdir, foo_py, test_py)
 
+    yield tmpdir
+
+    # This is a hack to get pony to forget about the old db file
+    # otherwise Pony thinks we've already created the tables
+    import mutmut.cache
+    mutmut.cache.db.provider = None
+    mutmut.cache.db.schema = None
+
 
 def create_filesystem(tmpdir, file_to_mutate_contents, test_file_contents):
     test_dir = str(tmpdir)
@@ -601,6 +609,48 @@ def test_enable_and_disable_mutation_type_are_exclusive():
     assert "You can't combine --disable-mutation-types and --enable-mutation-types" in result.output
 
 
+@pytest.mark.parametrize(
+    "mutation_type, expected_mutation",
+    [
+        ("expr_stmt", "result = None"),
+        ("operator", "result = a - b"),
+    ]
+)
+def test_show_mutant_after_run_with_disabled_mutation_types(surviving_mutants_filesystem, mutation_type, expected_mutation):
+    """Test for issue #234: ``mutmut show <id>`` did not show the correct mutant if ``mutmut run`` was
+    run with ``--enable-mutation-types`` or ``--disable-mutation-types``."""
+    CliRunner().invoke(climain, ['run', '--paths-to-mutate=foo.py', f'--enable-mutation-types={mutation_type}'], catch_exceptions=False)
+    result = CliRunner().invoke(climain, ['show', '1'])
+    assert f"""
+ def foo(a, b):
+-    result = a + b
++    {expected_mutation}
+     return result
+""" in result.output
+
+
+def test_run_multiple_times_with_different_mutation_types(filesystem):
+    """Running multiple times with different mutation types enabled should append the new mutants to the cache without
+    altering existing mutants."""
+    CliRunner().invoke(climain, ['run', '--paths-to-mutate=foo.py', '--enable-mutation-types=number'], catch_exceptions=False)
+    result = CliRunner().invoke(climain, ['show', '1'])
+    assert """
+-c = 1
++c = 2
+""" in result.output
+    CliRunner().invoke(climain, ['run', '--paths-to-mutate=foo.py', '--enable-mutation-types=operator'], catch_exceptions=False)
+    result = CliRunner().invoke(climain, ['show', '1'])
+    assert """
+-c = 1
++c = 2
+""" in result.output, "mutant ID has changed!"
+    result = CliRunner().invoke(climain, ['show', '8'])
+    assert """
+-c += 1
++c -= 1
+""" in result.output, "no new mutation types added!"
+
+
 def test_show(surviving_mutants_filesystem):
     CliRunner().invoke(climain, ['run', '--paths-to-mutate=foo.py', "--test-time-base=15.0"], catch_exceptions=False)
     result = CliRunner().invoke(climain, ['show'])