sflkit 0.5.5__tar.gz → 0.5.7__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.
- {sflkit-0.5.5/src/sflkit.egg-info → sflkit-0.5.7}/PKG-INFO +1 -1
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/__init__.py +1 -2
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/runners/run.py +67 -30
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/weights/__init__.py +2 -2
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/weights/analyzer.py +5 -3
- {sflkit-0.5.5 → sflkit-0.5.7/src/sflkit.egg-info}/PKG-INFO +1 -1
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_dependency_weights.py +8 -8
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_runner.py +69 -1
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_runner_parallel.py +3 -1
- {sflkit-0.5.5 → sflkit-0.5.7}/LICENSE +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/README.md +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/pyproject.toml +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/setup.cfg +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/analysis_type.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/analyzer.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/factory.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/mapping.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/predicate.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/similarity.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/spectra.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/analysis/suggestion.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/cli.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/color.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/config.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/evaluation.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/events/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/events/event_file.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/events/mapping.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/features/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/features/handler.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/features/value.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/features/vector.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/instrumentation/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/instrumentation/dir_instrumentation.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/instrumentation/file_instrumentation.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/extract.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/finder.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/java/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/java/extract.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/java/factory.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/java/finder.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/java/visitor.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/language.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/meta.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/python/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/python/extract.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/python/factory.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/python/finder.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/python/visitor.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/language/visitor.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/logger.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/model/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/model/model.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/model/parallel.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/model/scope.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/runners/__init__.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit/weights/models.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit.egg-info/SOURCES.txt +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit.egg-info/dependency_links.txt +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit.egg-info/entry_points.txt +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit.egg-info/requires.txt +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/src/sflkit.egg-info/top_level.txt +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_analysis_objects.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_analyzer.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_cli.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_color.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_config.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_coverage.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_evaluation.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_events.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_execution.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_expression.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_features.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_instrumentation.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_language.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_parallelization.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_predicates.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_runner_pytest_versions.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_scope.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_spectra.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_suggestions.py +0 -0
- {sflkit-0.5.5 → sflkit-0.5.7}/tests/test_visitors.py +0 -0
|
@@ -5,8 +5,7 @@ from sflkit.analysis.analyzer import Analyzer
|
|
|
5
5
|
from sflkit.config import Config, parse_config
|
|
6
6
|
from sflkit.instrumentation.dir_instrumentation import DirInstrumentation
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
__version__ = "0.5.5"
|
|
8
|
+
__version__ = "0.5.7"
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def instrument_config(conf: Config):
|
|
@@ -25,6 +25,8 @@ PYTEST_RESULT_PATTERN = re.compile(
|
|
|
25
25
|
|
|
26
26
|
DEFAULT_TIMEOUT = 10
|
|
27
27
|
|
|
28
|
+
DEFAULT_MAX_WORKERS = 64
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
class TestResult(enum.Enum):
|
|
30
32
|
PASSING = "PASSING"
|
|
@@ -57,7 +59,7 @@ class Runner(abc.ABC):
|
|
|
57
59
|
self.thread_support = thread_support
|
|
58
60
|
|
|
59
61
|
@staticmethod
|
|
60
|
-
def use_parallel() -> "Runner":
|
|
62
|
+
def use_parallel() -> type["Runner"]:
|
|
61
63
|
raise NotImplementedError()
|
|
62
64
|
|
|
63
65
|
def get_tests(
|
|
@@ -66,24 +68,26 @@ class Runner(abc.ABC):
|
|
|
66
68
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
67
69
|
base: Optional[os.PathLike] = None,
|
|
68
70
|
environ: Environment = None,
|
|
71
|
+
python="python3",
|
|
69
72
|
k: str = None,
|
|
70
73
|
) -> List[str]:
|
|
71
74
|
return []
|
|
72
75
|
|
|
73
76
|
def run_test(
|
|
74
|
-
self, directory: Path, test: str, environ: Environment = None
|
|
77
|
+
self, directory: Path, test: str, environ: Environment = None, python="python3"
|
|
75
78
|
) -> TestResult:
|
|
76
79
|
return TestResult.UNDEFINED
|
|
77
80
|
|
|
78
81
|
def filter_tests(self, tests: List[str]) -> List[str]:
|
|
82
|
+
# noinspection PyTypeChecker
|
|
79
83
|
return list(filter(self.re_filter.search, tests))
|
|
80
84
|
|
|
81
85
|
@staticmethod
|
|
82
86
|
def safe(s: str):
|
|
83
|
-
s = s.encode("ascii", "ignore")
|
|
87
|
+
s: bytes = s.encode("ascii", "ignore")
|
|
84
88
|
if len(s) > 255:
|
|
85
89
|
return hashlib.md5(s).hexdigest()
|
|
86
|
-
s = s.decode("ascii")
|
|
90
|
+
s: str = s.decode("ascii")
|
|
87
91
|
for c in string.punctuation:
|
|
88
92
|
if c in s:
|
|
89
93
|
s = s.replace(c, "_")
|
|
@@ -95,12 +99,13 @@ class Runner(abc.ABC):
|
|
|
95
99
|
output: Path,
|
|
96
100
|
tests: List[str],
|
|
97
101
|
environ: Environment = None,
|
|
102
|
+
python="python3",
|
|
98
103
|
):
|
|
99
104
|
output.mkdir(parents=True, exist_ok=True)
|
|
100
105
|
for test_result in TestResult:
|
|
101
106
|
(output / test_result.get_dir()).mkdir(parents=True, exist_ok=True)
|
|
102
107
|
for event_file, test in enumerate(tests):
|
|
103
|
-
test_result = self.run_test(directory, test, environ=environ)
|
|
108
|
+
test_result = self.run_test(directory, test, environ=environ, python=python)
|
|
104
109
|
self.tests[test_result].add(test)
|
|
105
110
|
if os.path.exists(directory / "EVENTS_PATH"):
|
|
106
111
|
shutil.move(
|
|
@@ -119,6 +124,7 @@ class Runner(abc.ABC):
|
|
|
119
124
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
120
125
|
base: Optional[os.PathLike] = None,
|
|
121
126
|
environ: Environment = None,
|
|
127
|
+
python="python3",
|
|
122
128
|
k: str = None,
|
|
123
129
|
):
|
|
124
130
|
self.passing_tests.clear()
|
|
@@ -131,15 +137,23 @@ class Runner(abc.ABC):
|
|
|
131
137
|
directory,
|
|
132
138
|
output,
|
|
133
139
|
self.filter_tests(
|
|
134
|
-
self.get_tests(
|
|
140
|
+
self.get_tests(
|
|
141
|
+
directory,
|
|
142
|
+
files=files,
|
|
143
|
+
base=base,
|
|
144
|
+
environ=environ,
|
|
145
|
+
python=python,
|
|
146
|
+
k=k,
|
|
147
|
+
)
|
|
135
148
|
),
|
|
136
149
|
environ=environ,
|
|
150
|
+
python=python,
|
|
137
151
|
)
|
|
138
152
|
|
|
139
153
|
|
|
140
154
|
class VoidRunner(Runner):
|
|
141
155
|
@staticmethod
|
|
142
|
-
def use_parallel() -> Runner:
|
|
156
|
+
def use_parallel() -> type[Runner]:
|
|
143
157
|
return VoidRunner
|
|
144
158
|
|
|
145
159
|
|
|
@@ -290,11 +304,11 @@ class PytestRunner(Runner):
|
|
|
290
304
|
self.set_python_path = set_python_path
|
|
291
305
|
|
|
292
306
|
@staticmethod
|
|
293
|
-
def use_parallel() -> Runner:
|
|
307
|
+
def use_parallel() -> type[Runner]:
|
|
294
308
|
return ParallelPytestRunner
|
|
295
309
|
|
|
296
310
|
@staticmethod
|
|
297
|
-
def common_base(directory: Path, tests: List[str]) -> Path:
|
|
311
|
+
def common_base(directory: Path, tests: List[str]) -> Path | None:
|
|
298
312
|
parts = directory.parts
|
|
299
313
|
common_bases = {Path(*parts[:i]) for i in range(1, len(parts) + 1)}
|
|
300
314
|
leaves_paths = {Path(r.split("::", 1)[0] if "::" in r else r) for r in tests}
|
|
@@ -323,7 +337,7 @@ class PytestRunner(Runner):
|
|
|
323
337
|
return None
|
|
324
338
|
|
|
325
339
|
@staticmethod
|
|
326
|
-
def get_files(files: List[os.PathLike]) -> Set[str]:
|
|
340
|
+
def get_files(files: List[str | os.PathLike[str]]) -> Set[str]:
|
|
327
341
|
paths = set()
|
|
328
342
|
for f in files:
|
|
329
343
|
f = str(f)
|
|
@@ -393,6 +407,7 @@ class PytestRunner(Runner):
|
|
|
393
407
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
394
408
|
base: Optional[os.PathLike] = None,
|
|
395
409
|
environ: Environment = None,
|
|
410
|
+
python="python3",
|
|
396
411
|
k: str = None,
|
|
397
412
|
) -> List[str]:
|
|
398
413
|
c = []
|
|
@@ -416,7 +431,7 @@ class PytestRunner(Runner):
|
|
|
416
431
|
c += str_files
|
|
417
432
|
process = subprocess.run(
|
|
418
433
|
[
|
|
419
|
-
|
|
434
|
+
python,
|
|
420
435
|
"-m",
|
|
421
436
|
"pytest",
|
|
422
437
|
"--collect-only",
|
|
@@ -451,11 +466,11 @@ class PytestRunner(Runner):
|
|
|
451
466
|
return False, None, None
|
|
452
467
|
|
|
453
468
|
def run_test(
|
|
454
|
-
self, directory: Path, test: str, environ: Environment = None
|
|
469
|
+
self, directory: Path, test: str, environ: Environment = None, python="python3"
|
|
455
470
|
) -> TestResult:
|
|
456
471
|
try:
|
|
457
472
|
output = subprocess.run(
|
|
458
|
-
[
|
|
473
|
+
[python, "-m", "pytest", test],
|
|
459
474
|
stdout=subprocess.PIPE,
|
|
460
475
|
env=environ,
|
|
461
476
|
cwd=directory,
|
|
@@ -464,7 +479,7 @@ class PytestRunner(Runner):
|
|
|
464
479
|
except subprocess.TimeoutExpired:
|
|
465
480
|
return TestResult.UNDEFINED
|
|
466
481
|
successful, passing, failing = self.__get_pytest_result__(output)
|
|
467
|
-
if successful:
|
|
482
|
+
if successful and passing is not None and failing is not None:
|
|
468
483
|
if passing > 0 and failing == 0:
|
|
469
484
|
return TestResult.PASSING
|
|
470
485
|
elif failing > 0 and passing == 0:
|
|
@@ -496,7 +511,7 @@ class InputRunner(Runner):
|
|
|
496
511
|
self.failing: Dict[str, List[str]] = self._prepare_tests(failing, "failing")
|
|
497
512
|
|
|
498
513
|
@staticmethod
|
|
499
|
-
def use_parallel() -> Runner:
|
|
514
|
+
def use_parallel() -> type[Runner]:
|
|
500
515
|
return ParallelInputRunner
|
|
501
516
|
|
|
502
517
|
@staticmethod
|
|
@@ -529,22 +544,27 @@ class InputRunner(Runner):
|
|
|
529
544
|
files: Optional[List[os.PathLike] | os.PathLike] = None,
|
|
530
545
|
base: Optional[os.PathLike] = None,
|
|
531
546
|
environ: Environment = None,
|
|
547
|
+
python="python3",
|
|
532
548
|
k: str = None,
|
|
533
549
|
) -> List[str]:
|
|
534
550
|
return list(self.passing.keys()) + list(self.failing.keys())
|
|
535
551
|
|
|
536
552
|
def run_test(
|
|
537
|
-
self,
|
|
553
|
+
self,
|
|
554
|
+
directory: Path | None,
|
|
555
|
+
test: str,
|
|
556
|
+
environ: Environment = None,
|
|
557
|
+
python="python3",
|
|
538
558
|
) -> TestResult:
|
|
539
|
-
if "passing" in
|
|
540
|
-
|
|
559
|
+
if "passing" in test:
|
|
560
|
+
test_args = self.passing[test]
|
|
541
561
|
result = TestResult.PASSING
|
|
542
562
|
else:
|
|
543
|
-
|
|
563
|
+
test_args = self.failing[test]
|
|
544
564
|
result = TestResult.FAILING
|
|
545
565
|
try:
|
|
546
566
|
subprocess.run(
|
|
547
|
-
[
|
|
567
|
+
[python, self.access] + test_args,
|
|
548
568
|
stdout=subprocess.PIPE,
|
|
549
569
|
stderr=subprocess.PIPE,
|
|
550
570
|
env=environ,
|
|
@@ -568,21 +588,27 @@ class ParallelPytestRunner(PytestRunner):
|
|
|
568
588
|
super().__init__(
|
|
569
589
|
re_filter, timeout, set_python_path, thread_support=thread_support
|
|
570
590
|
)
|
|
571
|
-
self.workers = max(min(workers, os.cpu_count() or
|
|
591
|
+
self.workers = max(min(workers, os.cpu_count() or DEFAULT_MAX_WORKERS), 1)
|
|
572
592
|
self.is_parallel = True
|
|
573
593
|
self.environ = None
|
|
594
|
+
self.python = None
|
|
574
595
|
self.directory = None
|
|
575
596
|
self.output = None
|
|
576
597
|
|
|
577
598
|
@staticmethod
|
|
578
|
-
def use_parallel() -> Runner:
|
|
599
|
+
def use_parallel() -> type[Runner]:
|
|
579
600
|
return ParallelPytestRunner
|
|
580
601
|
|
|
581
|
-
def process_test(
|
|
602
|
+
def process_test(
|
|
603
|
+
self,
|
|
604
|
+
test: str,
|
|
605
|
+
):
|
|
582
606
|
local_environ = self.environ.copy()
|
|
583
607
|
events_path_name = f"EVENTS_PATH_{os.getpid()}"
|
|
584
608
|
local_environ["EVENTS_PATH"] = events_path_name
|
|
585
|
-
tr = self.run_test(
|
|
609
|
+
tr = self.run_test(
|
|
610
|
+
self.directory, test, environ=local_environ, python=self.python
|
|
611
|
+
)
|
|
586
612
|
self.tests[tr].add(test)
|
|
587
613
|
if os.path.exists(self.directory / events_path_name):
|
|
588
614
|
shutil.move(
|
|
@@ -598,6 +624,7 @@ class ParallelPytestRunner(PytestRunner):
|
|
|
598
624
|
output: Path,
|
|
599
625
|
tests: List[str],
|
|
600
626
|
environ: Environment = None,
|
|
627
|
+
python="python3",
|
|
601
628
|
):
|
|
602
629
|
output.mkdir(parents=True, exist_ok=True)
|
|
603
630
|
for test_result in TestResult:
|
|
@@ -605,7 +632,9 @@ class ParallelPytestRunner(PytestRunner):
|
|
|
605
632
|
|
|
606
633
|
self.directory = directory
|
|
607
634
|
self.output = output
|
|
635
|
+
# noinspection PyUnresolvedReferences
|
|
608
636
|
self.environ = environ or os.environ.copy()
|
|
637
|
+
self.python = python
|
|
609
638
|
|
|
610
639
|
with ProcessPoolExecutor(max_workers=self.workers) as executor:
|
|
611
640
|
# Consume the iterator to ensure all tasks complete
|
|
@@ -625,23 +654,28 @@ class ParallelInputRunner(InputRunner):
|
|
|
625
654
|
thread_support: bool = False,
|
|
626
655
|
):
|
|
627
656
|
super().__init__(access, passing, failing, thread_support=thread_support)
|
|
628
|
-
self.workers = max(min(workers, os.cpu_count() or
|
|
657
|
+
self.workers = max(min(workers, os.cpu_count() or DEFAULT_MAX_WORKERS), 1)
|
|
629
658
|
self.is_parallel = True
|
|
630
659
|
self.environ = None
|
|
631
|
-
self.
|
|
632
|
-
self.
|
|
660
|
+
self.python = None
|
|
661
|
+
self.directory: Path | None = None
|
|
662
|
+
self.output: Path | None = None
|
|
633
663
|
|
|
634
664
|
@staticmethod
|
|
635
|
-
def use_parallel() -> Runner:
|
|
665
|
+
def use_parallel() -> type[Runner]:
|
|
636
666
|
return ParallelInputRunner
|
|
637
667
|
|
|
638
668
|
def process_test(self, test_name: str):
|
|
639
669
|
local_environ = self.environ.copy()
|
|
640
670
|
events_path_name = f"EVENTS_PATH_{os.getpid()}"
|
|
641
671
|
local_environ["EVENTS_PATH"] = events_path_name
|
|
642
|
-
tr = self.run_test(
|
|
672
|
+
tr = self.run_test(
|
|
673
|
+
self.directory, test_name, environ=local_environ, python=self.python
|
|
674
|
+
)
|
|
643
675
|
self.tests[tr].add(test_name)
|
|
676
|
+
# noinspection PyUnresolvedReferences
|
|
644
677
|
if os.path.exists(self.directory / events_path_name):
|
|
678
|
+
# noinspection PyUnresolvedReferences
|
|
645
679
|
shutil.move(
|
|
646
680
|
self.directory / events_path_name,
|
|
647
681
|
self.output / tr.get_dir() / self.safe(test_name),
|
|
@@ -655,6 +689,7 @@ class ParallelInputRunner(InputRunner):
|
|
|
655
689
|
output: Path,
|
|
656
690
|
tests: List[str],
|
|
657
691
|
environ: Environment = None,
|
|
692
|
+
python="python3",
|
|
658
693
|
):
|
|
659
694
|
output.mkdir(parents=True, exist_ok=True)
|
|
660
695
|
for test_result in TestResult:
|
|
@@ -662,11 +697,13 @@ class ParallelInputRunner(InputRunner):
|
|
|
662
697
|
|
|
663
698
|
self.directory = directory
|
|
664
699
|
self.output = output
|
|
700
|
+
# noinspection PyUnresolvedReferences
|
|
665
701
|
self.environ = environ or os.environ.copy()
|
|
702
|
+
self.python = python
|
|
666
703
|
|
|
667
704
|
with ProcessPoolExecutor(max_workers=self.workers) as executor:
|
|
668
705
|
# Consume the iterator to ensure all tasks complete
|
|
669
|
-
executor.map(self.process_test, tests)
|
|
706
|
+
list(executor.map(self.process_test, tests))
|
|
670
707
|
|
|
671
708
|
# Ensure all files are flushed to disk (helps with race conditions in CI)
|
|
672
709
|
os.sync()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from sflkit.weights.analyzer import
|
|
1
|
+
from sflkit.weights.analyzer import ProximityAnalyzer
|
|
2
2
|
from sflkit.weights.models import (
|
|
3
3
|
TestTimeModel,
|
|
4
4
|
TestFunctionModel,
|
|
@@ -10,7 +10,7 @@ from sflkit.weights.models import (
|
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
__all__ = [
|
|
13
|
-
"
|
|
13
|
+
"ProximityAnalyzer",
|
|
14
14
|
"TestTimeModel",
|
|
15
15
|
"TestFunctionModel",
|
|
16
16
|
"TestLineModel",
|
|
@@ -12,7 +12,7 @@ from sflkit.events.event_file import EventFile
|
|
|
12
12
|
from sflkit.model.model import MetaModel
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class
|
|
15
|
+
class ProximityAnalyzer(Analyzer):
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
model_class: Type[TestTimeModel],
|
|
@@ -51,10 +51,12 @@ class TimeAnalyzer(Analyzer):
|
|
|
51
51
|
|
|
52
52
|
@staticmethod
|
|
53
53
|
def load_with_dependencies(path: os.PathLike, model_class: Type[TestTimeModel]):
|
|
54
|
-
return
|
|
54
|
+
return ProximityAnalyzer(
|
|
55
|
+
model_class, meta_model=MetaModel(load_analysis_json(path))
|
|
56
|
+
)
|
|
55
57
|
|
|
56
58
|
@staticmethod
|
|
57
59
|
def loads_with_dependencies(data: str, model_class: Type[TestTimeModel]):
|
|
58
|
-
return
|
|
60
|
+
return ProximityAnalyzer(
|
|
59
61
|
model_class, meta_model=MetaModel(set(map(deserialize, json.loads(data))))
|
|
60
62
|
)
|
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from sflkit import Config, instrument_config
|
|
5
5
|
from sflkit.analysis.analysis_type import AnalysisType
|
|
6
6
|
from sflkit.analysis.suggestion import Location
|
|
7
|
-
from sflkit.weights.analyzer import
|
|
7
|
+
from sflkit.weights.analyzer import ProximityAnalyzer
|
|
8
8
|
from sflkit.weights.models import (
|
|
9
9
|
TestLineModel,
|
|
10
10
|
TestDefUseModel,
|
|
@@ -39,7 +39,7 @@ class TestTimeWeights(BaseTest):
|
|
|
39
39
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
40
40
|
runner.run(Path(BaseTest.TEST_DIR), output, files=["test.py"])
|
|
41
41
|
mapping = EventMapping.load(config)
|
|
42
|
-
analyzer =
|
|
42
|
+
analyzer = ProximityAnalyzer(
|
|
43
43
|
TestFunctionModel,
|
|
44
44
|
[
|
|
45
45
|
EventFile(
|
|
@@ -82,7 +82,7 @@ class TestTimeWeights(BaseTest):
|
|
|
82
82
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
83
83
|
runner.run(Path(BaseTest.TEST_DIR), output, files=["test.py"])
|
|
84
84
|
mapping = EventMapping.load(config)
|
|
85
|
-
analyzer =
|
|
85
|
+
analyzer = ProximityAnalyzer(
|
|
86
86
|
TestLineModel,
|
|
87
87
|
[
|
|
88
88
|
EventFile(
|
|
@@ -130,7 +130,7 @@ class TestTimeWeights(BaseTest):
|
|
|
130
130
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
131
131
|
runner.run(Path(BaseTest.TEST_DIR), output, files=["test.py"])
|
|
132
132
|
mapping = EventMapping.load(config)
|
|
133
|
-
analyzer =
|
|
133
|
+
analyzer = ProximityAnalyzer(
|
|
134
134
|
TestDefUseModel,
|
|
135
135
|
[
|
|
136
136
|
EventFile(
|
|
@@ -178,7 +178,7 @@ class TestTimeWeights(BaseTest):
|
|
|
178
178
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
179
179
|
runner.run(Path(BaseTest.TEST_DIR), output, files=["test.py"])
|
|
180
180
|
mapping = EventMapping.load(config)
|
|
181
|
-
analyzer =
|
|
181
|
+
analyzer = ProximityAnalyzer(
|
|
182
182
|
TestDefUsesModel,
|
|
183
183
|
[
|
|
184
184
|
EventFile(
|
|
@@ -225,7 +225,7 @@ class TestTimeWeights(BaseTest):
|
|
|
225
225
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
226
226
|
runner.run(Path(BaseTest.TEST_DIR), output, files=["test.py"])
|
|
227
227
|
mapping = EventMapping.load(config)
|
|
228
|
-
analyzer =
|
|
228
|
+
analyzer = ProximityAnalyzer(
|
|
229
229
|
TestAssertDefUseModel,
|
|
230
230
|
[
|
|
231
231
|
EventFile(
|
|
@@ -272,7 +272,7 @@ class TestTimeWeights(BaseTest):
|
|
|
272
272
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
273
273
|
runner.run(Path(BaseTest.TEST_DIR), output, files=["test.py"])
|
|
274
274
|
mapping = EventMapping.load(config)
|
|
275
|
-
analyzer =
|
|
275
|
+
analyzer = ProximityAnalyzer(
|
|
276
276
|
TestAssertDefUsesModel,
|
|
277
277
|
[
|
|
278
278
|
EventFile(
|
|
@@ -337,7 +337,7 @@ class TestTimeWeights(BaseTest):
|
|
|
337
337
|
],
|
|
338
338
|
expectations,
|
|
339
339
|
):
|
|
340
|
-
analyzer =
|
|
340
|
+
analyzer = ProximityAnalyzer(
|
|
341
341
|
model_class,
|
|
342
342
|
[
|
|
343
343
|
EventFile(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import time
|
|
3
2
|
from pathlib import Path
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
4
|
|
|
5
5
|
from sflkit import Config, instrument_config, Analyzer
|
|
6
6
|
from sflkit.analysis.analysis_type import AnalysisType
|
|
@@ -180,3 +180,71 @@ class RunnerTests(BaseTest):
|
|
|
180
180
|
os.path.join("structure", "tests", "test_a.py::test_c"),
|
|
181
181
|
tests,
|
|
182
182
|
)
|
|
183
|
+
|
|
184
|
+
def test_runner_python_interpreter(self):
|
|
185
|
+
"""
|
|
186
|
+
Test that the python argument is correctly passed to subprocess calls.
|
|
187
|
+
This test verifies that pytest collection uses the specified python
|
|
188
|
+
interpreter instead of the default 'python3'.
|
|
189
|
+
"""
|
|
190
|
+
custom_python = "python3.11"
|
|
191
|
+
|
|
192
|
+
# Create a mock for subprocess.run
|
|
193
|
+
with patch("sflkit.runners.run.subprocess.run") as mock_run:
|
|
194
|
+
# Create a mock return value that simulates pytest collection output
|
|
195
|
+
mock_process = MagicMock()
|
|
196
|
+
mock_process.returncode = 0
|
|
197
|
+
mock_process.stdout = (
|
|
198
|
+
b"<Package tests>\n"
|
|
199
|
+
b" <Module test_a.py>\n"
|
|
200
|
+
b" <Function test_sample>\n"
|
|
201
|
+
)
|
|
202
|
+
mock_process.stderr = b""
|
|
203
|
+
mock_run.return_value = mock_process
|
|
204
|
+
|
|
205
|
+
# Create a pytest runner
|
|
206
|
+
runner = PytestRunner(set_python_path=True)
|
|
207
|
+
|
|
208
|
+
# Create a temporary directory for testing
|
|
209
|
+
test_dir = Path(BaseTest.TEST_DIR)
|
|
210
|
+
output_dir = test_dir / "events_python_test"
|
|
211
|
+
|
|
212
|
+
# Call runner.run with custom python interpreter
|
|
213
|
+
runner.run(
|
|
214
|
+
test_dir,
|
|
215
|
+
output_dir,
|
|
216
|
+
python=custom_python,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Verify that subprocess.run was called
|
|
220
|
+
self.assertTrue(mock_run.called, "subprocess.run should have been called")
|
|
221
|
+
|
|
222
|
+
# Collect all the calls made to subprocess.run
|
|
223
|
+
calls = mock_run.call_args_list
|
|
224
|
+
self.assertGreater(
|
|
225
|
+
len(calls), 0, "Expected at least one call to subprocess.run"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Check that the first call (pytest collection) uses the custom python interpreter
|
|
229
|
+
first_call_args = calls[0][0][0] # Get the command list from the first call
|
|
230
|
+
self.assertIsInstance(first_call_args, list, "Command should be a list")
|
|
231
|
+
self.assertEqual(
|
|
232
|
+
first_call_args[0],
|
|
233
|
+
custom_python,
|
|
234
|
+
f"First subprocess call should start with {custom_python}, got {first_call_args[0]}",
|
|
235
|
+
)
|
|
236
|
+
self.assertIn(
|
|
237
|
+
"-m",
|
|
238
|
+
first_call_args,
|
|
239
|
+
"pytest collection call should contain -m argument",
|
|
240
|
+
)
|
|
241
|
+
self.assertIn(
|
|
242
|
+
"pytest",
|
|
243
|
+
first_call_args,
|
|
244
|
+
"pytest collection call should contain pytest argument",
|
|
245
|
+
)
|
|
246
|
+
self.assertIn(
|
|
247
|
+
"--collect-only",
|
|
248
|
+
first_call_args,
|
|
249
|
+
"pytest collection call should contain --collect-only argument",
|
|
250
|
+
)
|
|
@@ -80,13 +80,15 @@ class ParallelRunnerTests(BaseTest):
|
|
|
80
80
|
working=BaseTest.TEST_DIR,
|
|
81
81
|
exclude="tests",
|
|
82
82
|
mapping_path=BaseTest.TEST_MAPPING,
|
|
83
|
+
thread_support=True,
|
|
83
84
|
)
|
|
84
85
|
instrument_config(config)
|
|
85
86
|
runner = ParallelInputRunner(
|
|
86
|
-
"main.py",
|
|
87
|
+
Path("main.py"),
|
|
87
88
|
failing=[["2", "1", "3"]],
|
|
88
89
|
passing=[["3", "2", "1"], ["3", "1", "2"]],
|
|
89
90
|
workers=4,
|
|
91
|
+
thread_support=True,
|
|
90
92
|
)
|
|
91
93
|
output = Path(BaseTest.TEST_DIR, "events").absolute()
|
|
92
94
|
runner.run(Path(BaseTest.TEST_DIR), output)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|