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.
- {mutmut-2.4.3 → mutmut-2.4.5}/AUTHORS.rst +8 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/HISTORY.rst +23 -1
- {mutmut-2.4.3 → mutmut-2.4.5}/PKG-INFO +23 -7
- {mutmut-2.4.3 → mutmut-2.4.5}/README.rst +22 -6
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut/__init__.py +118 -118
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut/__main__.py +74 -24
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut/cache.py +13 -7
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/PKG-INFO +23 -7
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/SOURCES.txt +5 -1
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/requires.txt +1 -1
- {mutmut-2.4.3 → mutmut-2.4.5}/requirements.txt +1 -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.3 → mutmut-2.4.5}/LICENSE +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/MANIFEST.in +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/dependency_links.txt +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/entry_points.txt +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/not-zip-safe +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/mutmut.egg-info/top_level.txt +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/setup.cfg +0 -0
- {mutmut-2.4.3 → mutmut-2.4.5}/setup.py +0 -0
- {mutmut-2.4.3 → 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,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
|
+
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
|
------------
|
|
@@ -203,7 +219,7 @@ Description: mutmut - python mutation tester
|
|
|
203
219
|
|
|
204
220
|
mutmut run --disable-mutation-types=string,decorator
|
|
205
221
|
|
|
206
|
-
|
|
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.
|
|
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
|
------------
|
|
@@ -195,7 +211,7 @@ by comma:
|
|
|
195
211
|
|
|
196
212
|
mutmut run --disable-mutation-types=string,decorator
|
|
197
213
|
|
|
198
|
-
|
|
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.
|
|
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 = {}
|
|
@@ -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
|
|
481
|
-
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
|
+
):
|
|
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(
|
|
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 + (
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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(
|
|
1228
|
-
|
|
1229
|
-
:
|
|
1230
|
-
:
|
|
1231
|
-
:
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|