mutmut 2.4.4__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.
- {mutmut-2.4.4 → mutmut-2.4.5}/AUTHORS.rst +8 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/HISTORY.rst +11 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/PKG-INFO +22 -6
- {mutmut-2.4.4 → mutmut-2.4.5}/README.rst +21 -5
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut/__init__.py +98 -116
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut/__main__.py +74 -24
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut/cache.py +13 -7
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/PKG-INFO +22 -6
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/SOURCES.txt +5 -1
- mutmut-2.4.5/tests/test_cache.py +40 -0
- mutmut-2.4.5/tests/test_init.py +170 -0
- mutmut-2.4.5/tests/test_main.py +706 -0
- mutmut-2.4.5/tests/test_mutation.py +393 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/LICENSE +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/MANIFEST.in +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/dependency_links.txt +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/entry_points.txt +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/not-zip-safe +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/requires.txt +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/mutmut.egg-info/top_level.txt +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/requirements.txt +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/setup.cfg +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/setup.py +0 -0
- {mutmut-2.4.4 → mutmut-2.4.5}/test_requirements.txt +0 -0
|
@@ -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,6 +1,17 @@
|
|
|
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
|
+
|
|
4
15
|
2.4.4
|
|
5
16
|
~~~~~
|
|
6
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mutmut
|
|
3
|
-
Version: 2.4.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
------------
|
|
@@ -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.
|
|
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
|
|
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.
|
|
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
|
------------
|
|
@@ -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
34
|
from parso.python.tree import Name, Number, Keyword, FStringStart, FStringEnd
|
|
31
35
|
|
|
32
|
-
__version__ = '2.4.
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
62
|
+
class ASTPattern:
|
|
68
63
|
def __init__(self, source, **definitions):
|
|
69
64
|
if definitions is None:
|
|
70
65
|
definitions = {}
|
|
@@ -288,12 +283,10 @@ def lambda_mutation(children, **_):
|
|
|
288
283
|
NEWLINE = {'formatting': [], 'indent': '', 'type': 'endl', 'value': ''}
|
|
289
284
|
|
|
290
285
|
|
|
291
|
-
def argument_mutation(children, context, **_):
|
|
286
|
+
def argument_mutation(children, context: Context, **_):
|
|
292
287
|
"""Mutate the arguments one by one from dict(a=b) to dict(aXXX=b).
|
|
293
288
|
|
|
294
289
|
This is similar to the mutation of dict literals in the form {'a': b}.
|
|
295
|
-
|
|
296
|
-
:type context: Context
|
|
297
290
|
"""
|
|
298
291
|
if len(context.stack) >= 3 and context.stack[-3].type in ('power', 'atom_expr'):
|
|
299
292
|
stack_pos_of_power_node = -3
|
|
@@ -474,7 +467,7 @@ mutations_by_type = {
|
|
|
474
467
|
# TODO: detect regexes and mutate them in nasty ways? Maybe mutate all strings as if they are regexes
|
|
475
468
|
|
|
476
469
|
|
|
477
|
-
def should_exclude(context, config):
|
|
470
|
+
def should_exclude(context, config: Optional[Config]):
|
|
478
471
|
if config is None or config.covered_lines_by_filename is None:
|
|
479
472
|
return False
|
|
480
473
|
|
|
@@ -495,8 +488,16 @@ def should_exclude(context, config):
|
|
|
495
488
|
return False
|
|
496
489
|
|
|
497
490
|
|
|
498
|
-
class Context
|
|
499
|
-
def __init__(
|
|
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
|
+
):
|
|
500
501
|
self.index = index
|
|
501
502
|
self.remove_newline_at_end = False
|
|
502
503
|
self._source = None
|
|
@@ -562,11 +563,9 @@ class Context(object):
|
|
|
562
563
|
return self.mutation_id in (ALL, self.mutation_id_of_current_index)
|
|
563
564
|
|
|
564
565
|
|
|
565
|
-
def mutate(context):
|
|
566
|
+
def mutate(context: Context) -> Tuple[str, int]:
|
|
566
567
|
"""
|
|
567
|
-
:type context: Context
|
|
568
568
|
:return: tuple of mutated source code and number of mutations performed
|
|
569
|
-
:rtype: Tuple[str, int]
|
|
570
569
|
"""
|
|
571
570
|
try:
|
|
572
571
|
result = parse(context.source, error_recovery=False)
|
|
@@ -590,10 +589,7 @@ def mutate(context):
|
|
|
590
589
|
return mutated_source, len(context.performed_mutation_ids)
|
|
591
590
|
|
|
592
591
|
|
|
593
|
-
def mutate_node(node, context):
|
|
594
|
-
"""
|
|
595
|
-
:type context: Context
|
|
596
|
-
"""
|
|
592
|
+
def mutate_node(node, context: Context):
|
|
597
593
|
context.stack.append(node)
|
|
598
594
|
try:
|
|
599
595
|
if node.type in ('tfpdef', 'import_from', 'import_name'):
|
|
@@ -665,10 +661,7 @@ def mutate_node(node, context):
|
|
|
665
661
|
context.stack.pop()
|
|
666
662
|
|
|
667
663
|
|
|
668
|
-
def mutate_list_of_nodes(node, context):
|
|
669
|
-
"""
|
|
670
|
-
:type context: Context
|
|
671
|
-
"""
|
|
664
|
+
def mutate_list_of_nodes(node, context: Context):
|
|
672
665
|
return_annotation_started = False
|
|
673
666
|
|
|
674
667
|
for child_node in node.children:
|
|
@@ -688,22 +681,13 @@ def mutate_list_of_nodes(node, context):
|
|
|
688
681
|
return
|
|
689
682
|
|
|
690
683
|
|
|
691
|
-
def list_mutations(context):
|
|
692
|
-
"""
|
|
693
|
-
:type context: Context
|
|
694
|
-
"""
|
|
684
|
+
def list_mutations(context: Context):
|
|
695
685
|
assert context.mutation_id == ALL
|
|
696
686
|
mutate(context)
|
|
697
687
|
return context.performed_mutation_ids
|
|
698
688
|
|
|
699
689
|
|
|
700
|
-
def mutate_file(backup, context):
|
|
701
|
-
"""
|
|
702
|
-
:type backup: bool
|
|
703
|
-
:type context: Context
|
|
704
|
-
|
|
705
|
-
:return: Tuple[str, str]
|
|
706
|
-
"""
|
|
690
|
+
def mutate_file(backup: bool, context: Context) -> Tuple[str, str]:
|
|
707
691
|
with open(context.filename) as f:
|
|
708
692
|
original = f.read()
|
|
709
693
|
if backup:
|
|
@@ -715,7 +699,13 @@ def mutate_file(backup, context):
|
|
|
715
699
|
return original, mutated
|
|
716
700
|
|
|
717
701
|
|
|
718
|
-
def queue_mutants(
|
|
702
|
+
def queue_mutants(
|
|
703
|
+
*,
|
|
704
|
+
progress: Progress,
|
|
705
|
+
config: Config,
|
|
706
|
+
mutants_queue,
|
|
707
|
+
mutations_by_file: Dict[str, List[RelativeMutationID]],
|
|
708
|
+
):
|
|
719
709
|
from mutmut.cache import get_cached_mutation_statuses
|
|
720
710
|
|
|
721
711
|
try:
|
|
@@ -810,7 +800,9 @@ def run_mutation(context: Context, callback) -> str:
|
|
|
810
800
|
return BAD_TIMEOUT
|
|
811
801
|
|
|
812
802
|
time_elapsed = time() - start
|
|
813
|
-
if not survived and time_elapsed > config.test_time_base + (
|
|
803
|
+
if not survived and time_elapsed > config.test_time_base + (
|
|
804
|
+
config.baseline_time_elapsed * config.test_time_multiplier
|
|
805
|
+
):
|
|
814
806
|
return OK_SUSPICIOUS
|
|
815
807
|
|
|
816
808
|
if survived:
|
|
@@ -830,31 +822,31 @@ def run_mutation(context: Context, callback) -> str:
|
|
|
830
822
|
callback(result)
|
|
831
823
|
|
|
832
824
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
self.
|
|
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
|
|
858
850
|
|
|
859
851
|
|
|
860
852
|
def tests_pass(config: Config, callback) -> bool:
|
|
@@ -923,11 +915,9 @@ def status_printer():
|
|
|
923
915
|
return p
|
|
924
916
|
|
|
925
917
|
|
|
926
|
-
def guess_paths_to_mutate():
|
|
927
|
-
"""Guess the path to source code to mutate
|
|
928
918
|
|
|
929
|
-
|
|
930
|
-
"""
|
|
919
|
+
def guess_paths_to_mutate() -> str:
|
|
920
|
+
"""Guess the path to source code to mutate"""
|
|
931
921
|
this_dir = os.getcwd().split(os.sep)[-1]
|
|
932
922
|
if isdir('lib'):
|
|
933
923
|
return 'lib'
|
|
@@ -950,7 +940,7 @@ def guess_paths_to_mutate():
|
|
|
950
940
|
'section.')
|
|
951
941
|
|
|
952
942
|
|
|
953
|
-
class Progress
|
|
943
|
+
class Progress:
|
|
954
944
|
def __init__(self, total, output_legend, no_progress=False):
|
|
955
945
|
self.total = total
|
|
956
946
|
self.output_legend = output_legend
|
|
@@ -1009,24 +999,18 @@ def get_mutations_by_file_from_cache(mutation_pk):
|
|
|
1009
999
|
return {filename: [mutation_id]}
|
|
1010
1000
|
|
|
1011
1001
|
|
|
1012
|
-
def popen_streaming_output(
|
|
1002
|
+
def popen_streaming_output(
|
|
1003
|
+
cmd: str, callback: Callable[[str], None], timeout: Optional[float] = None
|
|
1004
|
+
) -> int:
|
|
1013
1005
|
"""Open a subprocess and stream its output without hard-blocking.
|
|
1014
1006
|
|
|
1015
1007
|
:param cmd: the command to execute within the subprocess
|
|
1016
|
-
:type cmd: str
|
|
1017
|
-
|
|
1018
1008
|
:param callback: function that intakes the subprocess' stdout line by line.
|
|
1019
1009
|
It is called for each line received from the subprocess' stdout stream.
|
|
1020
|
-
:type callback: Callable[[Context], bool]
|
|
1021
|
-
|
|
1022
1010
|
:param timeout: the timeout time of the subprocess
|
|
1023
|
-
:type timeout: float
|
|
1024
|
-
|
|
1025
1011
|
:raises TimeoutError: if the subprocess' execution time exceeds
|
|
1026
1012
|
the timeout time
|
|
1027
|
-
|
|
1028
1013
|
:return: the return code of the executed subprocess
|
|
1029
|
-
:rtype: int
|
|
1030
1014
|
"""
|
|
1031
1015
|
if os.name == 'nt': # pragma: no cover
|
|
1032
1016
|
process = subprocess.Popen(
|
|
@@ -1088,7 +1072,7 @@ def popen_streaming_output(cmd, callback, timeout=None):
|
|
|
1088
1072
|
return process.returncode
|
|
1089
1073
|
|
|
1090
1074
|
|
|
1091
|
-
def hammett_tests_pass(config, callback):
|
|
1075
|
+
def hammett_tests_pass(config: Config, callback) -> bool:
|
|
1092
1076
|
# noinspection PyUnresolvedReferences
|
|
1093
1077
|
from hammett import main_cli
|
|
1094
1078
|
modules_before = set(sys.modules.keys())
|
|
@@ -1143,12 +1127,12 @@ def hammett_tests_pass(config, callback):
|
|
|
1143
1127
|
|
|
1144
1128
|
CYCLE_PROCESS_AFTER = 100
|
|
1145
1129
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1130
|
+
|
|
1131
|
+
def run_mutation_tests(
|
|
1132
|
+
config: Config,
|
|
1133
|
+
progress: Progress,
|
|
1134
|
+
mutations_by_file: Dict[str, List[RelativeMutationID]],
|
|
1135
|
+
):
|
|
1152
1136
|
from mutmut.cache import update_mutant_status
|
|
1153
1137
|
|
|
1154
1138
|
# Need to explicitly use the spawn method for python < 3.8 on macOS
|
|
@@ -1211,10 +1195,9 @@ def run_mutation_tests(config, progress, mutations_by_file):
|
|
|
1211
1195
|
update_mutant_status(file_to_mutate=filename, mutation_id=mutation_id, status=status, tests_hash=config.hash_of_tests)
|
|
1212
1196
|
|
|
1213
1197
|
|
|
1214
|
-
def read_coverage_data():
|
|
1198
|
+
def read_coverage_data() -> Dict[str, Dict[int, List[str]]]:
|
|
1215
1199
|
"""
|
|
1216
1200
|
Reads the coverage database and returns a dictionary which maps the filenames to the covered lines and their contexts.
|
|
1217
|
-
:rtype: dict[str, dict[int, list[str]]]
|
|
1218
1201
|
"""
|
|
1219
1202
|
try:
|
|
1220
1203
|
# noinspection PyPackageRequirements,PyUnresolvedReferences
|
|
@@ -1227,7 +1210,7 @@ def read_coverage_data():
|
|
|
1227
1210
|
return {filepath: data.contexts_by_lineno(filepath) for filepath in data.measured_files()}
|
|
1228
1211
|
|
|
1229
1212
|
|
|
1230
|
-
def read_patch_data(patch_file_path):
|
|
1213
|
+
def read_patch_data(patch_file_path: str):
|
|
1231
1214
|
try:
|
|
1232
1215
|
# noinspection PyPackageRequirements
|
|
1233
1216
|
import whatthepatch
|
|
@@ -1242,12 +1225,12 @@ def read_patch_data(patch_file_path):
|
|
|
1242
1225
|
}
|
|
1243
1226
|
|
|
1244
1227
|
|
|
1245
|
-
def add_mutations_by_file(
|
|
1246
|
-
|
|
1247
|
-
:
|
|
1248
|
-
:
|
|
1249
|
-
:
|
|
1250
|
-
|
|
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
|
+
):
|
|
1251
1234
|
with open(filename) as f:
|
|
1252
1235
|
source = f.read()
|
|
1253
1236
|
context = Context(
|
|
@@ -1260,27 +1243,28 @@ def add_mutations_by_file(mutations_by_file, filename, dict_synonyms, config):
|
|
|
1260
1243
|
try:
|
|
1261
1244
|
mutations_by_file[filename] = list_mutations(context)
|
|
1262
1245
|
from mutmut.cache import register_mutants
|
|
1246
|
+
|
|
1263
1247
|
register_mutants(mutations_by_file)
|
|
1264
1248
|
except Exception as e:
|
|
1265
|
-
raise RuntimeError(
|
|
1249
|
+
raise RuntimeError(
|
|
1250
|
+
'Failed while creating mutations for {}, for line "{}"'.format(
|
|
1251
|
+
context.filename, context.current_source_line
|
|
1252
|
+
)
|
|
1253
|
+
) from e
|
|
1266
1254
|
|
|
1267
1255
|
|
|
1268
|
-
def python_source_files(
|
|
1256
|
+
def python_source_files(
|
|
1257
|
+
path: str, tests_dirs: List[str], paths_to_exclude: Optional[List[str]] = None
|
|
1258
|
+
) -> Iterator[str]:
|
|
1269
1259
|
"""Attempt to guess where the python source files to mutate are and yield
|
|
1270
1260
|
their paths
|
|
1271
1261
|
|
|
1272
1262
|
:param path: path to a python source file or package directory
|
|
1273
|
-
:type path: str
|
|
1274
|
-
|
|
1275
1263
|
:param tests_dirs: list of directory paths containing test files
|
|
1276
1264
|
(we do not want to mutate these!)
|
|
1277
|
-
:type tests_dirs: list[str]
|
|
1278
|
-
|
|
1279
1265
|
:param paths_to_exclude: list of UNIX filename patterns to exclude
|
|
1280
|
-
:type paths_to_exclude: list[str]
|
|
1281
1266
|
|
|
1282
1267
|
:return: generator listing the paths to the python source files to mutate
|
|
1283
|
-
:rtype: Generator[str, None, None]
|
|
1284
1268
|
"""
|
|
1285
1269
|
paths_to_exclude = paths_to_exclude or []
|
|
1286
1270
|
if isdir(path):
|
|
@@ -1297,10 +1281,12 @@ def python_source_files(path, tests_dirs, paths_to_exclude=None):
|
|
|
1297
1281
|
yield path
|
|
1298
1282
|
|
|
1299
1283
|
|
|
1300
|
-
def compute_exit_code(
|
|
1284
|
+
def compute_exit_code(
|
|
1285
|
+
progress: Progress, exception: Optional[Exception] = None, ci: bool = False
|
|
1286
|
+
) -> int:
|
|
1301
1287
|
"""Compute an exit code for mutmut mutation testing
|
|
1302
1288
|
|
|
1303
|
-
The following exit codes are available for mutmut:
|
|
1289
|
+
The following exit codes are available for mutmut (as documented for the CLI run command):
|
|
1304
1290
|
* 0 if all mutants were killed (OK_KILLED)
|
|
1305
1291
|
* 1 if a fatal error occurred
|
|
1306
1292
|
* 2 if one or more mutants survived (BAD_SURVIVED)
|
|
@@ -1314,14 +1300,10 @@ def compute_exit_code(progress, exception=None, ci=False):
|
|
|
1314
1300
|
1 for a fatal error or 0 for any other case.
|
|
1315
1301
|
|
|
1316
1302
|
:param exception:
|
|
1317
|
-
:type exception: Exception
|
|
1318
1303
|
:param progress:
|
|
1319
|
-
:type progress: Progress
|
|
1320
1304
|
:param ci:
|
|
1321
|
-
:type ci: bool
|
|
1322
1305
|
|
|
1323
1306
|
:return: integer noting the exit code of the mutation tests.
|
|
1324
|
-
:rtype: int
|
|
1325
1307
|
"""
|
|
1326
1308
|
code = 0
|
|
1327
1309
|
if exception is not None:
|