sflkit 0.5.6__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.
Files changed (84) hide show
  1. {sflkit-0.5.6/src/sflkit.egg-info → sflkit-0.5.7}/PKG-INFO +1 -1
  2. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/__init__.py +1 -1
  3. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/runners/run.py +66 -29
  4. {sflkit-0.5.6 → sflkit-0.5.7/src/sflkit.egg-info}/PKG-INFO +1 -1
  5. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_runner.py +69 -1
  6. {sflkit-0.5.6 → sflkit-0.5.7}/LICENSE +0 -0
  7. {sflkit-0.5.6 → sflkit-0.5.7}/README.md +0 -0
  8. {sflkit-0.5.6 → sflkit-0.5.7}/pyproject.toml +0 -0
  9. {sflkit-0.5.6 → sflkit-0.5.7}/setup.cfg +0 -0
  10. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/__init__.py +0 -0
  11. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/analysis_type.py +0 -0
  12. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/analyzer.py +0 -0
  13. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/factory.py +0 -0
  14. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/mapping.py +0 -0
  15. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/predicate.py +0 -0
  16. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/similarity.py +0 -0
  17. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/spectra.py +0 -0
  18. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/analysis/suggestion.py +0 -0
  19. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/cli.py +0 -0
  20. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/color.py +0 -0
  21. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/config.py +0 -0
  22. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/evaluation.py +0 -0
  23. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/events/__init__.py +0 -0
  24. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/events/event_file.py +0 -0
  25. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/events/mapping.py +0 -0
  26. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/features/__init__.py +0 -0
  27. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/features/handler.py +0 -0
  28. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/features/value.py +0 -0
  29. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/features/vector.py +0 -0
  30. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/instrumentation/__init__.py +0 -0
  31. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/instrumentation/dir_instrumentation.py +0 -0
  32. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/instrumentation/file_instrumentation.py +0 -0
  33. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/__init__.py +0 -0
  34. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/extract.py +0 -0
  35. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/finder.py +0 -0
  36. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/java/__init__.py +0 -0
  37. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/java/extract.py +0 -0
  38. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/java/factory.py +0 -0
  39. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/java/finder.py +0 -0
  40. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/java/visitor.py +0 -0
  41. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/language.py +0 -0
  42. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/meta.py +0 -0
  43. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/python/__init__.py +0 -0
  44. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/python/extract.py +0 -0
  45. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/python/factory.py +0 -0
  46. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/python/finder.py +0 -0
  47. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/python/visitor.py +0 -0
  48. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/language/visitor.py +0 -0
  49. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/logger.py +0 -0
  50. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/model/__init__.py +0 -0
  51. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/model/model.py +0 -0
  52. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/model/parallel.py +0 -0
  53. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/model/scope.py +0 -0
  54. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/runners/__init__.py +0 -0
  55. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/weights/__init__.py +0 -0
  56. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/weights/analyzer.py +0 -0
  57. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit/weights/models.py +0 -0
  58. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit.egg-info/SOURCES.txt +0 -0
  59. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit.egg-info/dependency_links.txt +0 -0
  60. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit.egg-info/entry_points.txt +0 -0
  61. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit.egg-info/requires.txt +0 -0
  62. {sflkit-0.5.6 → sflkit-0.5.7}/src/sflkit.egg-info/top_level.txt +0 -0
  63. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_analysis_objects.py +0 -0
  64. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_analyzer.py +0 -0
  65. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_cli.py +0 -0
  66. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_color.py +0 -0
  67. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_config.py +0 -0
  68. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_coverage.py +0 -0
  69. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_dependency_weights.py +0 -0
  70. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_evaluation.py +0 -0
  71. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_events.py +0 -0
  72. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_execution.py +0 -0
  73. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_expression.py +0 -0
  74. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_features.py +0 -0
  75. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_instrumentation.py +0 -0
  76. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_language.py +0 -0
  77. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_parallelization.py +0 -0
  78. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_predicates.py +0 -0
  79. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_runner_parallel.py +0 -0
  80. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_runner_pytest_versions.py +0 -0
  81. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_scope.py +0 -0
  82. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_spectra.py +0 -0
  83. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_suggestions.py +0 -0
  84. {sflkit-0.5.6 → sflkit-0.5.7}/tests/test_visitors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sflkit
3
- Version: 0.5.6
3
+ Version: 0.5.7
4
4
  Summary: SFLKit: : A Workbench for Statistical Fault Localization
5
5
  Author-email: Marius Smytzek <marius.smytzek@cispa.de>
6
6
  Project-URL: Homepage, https://github.com/smythi93/sflkit/
@@ -5,7 +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
- __version__ = "0.5.6"
8
+ __version__ = "0.5.7"
9
9
 
10
10
 
11
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(directory, files=files, base=base, environ=environ, k=k)
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
- "python3",
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
- ["python3", "-m", "pytest", test],
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, directory: Path, test_name: str, environ: Environment = None
553
+ self,
554
+ directory: Path | None,
555
+ test: str,
556
+ environ: Environment = None,
557
+ python="python3",
538
558
  ) -> TestResult:
539
- if "passing" in test_name:
540
- test = self.passing[test_name]
559
+ if "passing" in test:
560
+ test_args = self.passing[test]
541
561
  result = TestResult.PASSING
542
562
  else:
543
- test = self.failing[test_name]
563
+ test_args = self.failing[test]
544
564
  result = TestResult.FAILING
545
565
  try:
546
566
  subprocess.run(
547
- ["python3", self.access] + test,
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 float("inf")), 1)
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(self, test: str):
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(self.directory, test, environ=local_environ)
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 float("inf")), 1)
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.directory: Path = None
632
- self.output: Path = None
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(self.directory, test_name, environ=local_environ)
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,7 +697,9 @@ 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sflkit
3
- Version: 0.5.6
3
+ Version: 0.5.7
4
4
  Summary: SFLKit: : A Workbench for Statistical Fault Localization
5
5
  Author-email: Marius Smytzek <marius.smytzek@cispa.de>
6
6
  Project-URL: Homepage, https://github.com/smythi93/sflkit/
@@ -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
+ )
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