Repository: boxed/mutmut

Issue:
UTF-8 emoji icons fail to display in a Pycharm 2019.2.1 terminal in windows
This was created on a windows 10 environment with Pycharm 2019.2.1.

Running mutmut in the Pycharm native terminal fails to render the emojis indicating killed 🎉, suspicious 🤔, and surviving 🙁 mutants.

For example:
```console 
(venv) >mutmut run --paths-to-mutate my_project

- Mutation testing starting -

These are the steps:
1. A full test suite run will be made to make sure we
   can run the tests successfully and we know how long
   it takes (to detect infinite loops for example)
2. Mutants will be generated and checked

Results are stored in .mutmut-cache.
Print found mutants with `mutmut results`.

Legend for output:
� Killed mutants.   The goal is for everything to end up in this bucket.
⏰ Timeout.          Test suite took 10 times as long as the baseline so were killed.
� Suspicious.       Tests took a long time, but not long enough to be fatal.
� Survived.         This means your tests needs to be expanded.

mutmut cache is out of date, clearing it...
1. Running tests without mutations
...
```

Maybe we should simply adopt a ASCII text based notation? 

Discussion:

Patch:
diff --git a/mutmut/__init__.py b/mutmut/__init__.py
index 78ad464..b77329a 100644
--- a/mutmut/__init__.py
+++ b/mutmut/__init__.py
@@ -910,8 +910,9 @@ def guess_paths_to_mutate():
 
 
 class Progress(object):
-    def __init__(self, total):
+    def __init__(self, total, output_legend):
         self.total = total
+        self.output_legend = output_legend
         self.progress = 0
         self.skipped = 0
         self.killed_mutants = 0
@@ -920,7 +921,20 @@ class Progress(object):
         self.suspicious_mutants = 0
 
     def print(self):
-        print_status('{}/{}  🎉 {}  ⏰ {}  🤔 {}  🙁 {}  🔇 {}'.format(self.progress, self.total, self.killed_mutants, self.surviving_mutants_timeout, self.suspicious_mutants, self.surviving_mutants, self.skipped))
+        print_status('{}/{}  {} {}  {} {}  {} {}  {} {}  {} {}'.format(
+            self.progress,
+            self.total,
+            self.output_legend["killed"],
+            self.killed_mutants,
+            self.output_legend["timeout"],
+            self.surviving_mutants_timeout,
+            self.output_legend["suspicious"],
+            self.suspicious_mutants,
+            self.output_legend["survived"],
+            self.surviving_mutants,
+            self.output_legend["skipped"],
+            self.skipped)
+        )
 
     def register(self, status):
         if status == BAD_SURVIVED:
diff --git a/mutmut/__main__.py b/mutmut/__main__.py
index 2136aee..43e6efc 100644
--- a/mutmut/__main__.py
+++ b/mutmut/__main__.py
@@ -100,6 +100,7 @@ DEFAULT_RUNNER = 'python -m pytest -x --assert=plain'
 @click.option('--untested-policy', type=click.Choice(['ignore', 'skipped', 'error', 'failure']), default='ignore')
 @click.option('--pre-mutation')
 @click.option('--post-mutation')
