mutmut 2.4.3__tar.gz → 2.4.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,3 +28,11 @@ Credits
28
28
  * Daniel Versoza Alves <daniel.alves@voxy.com>
29
29
  * Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
30
30
  * Jan Królikowski <yanekk86@gmail.com>
31
+ * Tal Amuyal <talamuyal@gmail.com>
32
+ * Eivind Jahren <eivind.jahren@gmail.com>
33
+ * WiredNerd <pbuschmail-gitlab@yahoo.com>
34
+ * Kees Hink <kees@fourdigits.nl>
35
+ * Jim Rybarski <jim@rybarski.com>
36
+ * Christopher J. Brody <chris.brody+brodybits@gmail.com>
37
+ * Century-ss <hgfc7543ggfdch@gmail.com>
38
+ * Michael Champagne <michael.champagne@elapsetech.com>
@@ -1,11 +1,34 @@
1
1
  Changelog
2
2
  ---------
3
3
 
4
+ 2.4.5
5
+ ~~~~~
6
+
7
+ * Add skipped column in html index report
8
+
9
+ * Untested/skipped in show all does not show skipped
10
+
11
+ * mutmut should pick up the config before running the original tests. That allows to specify test suits you want to run
12
+
13
+ * improved some high-level help text & documentation
14
+
15
+ 2.4.4
16
+ ~~~~~
17
+
18
+ * Fix issue with `TypeError` for empty file diffs
19
+
20
+ * Introduce mutation of f-strings
21
+
22
+ * Allow newer versions of junit-xml
23
+
4
24
  2.4.3
5
25
  ~~~~~
6
26
 
7
27
  * Fix for a big where worker cycling would just hang mutmut (thanks Jan Królikowski!)
8
28
 
29
+ * Added `--CI` flag
30
+
31
+
9
32
 
10
33
  2.4.2
11
34
  ~~~~~
@@ -455,4 +478,3 @@ Thanks goes out Marcelo Da Cruz Pinto, Savo Kovačević,
455
478
  ~~~~~~~~~~~~~~~~~~
456
479
 
457
480
  * Initial version
458
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mutmut
3
- Version: 2.4.3
3
+ Version: 2.4.5
4
4
  Summary: mutation testing for Python 3
5
5
  Home-page: https://github.com/boxed/mutmut
6
6
  Author: Anders Hovmöller
@@ -56,16 +56,24 @@ Description: mutmut - python mutation tester
56
56
 
57
57
  This will by default run pytest (or unittest if pytest is unavailable)
58
58
  on tests in the "tests" or "test" folder and
59
- it will try to figure out where the code to mutate lies. Run
59
+ it will try to figure out where the code to mutate lies.
60
+
61
+ NOTE that mutmut will apply the mutations directly, one at a time;
62
+ it is **highly** recommended to add all changes to source control
63
+ before running.
64
+
65
+ Enter
60
66
 
61
67
  .. code-block:: console
62
68
 
63
- mutmut --help
69
+ mutmut run --help
64
70
 
65
71
  for the available flags, to use other runners, etc. The recommended way to use
66
- mutmut if the defaults aren't working for you is to add a block in ``setup.cfg``.
72
+ mutmut if the defaults aren't working for you is to add a
73
+ block in ``setup.cfg`` or ``project.toml``.
67
74
  Then when you come back to mutmut weeks later you don't have to figure out the
68
- flags again, just run ``mutmut run`` and it works. Like this:
75
+ flags again, just run ``mutmut run`` and it works.
76
+ Like this in ``setup.cfg``:
69
77
 
70
78
  .. code-block:: ini
71
79
 
@@ -76,6 +84,14 @@ Description: mutmut - python mutation tester
76
84
  tests_dir=tests/
77
85
  dict_synonyms=Struct, NamedStruct
78
86
 
87
+ or like this in ``pyproject.toml``:
88
+
89
+ .. code-block:: ini
90
+
91
+ [tool.mutmut]
92
+ paths_to_mutate="src"
93
+ runner="python -m hammett -x"
94
+
79
95
  To use multiple paths either in the ``paths_to_mutate`` or ``tests_dir`` option
