mutmut 3.1.0__tar.gz → 3.2.0__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-3.1.0 → mutmut-3.2.0}/AUTHORS.rst +1 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/HISTORY.rst +14 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/PKG-INFO +1 -1
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut/__init__.py +10 -1
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut/__main__.py +57 -36
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut/__pycache__/__init__.cpython-311.pyc +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut/__pycache__/__init__.cpython-312.pyc +0 -0
- mutmut-3.2.0/mutmut/__pycache__/__main__.cpython-311.pyc +0 -0
- mutmut-3.2.0/mutmut/__pycache__/__main__.cpython-312.pyc +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut/result_browser_layout.tcss +8 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/PKG-INFO +1 -1
- mutmut-3.1.0/mutmut/__pycache__/__main__.cpython-311.pyc +0 -0
- mutmut-3.1.0/mutmut/__pycache__/__main__.cpython-312.pyc +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/LICENSE +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/MANIFEST.in +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/README.rst +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut/.DS_Store +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/SOURCES.txt +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/dependency_links.txt +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/entry_points.txt +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/not-zip-safe +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/requires.txt +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/mutmut.egg-info/top_level.txt +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/requirements.txt +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/setup.cfg +0 -0
- {mutmut-3.1.0 → mutmut-3.2.0}/setup.py +0 -0
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
---------
|
|
3
3
|
|
|
4
|
+
3.2.0
|
|
5
|
+
~~~~~
|
|
6
|
+
|
|
7
|
+
* Timeouts for mutants implemented.
|
|
8
|
+
|
|
9
|
+
* Browser: syntax highlighting for diff view
|
|
10
|
+
|
|
11
|
+
* More fixes for generators.
|
|
12
|
+
|
|
13
|
+
* Fix for `src`-style layout of projects.
|
|
14
|
+
|
|
15
|
+
* Fixed bug where mutmut would recollect all tests on every run, slowing down startup.
|
|
16
|
+
|
|
17
|
+
|
|
4
18
|
3.1.0
|
|
5
19
|
~~~~~
|
|
6
20
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
1
3
|
from parso.python.tree import (
|
|
2
4
|
Keyword,
|
|
3
5
|
Name,
|
|
4
6
|
Number,
|
|
5
7
|
)
|
|
6
8
|
|
|
7
|
-
__version__ = '3.
|
|
9
|
+
__version__ = '3.2.0'
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
# We have a global whitelist for constants of the pattern __all__, __version__, etc
|
|
@@ -22,6 +24,13 @@ dunder_whitelist = [
|
|
|
22
24
|
'copyright',
|
|
23
25
|
]
|
|
24
26
|
|
|
27
|
+
duration_by_test = {}
|
|
28
|
+
stats_time = None
|
|
29
|
+
config = None
|
|
30
|
+
|
|
31
|
+
_stats = set()
|
|
32
|
+
tests_by_mangled_function_name = defaultdict(set)
|
|
33
|
+
|
|
25
34
|
|
|
26
35
|
class SkipException(Exception):
|
|
27
36
|
pass
|
|
@@ -25,6 +25,7 @@ from functools import lru_cache
|
|
|
25
25
|
from hashlib import md5
|
|
26
26
|
from io import TextIOBase
|
|
27
27
|
from json import JSONDecodeError
|
|
28
|
+
from math import ceil
|
|
28
29
|
from os import (
|
|
29
30
|
makedirs,
|
|
30
31
|
walk,
|
|
@@ -36,7 +37,10 @@ from textwrap import (
|
|
|
36
37
|
dedent,
|
|
37
38
|
indent,
|
|
38
39
|
)
|
|
40
|
+
import resource
|
|
41
|
+
|
|
39
42
|
from threading import Thread
|
|
43
|
+
from time import process_time
|
|
40
44
|
from typing import (
|
|
41
45
|
Dict,
|
|
42
46
|
List,
|
|
@@ -51,7 +55,6 @@ from setproctitle import setproctitle
|
|
|
51
55
|
|
|
52
56
|
import mutmut
|
|
53
57
|
|
|
54
|
-
mutmut.duration_by_test = {}
|
|
55
58
|
|
|
56
59
|
# Document: surviving mutants are retested when you ask mutmut to retest them, interactively in the UI or via command line
|
|
57
60
|
|
|
@@ -71,9 +74,6 @@ NEVER_MUTATE_FUNCTION_NAMES = {'__getattribute__', '__setattr__'}
|
|
|
71
74
|
NEVER_MUTATE_FUNCTION_CALLS = {'isinstance', 'len'}
|
|
72
75
|
CLASS_NAME_SEPARATOR = 'ǁ'
|
|
73
76
|
|
|
74
|
-
mutmut._stats = set()
|
|
75
|
-
mutmut.tests_by_mangled_function_name = defaultdict(set)
|
|
76
|
-
|
|
77
77
|
|
|
78
78
|
status_by_exit_code = {
|
|
79
79
|
1: 'killed',
|
|
@@ -86,6 +86,7 @@ status_by_exit_code = {
|
|
|
86
86
|
34: 'skipped',
|
|
87
87
|
35: 'suspicious',
|
|
88
88
|
36: 'timeout',
|
|
89
|
+
152: 'timeout', # SIGXCPU
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
emoji_by_status = {
|
|
@@ -189,18 +190,18 @@ def _mutmut_trampoline(orig, mutants, *args, **kwargs):
|
|
|
189
190
|
elif mutant_under_test == 'stats':
|
|
190
191
|
from mutmut.__main__ import record_trampoline_hit
|
|
191
192
|
record_trampoline_hit(orig.__module__ + '.' + orig.__name__)
|
|
192
|
-
|
|
193
|
-
return # for the yield case
|
|
193
|
+
result = orig(*args, **kwargs)
|
|
194
|
+
return result # for the yield case
|
|
194
195
|
prefix = orig.__module__ + '.' + orig.__name__ + '__mutmut_'
|
|
195
196
|
if not mutant_under_test.startswith(prefix):
|
|
196
|
-
|
|
197
|
-
return # for the yield case
|
|
197
|
+
result = orig(*args, **kwargs)
|
|
198
|
+
return result # for the yield case
|
|
198
199
|
mutant_name = mutant_under_test.rpartition('.')[-1]
|
|
199
|
-
|
|
200
|
+
result = mutants[mutant_name](*args, **kwargs)
|
|
201
|
+
return result
|
|
200
202
|
|
|
201
203
|
"""
|
|
202
|
-
yield_from_trampoline_impl = trampoline_impl.replace('
|
|
203
|
-
trampoline_impl = trampoline_impl.replace('ret ', 'return ')
|
|
204
|
+
yield_from_trampoline_impl = trampoline_impl.replace('result = ', 'result = yield from ').replace('_mutmut_trampoline', '_mutmut_yield_from_trampoline')
|
|
204
205
|
|
|
205
206
|
|
|
206
207
|
def create_mutants():
|
|
@@ -257,7 +258,8 @@ def create_mutants_for_file(filename, output_path):
|
|
|
257
258
|
exit(1)
|
|
258
259
|
|
|
259
260
|
source_file_mutation_data = SourceFileMutationData(path=filename)
|
|
260
|
-
module_name = str(filename)[:-len(filename.suffix)].replace(os.sep, '.')
|
|
261
|
+
module_name = strip_prefix(str(filename)[:-len(filename.suffix)].replace(os.sep, '.'), prefix='src.')
|
|
262
|
+
|
|
261
263
|
source_file_mutation_data.exit_code_by_key = {
|
|
262
264
|
'.'.join([module_name, x]).replace('.__init__.', '.'): None
|
|
263
265
|
for x in mutant_names
|
|
@@ -314,17 +316,18 @@ def build_trampoline(*, orig_name, mutants, class_name, is_generator):
|
|
|
314
316
|
access_suffix = '")'
|
|
315
317
|
|
|
316
318
|
if is_generator:
|
|
317
|
-
|
|
319
|
+
yield_statement = 'yield from ' # note the space at the end!
|
|
318
320
|
trampoline_name = '_mutmut_yield_from_trampoline'
|
|
319
321
|
else:
|
|
320
|
-
|
|
322
|
+
yield_statement = ''
|
|
321
323
|
trampoline_name = '_mutmut_trampoline'
|
|
322
324
|
|
|
323
325
|
return f"""
|
|
324
326
|
{mutants_dict}
|
|
325
327
|
|
|
326
328
|
def {orig_name}({'self, ' if class_name is not None else ''}*args, **kwargs):
|
|
327
|
-
{
|
|
329
|
+
result = {yield_statement}{trampoline_name}({access_prefix}{mangled_name}__mutmut_orig{access_suffix}, {access_prefix}{mangled_name}__mutmut_mutants{access_suffix}, *args, **kwargs)
|
|
330
|
+
return result
|
|
328
331
|
|
|
329
332
|
{orig_name}.__signature__ = _mutmut_signature({mangled_name}__mutmut_orig)
|
|
330
333
|
{mangled_name}__mutmut_orig.__name__ = '{mangled_name}'
|
|
@@ -655,28 +658,32 @@ def change_cwd(path):
|
|
|
655
658
|
os.chdir(old_cwd)
|
|
656
659
|
|
|
657
660
|
|
|
661
|
+
def collected_test_names():
|
|
662
|
+
return {
|
|
663
|
+
test_name
|
|
664
|
+
for _, test_names in mutmut.tests_by_mangled_function_name.items()
|
|
665
|
+
for test_name in test_names
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
|
|
658
669
|
class ListAllTestsResult:
|
|
659
670
|
def __init__(self, *, ids):
|
|
660
671
|
assert isinstance(ids, set)
|
|
661
672
|
self.ids = ids
|
|
662
673
|
|
|
663
|
-
@staticmethod
|
|
664
|
-
def old_tests():
|
|
665
|
-
return {
|
|
666
|
-
test_name
|
|
667
|
-
for _, test_names in mutmut.tests_by_mangled_function_name.items()
|
|
668
|
-
for test_name in test_names
|
|
669
|
-
}
|
|
670
|
-
|
|
671
674
|
def clear_out_obsolete_test_names(self):
|
|
675
|
+
count_before = len(mutmut.tests_by_mangled_function_name)
|
|
672
676
|
mutmut.tests_by_mangled_function_name = {
|
|
673
|
-
k: {test_name for test_name in test_names if test_name
|
|
677
|
+
k: {test_name for test_name in test_names if test_name in self.ids}
|
|
674
678
|
for k, test_names in mutmut.tests_by_mangled_function_name.items()
|
|
675
679
|
}
|
|
676
|
-
|
|
680
|
+
count_after = len(mutmut.tests_by_mangled_function_name)
|
|
681
|
+
if count_before != count_after:
|
|
682
|
+
print(f'Removed {count_before - count_after} obsolete test names')
|
|
683
|
+
save_stats()
|
|
677
684
|
|
|
678
685
|
def new_tests(self):
|
|
679
|
-
return self.ids -
|
|
686
|
+
return self.ids - collected_test_names()
|
|
680
687
|
|
|
681
688
|
|
|
682
689
|
class PytestRunner(TestRunner):
|
|
@@ -779,12 +786,13 @@ def mangled_name_from_mutant_name(mutant_name):
|
|
|
779
786
|
|
|
780
787
|
def orig_function_and_class_names_from_key(mutant_name):
|
|
781
788
|
r = mangled_name_from_mutant_name(mutant_name)
|
|
789
|
+
_, _, r = r.rpartition('.')
|
|
782
790
|
class_name = None
|
|
783
791
|
if CLASS_NAME_SEPARATOR in r:
|
|
784
792
|
class_name = r[r.index(CLASS_NAME_SEPARATOR) + 1: r.rindex(CLASS_NAME_SEPARATOR)]
|
|
785
793
|
r = r[r.rindex(CLASS_NAME_SEPARATOR) + 1:]
|
|
786
794
|
else:
|
|
787
|
-
assert r.startswith('x_')
|
|
795
|
+
assert r.startswith('x_'), r
|
|
788
796
|
r = r[2:]
|
|
789
797
|
return r, class_name
|
|
790
798
|
|
|
@@ -869,8 +877,7 @@ def run_forced_fail(runner):
|
|
|
869
877
|
with CatchOutput(show_spinner=True, spinner_title='running forced fail test') as catcher:
|
|
870
878
|
try:
|
|
871
879
|
if runner.run_forced_fail() == 0:
|
|
872
|
-
catcher.
|
|
873
|
-
print('\n'.join(catcher.strings))
|
|
880
|
+
catcher.dump_output()
|
|
874
881
|
print("FAILED")
|
|
875
882
|
os._exit(1)
|
|
876
883
|
except MutmutProgrammaticFailException:
|
|
@@ -902,6 +909,8 @@ class CatchOutput:
|
|
|
902
909
|
sys.stderr = sys.__stderr__
|
|
903
910
|
|
|
904
911
|
def start(self):
|
|
912
|
+
if self.show_spinner:
|
|
913
|
+
print_status(self.spinner_title)
|
|
905
914
|
sys.stdout = self.redirect
|
|
906
915
|
sys.stderr = self.redirect
|
|
907
916
|
|
|
@@ -979,6 +988,8 @@ def run_stats_collection(runner, tests=None):
|
|
|
979
988
|
|
|
980
989
|
os.environ['MUTANT_UNDER_TEST'] = 'stats'
|
|
981
990
|
os.environ['PY_IGNORE_IMPORTMISMATCH'] = '1'
|
|
991
|
+
start_cpu_time = process_time()
|
|
992
|
+
|
|
982
993
|
with CatchOutput(show_spinner=True, spinner_title='running stats') as output_catcher:
|
|
983
994
|
collect_stats_exit_code = runner.run_stats(tests=tests)
|
|
984
995
|
if collect_stats_exit_code != 0:
|
|
@@ -987,8 +998,10 @@ def run_stats_collection(runner, tests=None):
|
|
|
987
998
|
exit(1)
|
|
988
999
|
|
|
989
1000
|
print(' done')
|
|
1001
|
+
if not tests: # again, meaning all
|
|
1002
|
+
mutmut.stats_time = process_time() - start_cpu_time
|
|
990
1003
|
|
|
991
|
-
if not
|
|
1004
|
+
if not collected_test_names():
|
|
992
1005
|
print('failed to collect stats, no active tests found')
|
|
993
1006
|
exit(1)
|
|
994
1007
|
|
|
@@ -1028,6 +1041,7 @@ def load_stats():
|
|
|
1028
1041
|
for k, v in data.pop('tests_by_mangled_function_name').items():
|
|
1029
1042
|
mutmut.tests_by_mangled_function_name[k] |= set(v)
|
|
1030
1043
|
mutmut.duration_by_test = data.pop('duration_by_test')
|
|
1044
|
+
mutmut.stats_time = data.pop('stats_time')
|
|
1031
1045
|
assert not data, data
|
|
1032
1046
|
did_load = True
|
|
1033
1047
|
except (FileNotFoundError, JSONDecodeError):
|
|
@@ -1040,6 +1054,7 @@ def save_stats():
|
|
|
1040
1054
|
json.dump(dict(
|
|
1041
1055
|
tests_by_mangled_function_name={k: list(v) for k, v in mutmut.tests_by_mangled_function_name.items()},
|
|
1042
1056
|
duration_by_test=mutmut.duration_by_test,
|
|
1057
|
+
stats_time=mutmut.stats_time,
|
|
1043
1058
|
), f, indent=4)
|
|
1044
1059
|
|
|
1045
1060
|
|
|
@@ -1220,6 +1235,9 @@ def run(mutant_names, *, max_children):
|
|
|
1220
1235
|
if not tests:
|
|
1221
1236
|
os._exit(33)
|
|
1222
1237
|
|
|
1238
|
+
estimated_time_of_tests = sum(mutmut.duration_by_test[test_name] for test_name in tests) + 1
|
|
1239
|
+
resource.setrlimit(resource.RLIMIT_CPU, (ceil(estimated_time_of_tests * 2), ceil(estimated_time_of_tests * 2)))
|
|
1240
|
+
|
|
1223
1241
|
with CatchOutput():
|
|
1224
1242
|
result = runner.run_tests(mutant_name=mutant_name, tests=tests)
|
|
1225
1243
|
|
|
@@ -1427,7 +1445,9 @@ def browse():
|
|
|
1427
1445
|
from textual.containers import Container
|
|
1428
1446
|
from textual.widgets import Footer
|
|
1429
1447
|
from textual.widgets import DataTable
|
|
1430
|
-
from textual.widgets import
|
|
1448
|
+
from textual.widgets import Static
|
|
1449
|
+
from textual.widget import Widget
|
|
1450
|
+
from rich.syntax import Syntax
|
|
1431
1451
|
|
|
1432
1452
|
class ResultBrowser(App):
|
|
1433
1453
|
loading_id = None
|
|
@@ -1454,7 +1474,8 @@ def browse():
|
|
|
1454
1474
|
with Container(classes='container'):
|
|
1455
1475
|
yield DataTable(id='files')
|
|
1456
1476
|
yield DataTable(id='mutants')
|
|
1457
|
-
|
|
1477
|
+
with Widget(id="diff_view_widget"):
|
|
1478
|
+
yield Static(id='diff_view')
|
|
1458
1479
|
yield Footer()
|
|
1459
1480
|
|
|
1460
1481
|
def on_mount(self):
|
|
@@ -1513,9 +1534,9 @@ def browse():
|
|
|
1513
1534
|
assert event.data_table.id == 'mutants'
|
|
1514
1535
|
diff_view = self.query_one('#diff_view')
|
|
1515
1536
|
if event.row_key.value is None:
|
|
1516
|
-
diff_view.
|
|
1537
|
+
diff_view.update('')
|
|
1517
1538
|
else:
|
|
1518
|
-
diff_view.
|
|
1539
|
+
diff_view.update('<loading...>')
|
|
1519
1540
|
self.loading_id = event.row_key.value
|
|
1520
1541
|
|
|
1521
1542
|
def load_thread():
|
|
@@ -1523,9 +1544,9 @@ def browse():
|
|
|
1523
1544
|
try:
|
|
1524
1545
|
d = get_diff_for_mutant(event.row_key.value)
|
|
1525
1546
|
if event.row_key.value == self.loading_id:
|
|
1526
|
-
diff_view.
|
|
1547
|
+
diff_view.update(Syntax(d, "diff"))
|
|
1527
1548
|
except Exception as e:
|
|
1528
|
-
diff_view.
|
|
1549
|
+
diff_view.update(f"<{type(e)} {e}>")
|
|
1529
1550
|
|
|
1530
1551
|
t = Thread(target=load_thread)
|
|
1531
1552
|
t.start()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|