+@click.option('--simple-output', is_flag=True, default=False, help="Swap emojis in mutmut output to plain text alternatives.")
 @config_from_setup_cfg(
     dict_synonyms='',
     paths_to_exclude='',
@@ -113,7 +114,7 @@ def climain(command, argument, argument2, paths_to_mutate, backup, runner, tests
             test_time_multiplier, test_time_base,
             swallow_output, use_coverage, dict_synonyms, cache_only, version,
             suspicious_policy, untested_policy, pre_mutation, post_mutation,
-            use_patch_file, paths_to_exclude):
+            use_patch_file, paths_to_exclude, simple_output):
     """
 commands:\n
     run [mutation id]\n
@@ -137,14 +138,14 @@ commands:\n
                   tests_dir, test_time_multiplier, test_time_base,
                   swallow_output, use_coverage, dict_synonyms, cache_only,
                   version, suspicious_policy, untested_policy, pre_mutation,
-                  post_mutation, use_patch_file, paths_to_exclude))
+                  post_mutation, use_patch_file, paths_to_exclude, simple_output))
 
 
 def main(command, argument, argument2, paths_to_mutate, backup, runner, tests_dir,
          test_time_multiplier, test_time_base,
          swallow_output, use_coverage, dict_synonyms, cache_only, version,
          suspicious_policy, untested_policy, pre_mutation, post_mutation,
-         use_patch_file, paths_to_exclude):
+         use_patch_file, paths_to_exclude, simple_output):
     """return exit code, after performing an mutation test run.
 
     :return: the exit code from executing the mutation tests
@@ -223,6 +224,15 @@ def main(command, argument, argument2, paths_to_mutate, backup, runner, tests_di
     os.environ['PYTHONDONTWRITEBYTECODE'] = '1'  # stop python from creating .pyc files
 
     using_testmon = '--testmon' in runner
+    output_legend = {
+        "killed": "🎉",
+        "timeout": "⏰",
+        "suspicious": "🤔",
+        "survived": "🙁",
+        "skipped": "🔇",
+    }
+    if simple_output:
+        output_legend = {key: key.upper() for (key, value) in output_legend.items()}
 
     print("""
 - Mutation testing starting -
@@ -237,12 +247,12 @@ Results are stored in .mutmut-cache.
 Print found mutants with `mutmut results`.
 
 Legend for output:
-🎉 Killed mutants.   The goal is for everything to end up in this bucket.
-⏰ Timeout.          Test suite took 10 times as long as the baseline so were killed.
-🤔 Suspicious.       Tests took a long time, but not long enough to be fatal.
-🙁 Survived.         This means your tests need to be expanded.
-🔇 Skipped.          Skipped.
-""")
+{killed} Killed mutants.   The goal is for everything to end up in this bucket.
+{timeout} Timeout.          Test suite took 10 times as long as the baseline so were killed.
+{suspicious} Suspicious.       Tests took a long time, but not long enough to be fatal.
+{survived} Survived.         This means your tests need to be expanded.
+{skipped} Skipped.          Skipped.
+""".format(**output_legend))
     if runner is DEFAULT_RUNNER:
         try:
             import pytest
@@ -309,7 +319,7 @@ Legend for output:
 
     print()
     print('2. Checking mutants')
-    progress = Progress(total=config.total)
+    progress = Progress(total=config.total, output_legend=output_legend)
 
     try:
         run_mutation_tests(config=config, progress=progress, mutations_by_file=mutations_by_file)


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index 459279d..219f2b9 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -109,7 +109,7 @@ def test_compute_return_code():
     class MockProgress(Progress):
         def __init__(self, killed_mutants, surviving_mutants,
                      surviving_mutants_timeout, suspicious_mutants):
-            super(MockProgress, self).__init__(total=0)
+            super(MockProgress, self).__init__(total=0, output_legend={})
             self.killed_mutants = killed_mutants
             self.surviving_mutants = surviving_mutants
             self.surviving_mutants_timeout = surviving_mutants_timeout
@@ -441,3 +441,9 @@ def test_pre_and_post_mutation_hook(single_mutant_filesystem, tmpdir):
     assert "pre mutation stub" in result.output
     assert "post mutation stub" in result.output
     assert result.output.index("pre mutation stub") < result.output.index("post mutation stub")
+
+
+def test_simple_output(filesystem):
+    result = CliRunner().invoke(climain, ['run', '--paths-to-mutate=foo.py', "--simple-output"], catch_exceptions=False)
+    print(repr(result.output))
+    assert '14/14  KILLED 14  TIMEOUT 0  SUSPICIOUS 0  SURVIVED 0  SKIPPED 0' in repr(result.output)