mutmut 3.0.5__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.0.5 → mutmut-3.2.0}/AUTHORS.rst +1 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/HISTORY.rst +26 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/PKG-INFO +1 -1
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut/__init__.py +10 -1
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut/__main__.py +118 -37
- mutmut-3.2.0/mutmut/__pycache__/__init__.cpython-311.pyc +0 -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.0.5 → mutmut-3.2.0}/mutmut/result_browser_layout.tcss +8 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/PKG-INFO +1 -1
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/SOURCES.txt +5 -1
- {mutmut-3.0.5 → mutmut-3.2.0}/LICENSE +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/MANIFEST.in +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/README.rst +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut/.DS_Store +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/dependency_links.txt +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/entry_points.txt +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/not-zip-safe +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/requires.txt +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/mutmut.egg-info/top_level.txt +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/requirements.txt +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/setup.cfg +0 -0
- {mutmut-3.0.5 → mutmut-3.2.0}/setup.py +0 -0
|
@@ -1,6 +1,32 @@
|
|
|
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
|
+
|
|
18
|
+
3.1.0
|
|
19
|
+
~~~~~
|
|
20
|
+
|
|
21
|
+
* Correctly handle mutation for generator functions (`yield`).
|
|
22
|
+
|
|
23
|
+
* Fixed so that `from __future__` lines are always first.
|
|
24
|
+
|
|
25
|
+
* If no stats are collected exit directly, as that is a breaking error for mutation testing.
|
|
26
|
+
|
|
27
|
+
* Changed name mangling to make mutants less likely to trigger name-based python magic, like in pytest where functions named `test_*` have special meaning.
|
|
28
|
+
|
|
29
|
+
|
|
4
30
|
3.0.5
|
|
5
31
|
~~~~~
|
|
6
32
|
|
|
@@ -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.0
|
|
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,14 +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
|
+
result = orig(*args, **kwargs)
|
|
194
|
+
return result # for the yield case
|
|
193
195
|
prefix = orig.__module__ + '.' + orig.__name__ + '__mutmut_'
|
|
194
196
|
if not mutant_under_test.startswith(prefix):
|
|
195
|
-
|
|
197
|
+
result = orig(*args, **kwargs)
|
|
198
|
+
return result # for the yield case
|
|
196
199
|
mutant_name = mutant_under_test.rpartition('.')[-1]
|
|
197
|
-
|
|
200
|
+
result = mutants[mutant_name](*args, **kwargs)
|
|
201
|
+
return result
|
|
198
202
|
|
|
199
203
|
"""
|
|
204
|
+
yield_from_trampoline_impl = trampoline_impl.replace('result = ', 'result = yield from ').replace('_mutmut_trampoline', '_mutmut_yield_from_trampoline')
|
|
200
205
|
|
|
201
206
|
|
|
202
207
|
def create_mutants():
|
|
@@ -253,7 +258,8 @@ def create_mutants_for_file(filename, output_path):
|
|
|
253
258
|
exit(1)
|
|
254
259
|
|
|
255
260
|
source_file_mutation_data = SourceFileMutationData(path=filename)
|
|
256
|
-
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
|
+
|
|
257
263
|
source_file_mutation_data.exit_code_by_key = {
|
|
258
264
|
'.'.join([module_name, x]).replace('.__init__.', '.'): None
|
|
259
265
|
for x in mutant_names
|
|
@@ -297,7 +303,7 @@ def write_all_mutants_to_file(*, out, source, filename):
|
|
|
297
303
|
return mutant_names, hash_by_function_name
|
|
298
304
|
|
|
299
305
|
|
|
300
|
-
def build_trampoline(orig_name, mutants, class_name
|
|
306
|
+
def build_trampoline(*, orig_name, mutants, class_name, is_generator):
|
|
301
307
|
assert orig_name not in NEVER_MUTATE_FUNCTION_NAMES
|
|
302
308
|
|
|
303
309
|
mangled_name = mangle_function_name(name=orig_name, class_name=class_name)
|
|
@@ -309,11 +315,19 @@ def build_trampoline(orig_name, mutants, class_name=None):
|
|
|
309
315
|
access_prefix = f'object.__getattribute__(self, "'
|
|
310
316
|
access_suffix = '")'
|
|
311
317
|
|
|
318
|
+
if is_generator:
|
|
319
|
+
yield_statement = 'yield from ' # note the space at the end!
|
|
320
|
+
trampoline_name = '_mutmut_yield_from_trampoline'
|
|
321
|
+
else:
|
|
322
|
+
yield_statement = ''
|
|
323
|
+
trampoline_name = '_mutmut_trampoline'
|
|
324
|
+
|
|
312
325
|
return f"""
|
|
313
326
|
{mutants_dict}
|
|
314
327
|
|
|
315
328
|
def {orig_name}({'self, ' if class_name is not None else ''}*args, **kwargs):
|
|
316
|
-
|
|
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
|
|
317
331
|
|
|
318
332
|
{orig_name}.__signature__ = _mutmut_signature({mangled_name}__mutmut_orig)
|
|
319
333
|
{mangled_name}__mutmut_orig.__name__ = '{mangled_name}'
|
|
@@ -447,6 +461,23 @@ class FuncContext:
|
|
|
447
461
|
return False
|
|
448
462
|
|
|
449
463
|
|
|
464
|
+
def is_generator(node):
|
|
465
|
+
assert node.type == 'funcdef'
|
|
466
|
+
|
|
467
|
+
def _is_generator(n):
|
|
468
|
+
if n is not node and n.type in ('funcdef', 'classdef'):
|
|
469
|
+
return False
|
|
470
|
+
|
|
471
|
+
if n.type == 'keyword' and n.value == 'yield':
|
|
472
|
+
return True
|
|
473
|
+
|
|
474
|
+
for c in getattr(n, 'children', []):
|
|
475
|
+
if _is_generator(c):
|
|
476
|
+
return True
|
|
477
|
+
return False
|
|
478
|
+
return _is_generator(node)
|
|
479
|
+
|
|
480
|
+
|
|
450
481
|
def yield_mutants_for_function(node, *, class_name=None, no_mutate_lines):
|
|
451
482
|
assert node.type == 'funcdef'
|
|
452
483
|
|
|
@@ -481,7 +512,7 @@ def yield_mutants_for_function(node, *, class_name=None, no_mutate_lines):
|
|
|
481
512
|
finally:
|
|
482
513
|
context.stack.pop()
|
|
483
514
|
|
|
484
|
-
trampoline = build_trampoline(node.name.value, context.mutants, class_name=class_name)
|
|
515
|
+
trampoline = build_trampoline(orig_name=node.name.value, mutants=context.mutants, class_name=class_name, is_generator=is_generator(node))
|
|
485
516
|
if class_name is not None:
|
|
486
517
|
trampoline = indent(trampoline, ' ')
|
|
487
518
|
yield 'trampoline', trampoline, None, None
|
|
@@ -508,15 +539,38 @@ def yield_mutants_for_class_body(node, no_mutate_lines):
|
|
|
508
539
|
yield 'filler', child_node.get_code(), None, None
|
|
509
540
|
|
|
510
541
|
|
|
542
|
+
def is_from_future_import_node(c):
|
|
543
|
+
if c.type == 'simple_stmt':
|
|
544
|
+
if c.children:
|
|
545
|
+
c2 = c.children[0]
|
|
546
|
+
if c2.type == 'import_from' and c2.children[1].type == 'name' and c2.children[1].value == '__future__':
|
|
547
|
+
return True
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def yield_future_imports(node):
|
|
552
|
+
for c in node.children:
|
|
553
|
+
if is_from_future_import_node(c):
|
|
554
|
+
yield 'filler', c.get_code(), None, None
|
|
555
|
+
|
|
556
|
+
|
|
511
557
|
def yield_mutants_for_module(node, no_mutate_lines):
|
|
558
|
+
assert node.type == 'file_input'
|
|
559
|
+
|
|
560
|
+
# First yield `from __future__`, then the rest
|
|
561
|
+
yield from yield_future_imports(node)
|
|
562
|
+
|
|
512
563
|
yield 'trampoline_impl', trampoline_impl, None, None
|
|
564
|
+
yield 'trampoline_impl', yield_from_trampoline_impl, None, None
|
|
513
565
|
yield 'filler', '\n', None, None
|
|
514
|
-
assert node.type == 'file_input'
|
|
515
566
|
for child_node in node.children:
|
|
516
567
|
if child_node.type == 'funcdef':
|
|
517
568
|
yield from yield_mutants_for_function(child_node, no_mutate_lines=no_mutate_lines)
|
|
518
569
|
elif child_node.type == 'classdef':
|
|
519
570
|
yield from yield_mutants_for_class(child_node, no_mutate_lines=no_mutate_lines)
|
|
571
|
+
elif is_from_future_import_node(child_node):
|
|
572
|
+
# Don't yield `from __future__` after trampoline
|
|
573
|
+
pass
|
|
520
574
|
else:
|
|
521
575
|
yield 'filler', child_node.get_code(), None, None
|
|
522
576
|
|
|
@@ -604,28 +658,32 @@ def change_cwd(path):
|
|
|
604
658
|
os.chdir(old_cwd)
|
|
605
659
|
|
|
606
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
|
+
|
|
607
669
|
class ListAllTestsResult:
|
|
608
670
|
def __init__(self, *, ids):
|
|
609
671
|
assert isinstance(ids, set)
|
|
610
672
|
self.ids = ids
|
|
611
673
|
|
|
612
|
-
@staticmethod
|
|
613
|
-
def old_tests():
|
|
614
|
-
return {
|
|
615
|
-
test_name
|
|
616
|
-
for _, test_names in mutmut.tests_by_mangled_function_name.items()
|
|
617
|
-
for test_name in test_names
|
|
618
|
-
}
|
|
619
|
-
|
|
620
674
|
def clear_out_obsolete_test_names(self):
|
|
675
|
+
count_before = len(mutmut.tests_by_mangled_function_name)
|
|
621
676
|
mutmut.tests_by_mangled_function_name = {
|
|
622
|
-
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}
|
|
623
678
|
for k, test_names in mutmut.tests_by_mangled_function_name.items()
|
|
624
679
|
}
|
|
625
|
-
|
|
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()
|
|
626
684
|
|
|
627
685
|
def new_tests(self):
|
|
628
|
-
return self.ids -
|
|
686
|
+
return self.ids - collected_test_names()
|
|
629
687
|
|
|
630
688
|
|
|
631
689
|
class PytestRunner(TestRunner):
|
|
@@ -636,10 +694,7 @@ class PytestRunner(TestRunner):
|
|
|
636
694
|
raise BadTestExecutionCommandsException(params)
|
|
637
695
|
return exit_code
|
|
638
696
|
|
|
639
|
-
|
|
640
697
|
def run_stats(self, *, tests):
|
|
641
|
-
import pytest
|
|
642
|
-
|
|
643
698
|
class StatsCollector:
|
|
644
699
|
def pytest_runtest_teardown(self, item, nextitem):
|
|
645
700
|
unused(nextitem)
|
|
@@ -719,6 +774,8 @@ def mangle_function_name(*, name, class_name):
|
|
|
719
774
|
if class_name:
|
|
720
775
|
assert CLASS_NAME_SEPARATOR not in class_name
|
|
721
776
|
prefix = f'x{CLASS_NAME_SEPARATOR}{class_name}{CLASS_NAME_SEPARATOR}'
|
|
777
|
+
else:
|
|
778
|
+
prefix = 'x_'
|
|
722
779
|
return f'{prefix}{name}'
|
|
723
780
|
|
|
724
781
|
|
|
@@ -729,10 +786,14 @@ def mangled_name_from_mutant_name(mutant_name):
|
|
|
729
786
|
|
|
730
787
|
def orig_function_and_class_names_from_key(mutant_name):
|
|
731
788
|
r = mangled_name_from_mutant_name(mutant_name)
|
|
789
|
+
_, _, r = r.rpartition('.')
|
|
732
790
|
class_name = None
|
|
733
791
|
if CLASS_NAME_SEPARATOR in r:
|
|
734
792
|
class_name = r[r.index(CLASS_NAME_SEPARATOR) + 1: r.rindex(CLASS_NAME_SEPARATOR)]
|
|
735
793
|
r = r[r.rindex(CLASS_NAME_SEPARATOR) + 1:]
|
|
794
|
+
else:
|
|
795
|
+
assert r.startswith('x_'), r
|
|
796
|
+
r = r[2:]
|
|
736
797
|
return r, class_name
|
|
737
798
|
|
|
738
799
|
|
|
@@ -816,8 +877,7 @@ def run_forced_fail(runner):
|
|
|
816
877
|
with CatchOutput(show_spinner=True, spinner_title='running forced fail test') as catcher:
|
|
817
878
|
try:
|
|
818
879
|
if runner.run_forced_fail() == 0:
|
|
819
|
-
catcher.
|
|
820
|
-
print('\n'.join(catcher.strings))
|
|
880
|
+
catcher.dump_output()
|
|
821
881
|
print("FAILED")
|
|
822
882
|
os._exit(1)
|
|
823
883
|
except MutmutProgrammaticFailException:
|
|
@@ -849,6 +909,8 @@ class CatchOutput:
|
|
|
849
909
|
sys.stderr = sys.__stderr__
|
|
850
910
|
|
|
851
911
|
def start(self):
|
|
912
|
+
if self.show_spinner:
|
|
913
|
+
print_status(self.spinner_title)
|
|
852
914
|
sys.stdout = self.redirect
|
|
853
915
|
sys.stderr = self.redirect
|
|
854
916
|
|
|
@@ -926,6 +988,8 @@ def run_stats_collection(runner, tests=None):
|
|
|
926
988
|
|
|
927
989
|
os.environ['MUTANT_UNDER_TEST'] = 'stats'
|
|
928
990
|
os.environ['PY_IGNORE_IMPORTMISMATCH'] = '1'
|
|
991
|
+
start_cpu_time = process_time()
|
|
992
|
+
|
|
929
993
|
with CatchOutput(show_spinner=True, spinner_title='running stats') as output_catcher:
|
|
930
994
|
collect_stats_exit_code = runner.run_stats(tests=tests)
|
|
931
995
|
if collect_stats_exit_code != 0:
|
|
@@ -934,10 +998,12 @@ def run_stats_collection(runner, tests=None):
|
|
|
934
998
|
exit(1)
|
|
935
999
|
|
|
936
1000
|
print(' done')
|
|
1001
|
+
if not tests: # again, meaning all
|
|
1002
|
+
mutmut.stats_time = process_time() - start_cpu_time
|
|
937
1003
|
|
|
938
|
-
if not
|
|
1004
|
+
if not collected_test_names():
|
|
939
1005
|
print('failed to collect stats, no active tests found')
|
|
940
|
-
|
|
1006
|
+
exit(1)
|
|
941
1007
|
|
|
942
1008
|
save_stats()
|
|
943
1009
|
|
|
@@ -975,6 +1041,7 @@ def load_stats():
|
|
|
975
1041
|
for k, v in data.pop('tests_by_mangled_function_name').items():
|
|
976
1042
|
mutmut.tests_by_mangled_function_name[k] |= set(v)
|
|
977
1043
|
mutmut.duration_by_test = data.pop('duration_by_test')
|
|
1044
|
+
mutmut.stats_time = data.pop('stats_time')
|
|
978
1045
|
assert not data, data
|
|
979
1046
|
did_load = True
|
|
980
1047
|
except (FileNotFoundError, JSONDecodeError):
|
|
@@ -987,6 +1054,7 @@ def save_stats():
|
|
|
987
1054
|
json.dump(dict(
|
|
988
1055
|
tests_by_mangled_function_name={k: list(v) for k, v in mutmut.tests_by_mangled_function_name.items()},
|
|
989
1056
|
duration_by_test=mutmut.duration_by_test,
|
|
1057
|
+
stats_time=mutmut.stats_time,
|
|
990
1058
|
), f, indent=4)
|
|
991
1059
|
|
|
992
1060
|
|
|
@@ -1083,7 +1151,14 @@ def run(mutant_names, *, max_children):
|
|
|
1083
1151
|
time = datetime.now() - start
|
|
1084
1152
|
print(f' done in {round(time.total_seconds()*1000)}ms', )
|
|
1085
1153
|
|
|
1086
|
-
|
|
1154
|
+
src_path = (Path('mutants') / 'src')
|
|
1155
|
+
source_path = (Path('mutants') / 'source')
|
|
1156
|
+
if src_path.exists():
|
|
1157
|
+
sys.path.insert(0, str(src_path.absolute()))
|
|
1158
|
+
elif source_path.exists():
|
|
1159
|
+
sys.path.insert(0, str(source_path.absolute))
|
|
1160
|
+
else:
|
|
1161
|
+
sys.path.insert(0, os.path.abspath('mutants'))
|
|
1087
1162
|
|
|
1088
1163
|
# TODO: config/option for runner
|
|
1089
1164
|
# runner = HammettRunner()
|
|
@@ -1160,6 +1235,9 @@ def run(mutant_names, *, max_children):
|
|
|
1160
1235
|
if not tests:
|
|
1161
1236
|
os._exit(33)
|
|
1162
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
|
+
|
|
1163
1241
|
with CatchOutput():
|
|
1164
1242
|
result = runner.run_tests(mutant_name=mutant_name, tests=tests)
|
|
1165
1243
|
|
|
@@ -1367,7 +1445,9 @@ def browse():
|
|
|
1367
1445
|
from textual.containers import Container
|
|
1368
1446
|
from textual.widgets import Footer
|
|
1369
1447
|
from textual.widgets import DataTable
|
|
1370
|
-
from textual.widgets import
|
|
1448
|
+
from textual.widgets import Static
|
|
1449
|
+
from textual.widget import Widget
|
|
1450
|
+
from rich.syntax import Syntax
|
|
1371
1451
|
|
|
1372
1452
|
class ResultBrowser(App):
|
|
1373
1453
|
loading_id = None
|
|
@@ -1394,7 +1474,8 @@ def browse():
|
|
|
1394
1474
|
with Container(classes='container'):
|
|
1395
1475
|
yield DataTable(id='files')
|
|
1396
1476
|
yield DataTable(id='mutants')
|
|
1397
|
-
|
|
1477
|
+
with Widget(id="diff_view_widget"):
|
|
1478
|
+
yield Static(id='diff_view')
|
|
1398
1479
|
yield Footer()
|
|
1399
1480
|
|
|
1400
1481
|
def on_mount(self):
|
|
@@ -1453,9 +1534,9 @@ def browse():
|
|
|
1453
1534
|
assert event.data_table.id == 'mutants'
|
|
1454
1535
|
diff_view = self.query_one('#diff_view')
|
|
1455
1536
|
if event.row_key.value is None:
|
|
1456
|
-
diff_view.
|
|
1537
|
+
diff_view.update('')
|
|
1457
1538
|
else:
|
|
1458
|
-
diff_view.
|
|
1539
|
+
diff_view.update('<loading...>')
|
|
1459
1540
|
self.loading_id = event.row_key.value
|
|
1460
1541
|
|
|
1461
1542
|
def load_thread():
|
|
@@ -1463,9 +1544,9 @@ def browse():
|
|
|
1463
1544
|
try:
|
|
1464
1545
|
d = get_diff_for_mutant(event.row_key.value)
|
|
1465
1546
|
if event.row_key.value == self.loading_id:
|
|
1466
|
-
diff_view.
|
|
1547
|
+
diff_view.update(Syntax(d, "diff"))
|
|
1467
1548
|
except Exception as e:
|
|
1468
|
-
diff_view.
|
|
1549
|
+
diff_view.update(f"<{type(e)} {e}>")
|
|
1469
1550
|
|
|
1470
1551
|
t = Thread(target=load_thread)
|
|
1471
1552
|
t.start()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -19,4 +19,8 @@ mutmut.egg-info/dependency_links.txt
|
|
|
19
19
|
mutmut.egg-info/entry_points.txt
|
|
20
20
|
mutmut.egg-info/not-zip-safe
|
|
21
21
|
mutmut.egg-info/requires.txt
|
|
22
|
-
mutmut.egg-info/top_level.txt
|
|
22
|
+
mutmut.egg-info/top_level.txt
|
|
23
|
+
mutmut/__pycache__/__init__.cpython-311.pyc
|
|
24
|
+
mutmut/__pycache__/__init__.cpython-312.pyc
|
|
25
|
+
mutmut/__pycache__/__main__.cpython-311.pyc
|
|
26
|
+
mutmut/__pycache__/__main__.cpython-312.pyc
|
|
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
|