80
96
  use a comma or colon separated list. For example:
81
97
 
@@ -94,11 +110,11 @@ Description: mutmut - python mutation tester
94
110
  all mutants for a specific file with ``mutmut show path/to/file.py`` or all mutants
95
111
  with ``mutmut show all``.
96
112
 
97
-
98
113
  You can also write a mutant to disk with ``mutmut apply 3``. You should **REALLY**
99
114
  have the file you mutate under source code control and committed before you apply
100
115
  a mutant!
101
116
 
117
+ To generate a HTML report for a web browser: ``mutmut html``
102
118
 
103
119
  Whitelisting
104
120
  ------------
@@ -203,7 +219,7 @@ Description: mutmut - python mutation tester
203
219
 
204
220
  mutmut run --disable-mutation-types=string,decorator
205
221
 
206
- Inversly, you can also only specify to only run specific mutations with ``--enable-mutation-types``.
222
+ Inversely, you can also only specify to only run specific mutations with ``--enable-mutation-types``.
207
223
  Note that ``--disable-mutation-types`` and ``--enable-mutation-types`` are exclusive and cannot
208
224
  be combined.
209
225
 
@@ -48,16 +48,24 @@ You can get started with a simple:
48
48
 
49
49
  This will by default run pytest (or unittest if pytest is unavailable)
50
50
  on tests in the "tests" or "test" folder and
51
- it will try to figure out where the code to mutate lies. Run
51
+ it will try to figure out where the code to mutate lies.
52
+
53
+ NOTE that mutmut will apply the mutations directly, one at a time;
54
+ it is **highly** recommended to add all changes to source control
55
+ before running.
56
+
57
+ Enter
52
58
 
53
59
  .. code-block:: console
54
60
 
55
- mutmut --help
61
+ mutmut run --help
56
62
 
57
63
  for the available flags, to use other runners, etc. The recommended way to use
58
- mutmut if the defaults aren't working for you is to add a block in ``setup.cfg``.
64
+ mutmut if the defaults aren't working for you is to add a
65
+ block in ``setup.cfg`` or ``project.toml``.
59
66
  Then when you come back to mutmut weeks later you don't have to figure out the
60
- flags again, just run ``mutmut run`` and it works. Like this:
67
+ flags again, just run ``mutmut run`` and it works.
68
+ Like this in ``setup.cfg``:
61
69
 
62
70
  .. code-block:: ini
63
71
 
@@ -68,6 +76,14 @@ flags again, just run ``mutmut run`` and it works. Like this:
68
76
  tests_dir=tests/
69
77
  dict_synonyms=Struct, NamedStruct
70
78
 
79
+ or like this in ``pyproject.toml``:
80
+
81
+ .. code-block:: ini
82
+
83
+ [tool.mutmut]
84
+ paths_to_mutate="src"
85
+ runner="python -m hammett -x"
86
+
71
87
  To use multiple paths either in the ``paths_to_mutate`` or ``tests_dir`` option
72
88
  use a comma or colon separated list. For example:
73
89
 
@@ -86,11 +102,11 @@ grouped by file. You can now look at a specific mutant diff with ``mutmut show 3
86
102
  all mutants for a specific file with ``mutmut show path/to/file.py`` or all mutants
87
103
  with ``mutmut show all``.
88
104
 
89
-
90
105
  You can also write a mutant to disk with ``mutmut apply 3``. You should **REALLY**
91
106
  have the file you mutate under source code control and committed before you apply
92
107
  a mutant!
93
108
 
109
+ To generate a HTML report for a web browser: ``mutmut html``
94
110
 
95
111
  Whitelisting
96
112
  ------------
@@ -195,7 +211,7 @@ by comma:
195
211
 
196
212
  mutmut run --disable-mutation-types=string,decorator
197
213
 
198
- Inversly, you can also only specify to only run specific mutations with ``--enable-mutation-types``.
214
+ Inversely, you can also only specify to only run specific mutations with ``--enable-mutation-types``.
199
215
  Note that ``--disable-mutation-types`` and ``--enable-mutation-types`` are exclusive and cannot
200
216
  be combined.
201
217
 
@@ -1,4 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
2
4
  import fnmatch
3
5
  import itertools
4
6
  import multiprocessing
@@ -10,6 +12,7 @@ import sys
10
12
  import toml
11
13
  from configparser import ConfigParser
12
14
  from copy import copy as copy_obj
15
+ from dataclasses import dataclass, field
13
16
  from functools import wraps
14
17
  from io import (
15
18
  open,
@@ -25,11 +28,12 @@ from threading import (
25
28
  Thread,
26
29
  )
27
30
  from time import time
31
+ from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple
28
32
 
29
33
  from parso import parse
30
- from parso.python.tree import Name, Number, Keyword
34
+ from parso.python.tree import Name, Number, Keyword, FStringStart, FStringEnd
31
35
 
32
- __version__ = '2.4.3'
36
+ __version__ = '2.4.5'
33
37
 
34
38
 
35
39
  if os.getcwd() not in sys.path:
@@ -40,21 +44,12 @@ except ImportError:
40
44
  mutmut_config = None
41
45
 
42
46
 
43
- class RelativeMutationID(object):
44
- def __init__(self, line, index, line_number, filename=None):
45
- self.line = line
46
- self.index = index
47
- self.line_number = line_number
48
- self.filename = filename
49
-
50
- def __repr__(self):
51
- return 'MutationID(line="{}", index={}, line_number={}, filename={})'.format(self.line, self.index, self.line_number, self.filename)
52
-
53
- def __eq__(self, other):
54
- return (self.line, self.index, self.line_number) == (other.line, other.index, other.line_number)
55
-
56
- def __hash__(self):
57
- return hash((self.line, self.index, self.line_number))
47
+ @dataclass(frozen=True)
48
+ class RelativeMutationID:
49
+ line: str
50
+ index: int
51
+ line_number: int
52
+ filename: Optional[str] = field(default=None, compare=False, hash=False)
58
53
 
59
54
 
60
55
  ALL = RelativeMutationID(filename='%all%', line='%all%', index=-1, line_number=-1)
@@ -64,7 +59,7 @@ class InvalidASTPatternException(Exception):
64
59
  pass
65
60
 
66
61
 
67
- class ASTPattern(object):
62
+ class ASTPattern:
68
63
  def __init__(self, source, **definitions):
69
64
  if definitions is None:
70
65
  definitions = {}
@@ -251,6 +246,23 @@ def string_mutation(value, **_):
251
246
  return prefix + value[0] + 'XX' + value[1:-1] + 'XX' + value[-1]
252
247
 
253
248
 
249
+ def fstring_mutation(children, **_):
250
+ fstring_start: FStringStart = children[0]
251
+ fstring_end: FStringEnd = children[-1]
252
+
253
+ children = children[:] # we need to copy the list here, to not get in place mutation on the next line!
254
+
255
+ children[0] = FStringStart(fstring_start.value + 'XX',
256
+ start_pos=fstring_start.start_pos,
257
+ prefix=fstring_start.prefix)
258
+
259
+ children[-1] = FStringEnd('XX' + fstring_end.value,
260
+ start_pos=fstring_end.start_pos,
261
+ prefix=fstring_end.prefix)
262
+
263
+ return children
264
+
265
+
254
266
  def partition_node_list(nodes, value):
255
267
  for i, n in enumerate(nodes):
256
268
  if hasattr(n, 'value') and n.value == value:
@@ -271,12 +283,10 @@ def lambda_mutation(children, **_):
271
283
  NEWLINE = {'formatting': [], 'indent': '', 'type': 'endl', 'value': ''}
272
284
 
273
285
 
274
- def argument_mutation(children, context, **_):
286
+ def argument_mutation(children, context: Context, **_):
275
287
  """Mutate the arguments one by one from dict(a=b) to dict(aXXX=b).
276
288
 
277
289
  This is similar to the mutation of dict literals in the form {'a': b}.
278
-
279
- :type context: Context
280
290
  """
281
291
  if len(context.stack) >= 3 and context.stack[-3].type in ('power', 'atom_expr'):
282
292
  stack_pos_of_power_node = -3
@@ -444,6 +454,7 @@ mutations_by_type = {
444
454
  'number': dict(value=number_mutation),
445
455
  'name': dict(value=name_mutation),
446
456
  'string': dict(value=string_mutation),
457
+ 'fstring': dict(children=fstring_mutation),
447
458
  'argument': dict(children=argument_mutation),
448
459
  'or_test': dict(children=and_or_test_mutation),
449
460
  'and_test': dict(children=and_or_test_mutation),
@@ -456,7 +467,7 @@ mutations_by_type = {
456
467
  # TODO: detect regexes and mutate them in nasty ways? Maybe mutate all strings as if they are regexes
457
468
 
458
469
 
459
- def should_exclude(context, config):
470
+ def should_exclude(context, config: Optional[Config]):
460
471
  if config is None or config.covered_lines_by_filename is None:
461
472
  return False
462
473
 
@@ -477,8 +488,16 @@ def should_exclude(context, config):
477
488
  return False
478
489
 
479
490
 
480
- class Context(object):
481
- def __init__(self, source=None, mutation_id=ALL, dict_synonyms=None, filename=None, config=None, index=0):
491
+ class Context:
492
+ def __init__(
493
+ self,
494
+ source: Optional[str] = None,
495
+ mutation_id=ALL,
496
+ dict_synonyms=None,
497
+ filename=None,
498
+ config: Optional[Config] = None,
499
+ index=0,
500
+ ):
482
501
  self.index = index
483
502
  self.remove_newline_at_end = False
484
503
  self._source = None
@@ -544,11 +563,9 @@ class Context(object):
544
563
  return self.mutation_id in (ALL, self.mutation_id_of_current_index)
545
564
 
546
565
 
547
- def mutate(context):
566
+ def mutate(context: Context) -> Tuple[str, int]:
548
567
  """
549
- :type context: Context
550
568
  :return: tuple of mutated source code and number of mutations performed
551
- :rtype: Tuple[str, int]
552
569
  """
553
570
  try:
554
571
  result = parse(context.source, error_recovery=False)
@@ -572,10 +589,7 @@ def mutate(context):
572
589
  return mutated_source, len(context.performed_mutation_ids)
573
590
 
574
591
 
575
- def mutate_node(node, context):
576
- """
577
- :type context: Context
578
- """
592
+ def mutate_node(node, context: Context):
579
593
  context.stack.append(node)
580
594
  try:
581
595
  if node.type in ('tfpdef', 'import_from', 'import_name'):
@@ -647,10 +661,7 @@ def mutate_node(node, context):
647
661
  context.stack.pop()
648
662
 
649
663
 
650
- def mutate_list_of_nodes(node, context):
651
- """
652
- :type context: Context
653
- """
664
+ def mutate_list_of_nodes(node, context: Context):
654
665
  return_annotation_started = False
655
666
 
656
667
  for child_node in node.children:
@@ -670,22 +681,13 @@ def mutate_list_of_nodes(node, context):
670
681
  return
671
682
 
672
683
 
673
- def list_mutations(context):
674
- """
675
- :type context: Context
676
- """
684
+ def list_mutations(context: Context):
677
685
  assert context.mutation_id == ALL
678
686
  mutate(context)
679
687
  return context.performed_mutation_ids
680
688
 
681
689
 
682
- def mutate_file(backup, context):
683
- """
684
- :type backup: bool
685
- :type context: Context
686
-
687
- :return: Tuple[str, str]
688
- """
690
+ def mutate_file(backup: bool, context: Context) -> Tuple[str, str]:
689
691
  with open(context.filename) as f:
690
692
  original = f.read()
691
693
  if backup:
@@ -697,7 +699,13 @@ def mutate_file(backup, context):
697
699
  return original, mutated
698
700
 
699
701
 
700
- def queue_mutants(*, progress, config, mutants_queue, mutations_by_file):
702
+ def queue_mutants(
703
+ *,
704
+ progress: Progress,
705
+ config: Config,
706
+ mutants_queue,
707
+ mutations_by_file: Dict[str, List[RelativeMutationID]],
708
+ ):
701
709
  from mutmut.cache import get_cached_mutation_statuses
702
710
 
703
711
  try:
@@ -792,7 +800,9 @@ def run_mutation(context: Context, callback) -> str:
792
800
  return BAD_TIMEOUT
793
801
 
794
802
  time_elapsed = time() - start
795
- if not survived and time_elapsed > config.test_time_base + (config.baseline_time_elapsed * config.test_time_multipler):
803
+ if not survived and time_elapsed > config.test_time_base + (
804
+ config.baseline_time_elapsed * config.test_time_multiplier
805
+ ):
796
806
  return OK_SUSPICIOUS
797
807
 
798
808
  if survived:
@@ -812,31 +822,31 @@ def run_mutation(context: Context, callback) -> str:
812
822
  callback(result)
813
823
 
814
824
 
815
- class Config(object):
816
- def __init__(self, swallow_output, test_command, covered_lines_by_filename,
817
- baseline_time_elapsed, test_time_multiplier, test_time_base,
818
- dict_synonyms, total, using_testmon,
819
- tests_dirs, hash_of_tests, pre_mutation, post_mutation,
820
- coverage_data, paths_to_mutate, mutation_types_to_apply, no_progress, ci, rerun_all):
821
- self.swallow_output = swallow_output
822
- self.test_command = self._default_test_command = test_command
823
- self.covered_lines_by_filename = covered_lines_by_filename
824
- self.baseline_time_elapsed = baseline_time_elapsed
825
- self.test_time_multipler = test_time_multiplier
826
- self.test_time_base = test_time_base
827
- self.dict_synonyms = dict_synonyms
828
- self.total = total
829
- self.using_testmon = using_testmon
830
- self.tests_dirs = tests_dirs
831
- self.hash_of_tests = hash_of_tests
832
- self.post_mutation = post_mutation
833
- self.pre_mutation = pre_mutation
834
- self.coverage_data = coverage_data
835
- self.paths_to_mutate = paths_to_mutate
836
- self.mutation_types_to_apply = mutation_types_to_apply
837
- self.no_progress = no_progress
838
- self.ci = ci
839
- self.rerun_all = rerun_all
825
+ @dataclass
826
+ class Config:
827
+ swallow_output: bool
828
+ test_command: str
829
+ _default_test_command: str = field(init=False)
830
+ covered_lines_by_filename: Optional[Dict[str, set[Optional[int]]]]
831
+ baseline_time_elapsed: float
832
+ test_time_multiplier: float
833
+ test_time_base: float
834
+ dict_synonyms: List[str]
835
+ total: int
836
+ using_testmon: bool
837
+ tests_dirs: List[str]
838
+ hash_of_tests: str
839
+ post_mutation: str
840
+ pre_mutation: str
841
+ coverage_data: Dict[str, Dict[int, List[str]]]
842
+ paths_to_mutate: List[str]
843
+ mutation_types_to_apply: Set[str]
844
+ no_progress: bool
845
+ ci: bool
846
+ rerun_all: bool
847
+
848
+ def __post_init__(self):
849
+ self._default_test_command = self.test_command
840
850
 
841
851
 
842
852
  def tests_pass(config: Config, callback) -> bool:
@@ -905,11 +915,9 @@ def status_printer():
905
915
  return p
906
916
 
907
917
 
908
- def guess_paths_to_mutate():
909
- """Guess the path to source code to mutate
910
918
 
911
- :rtype: str
912
- """
919
+ def guess_paths_to_mutate() -> str:
920
+ """Guess the path to source code to mutate"""
913
921
  this_dir = os.getcwd().split(os.sep)[-1]
914
922
  if isdir('lib'):
915
923
  return 'lib'
@@ -932,7 +940,7 @@ def guess_paths_to_mutate():
932
940
  'section.')
933
941
 
934
942
 
935
- class Progress(object):
943
+ class Progress:
936
944
  def __init__(self, total, output_legend, no_progress=False):
937
945
  self.total = total
938
946
  self.output_legend = output_legend
@@ -991,24 +999,18 @@ def get_mutations_by_file_from_cache(mutation_pk):
991
999
  return {filename: [mutation_id]}
992
1000
 
993
1001
 
994
- def popen_streaming_output(cmd, callback, timeout=None):
1002
+ def popen_streaming_output(
1003
+ cmd: str, callback: Callable[[str], None], timeout: Optional[float] = None
1004
+ ) -> int:
995
1005
  """Open a subprocess and stream its output without hard-blocking.
996
1006
 
997
1007
  :param cmd: the command to execute within the subprocess
998
- :type cmd: str
999
-
1000
1008
  :param callback: function that intakes the subprocess' stdout line by line.
1001
1009
  It is called for each line received from the subprocess' stdout stream.
1002
- :type callback: Callable[[Context], bool]
1003
-
1004
1010
  :param timeout: the timeout time of the subprocess
1005
- :type timeout: float
1006
-
1007
1011
  :raises TimeoutError: if the subprocess' execution time exceeds
1008
1012
  the timeout time
1009
-
1010
1013
  :return: the return code of the executed subprocess
1011
- :rtype: int
1012
1014
  """
1013
1015
  if os.name == 'nt': # pragma: no cover
1014
1016
  process = subprocess.Popen(
@@ -1070,7 +1072,7 @@ def popen_streaming_output(cmd, callback, timeout=None):
1070
1072
  return process.returncode
1071
1073
 
1072
1074
 
1073
- def hammett_tests_pass(config, callback):
1075
+ def hammett_tests_pass(config: Config, callback) -> bool:
1074
1076
  # noinspection PyUnresolvedReferences
1075
1077
  from hammett import main_cli
1076
1078
  modules_before = set(sys.modules.keys())
@@ -1125,12 +1127,12 @@ def hammett_tests_pass(config, callback):
1125
1127
 
1126
1128
  CYCLE_PROCESS_AFTER = 100
1127
1129
 
1128
- def run_mutation_tests(config, progress, mutations_by_file):
1129
- """
1130
- :type config: Config
1131
- :type progress: Progress
1132
- :type mutations_by_file: dict[str, list[RelativeMutationID]]
1133
- """
1130
+
1131
+ def run_mutation_tests(
1132
+ config: Config,
1133
+ progress: Progress,
1134
+ mutations_by_file: Dict[str, List[RelativeMutationID]],
1135
+ ):
1134
1136
  from mutmut.cache import update_mutant_status
1135
1137
 
1136
1138
  # Need to explicitly use the spawn method for python < 3.8 on macOS
@@ -1193,10 +1195,9 @@ def run_mutation_tests(config, progress, mutations_by_file):
1193
1195
  update_mutant_status(file_to_mutate=filename, mutation_id=mutation_id, status=status, tests_hash=config.hash_of_tests)
1194
1196
 
1195
1197
 
1196
- def read_coverage_data():
1198
+ def read_coverage_data() -> Dict[str, Dict[int, List[str]]]:
1197
1199
  """
1198
1200
  Reads the coverage database and returns a dictionary which maps the filenames to the covered lines and their contexts.
1199
- :rtype: dict[str, dict[int, list[str]]]
1200
1201
  """
1201
1202
  try:
1202
1203
  # noinspection PyPackageRequirements,PyUnresolvedReferences
@@ -1209,7 +1210,7 @@ def read_coverage_data():
1209
1210
  return {filepath: data.contexts_by_lineno(filepath) for filepath in data.measured_files()}
1210
1211
 
1211
1212
 
1212
- def read_patch_data(patch_file_path):
1213
+ def read_patch_data(patch_file_path: str):
1213
1214
  try:
1214
1215
  # noinspection PyPackageRequirements
1215
1216
  import whatthepatch
@@ -1220,16 +1221,16 @@ def read_patch_data(patch_file_path):
1220
1221
 
1221
1222
  return {
1222
1223
  diff.header.new_path: {change.new for change in diff.changes if change.old is None}
1223
- for diff in diffs
1224
+ for diff in diffs if diff.changes
1224
1225
  }
1225
1226
 
1226
1227
 
1227
- def add_mutations_by_file(mutations_by_file, filename, dict_synonyms, config):
1228
- """
1229
- :type mutations_by_file: dict[str, list[RelativeMutationID]]
1230
- :type filename: str
1231
- :type dict_synonyms: list[str]
1232
- """
1228
+ def add_mutations_by_file(
1229
+ mutations_by_file: Dict[str, List[RelativeMutationID]],
1230
+ filename: str,
1231
+ dict_synonyms: List[str],
1232
+ config: Optional[Config],
1233
+ ):
1233
1234
  with open(filename) as f:
1234
1235
  source = f.read()
1235
1236
  context = Context(
@@ -1242,27 +1243,28 @@ def add_mutations_by_file(mutations_by_file, filename, dict_synonyms, config):
1242
1243
  try:
1243
1244
  mutations_by_file[filename] = list_mutations(context)
1244
1245
  from mutmut.cache import register_mutants
1246
+
1245
1247
  register_mutants(mutations_by_file)
1246
1248
  except Exception as e:
1247
- raise RuntimeError('Failed while creating mutations for {}, for line "{}"'.format(context.filename, context.current_source_line)) from e
1249
+ raise RuntimeError(
1250
+ 'Failed while creating mutations for {}, for line "{}"'.format(
1251
+ context.filename, context.current_source_line
1252
+ )
1253
+ ) from e
1248
1254
 
1249
1255
 
1250
- def python_source_files(path, tests_dirs, paths_to_exclude=None):
1256
+ def python_source_files(
1257
+ path: str, tests_dirs: List[str], paths_to_exclude: Optional[List[str]] = None
1258
+ ) -> Iterator[str]:
1251
1259
  """Attempt to guess where the python source files to mutate are and yield
1252
1260
  their paths
1253
1261
 
1254
1262
  :param path: path to a python source file or package directory
1255
- :type path: str
1256
-
1257
1263
  :param tests_dirs: list of directory paths containing test files
1258
1264
  (we do not want to mutate these!)
1259
- :type tests_dirs: list[str]
1260
-
1261
1265
  :param paths_to_exclude: list of UNIX filename patterns to exclude
1262
- :type paths_to_exclude: list[str]
1263
1266
 
1264
1267
  :return: generator listing the paths to the python source files to mutate
1265
- :rtype: Generator[str, None, None]
1266
1268
  """
1267
1269
  paths_to_exclude = paths_to_exclude or []
1268
1270
  if isdir(path):
@@ -1279,10 +1281,12 @@ def python_source_files(path, tests_dirs, paths_to_exclude=None):
1279
1281
  yield path
1280
1282
 
1281
1283
 
1282
- def compute_exit_code(progress, exception=None, ci=False):
1284
+ def compute_exit_code(
1285
+ progress: Progress, exception: Optional[Exception] = None, ci: bool = False
1286
+ ) -> int:
1283
1287
  """Compute an exit code for mutmut mutation testing
1284
1288
 
1285
- The following exit codes are available for mutmut:
1289
+ The following exit codes are available for mutmut (as documented for the CLI run command):
1286
1290
  * 0 if all mutants were killed (OK_KILLED)
1287
1291
  * 1 if a fatal error occurred
1288
1292
  * 2 if one or more mutants survived (BAD_SURVIVED)
@@ -1296,14 +1300,10 @@ def compute_exit_code(progress, exception=None, ci=False):
1296
1300
  1 for a fatal error or 0 for any other case.
1297
1301
 
1298
1302
  :param exception:
1299
- :type exception: Exception
1300
1303
  :param progress:
1301
- :type progress: Progress
1302
1304
  :param ci:
1303
- :type ci: bool
1304
1305
 
1305
1306
  :return: integer noting the exit code of the mutation tests.
1306
- :rtype: int
1307
1307
  """
1308
1308
  code = 0
1309
1309
  if exception is not None: