From 69a34583ba4bab52998df8c21e3d875e82d2d76c Mon Sep 17 00:00:00 2001 From: MgnMtn Date: Sat, 1 Aug 2026 17:31:55 +0100 Subject: [PATCH 1/2] feat: pytest output --- multiplex/execute/pytest_runner.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/multiplex/execute/pytest_runner.py b/multiplex/execute/pytest_runner.py index e4dfc41..1736bbb 100644 --- a/multiplex/execute/pytest_runner.py +++ b/multiplex/execute/pytest_runner.py @@ -21,7 +21,7 @@ from util.rewrite_method import rewrite_method -def _execute(project_root): +def _execute(project_root, output_path=None, approach=None, label=None): """Run the project's tests with pytest. Returns True if pytest exits 0 (all tests pass) — i.e. the mutant is not @@ -31,7 +31,8 @@ def _execute(project_root): command = [sys.executable, "-m", "pytest", "-q", str(project_root)] try: result = subprocess.run( - command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, check=False ) except FileNotFoundError as exc: raise SystemExit( @@ -39,6 +40,15 @@ def _execute(project_root): f"({sys.executable}). Install pytest to use the 'pytest' runtool." ) from exc + if output_path is not None and approach and label: + test_dir = Path(output_path, approach + "-test") + test_dir.mkdir(parents=True, exist_ok=True) + safe = str(label).replace(os.sep, "_") + (test_dir / f"{safe}_test.txt").write_text( + f"$ {' '.join(command)}\n# exit code: {result.returncode}\n\n" + f"{result.stdout}" + ) + return result.returncode == 0 @@ -59,7 +69,7 @@ def run_mutants( mutants = [["MUTANT", "EQUIVALENCE", "COMPILABLE", "SURVIVES"]] - if not _execute(project_root): + if not _execute(project_root, output_path, approach, "ORIGINAL"): raise IOError( "The original (unmutated) project did not pass pytest, so mutants " "cannot be evaluated against it. Run it manually to see why: " @@ -83,7 +93,7 @@ def run_mutants( mutant_survives = False if mutant_compiles: - mutant_survives = _execute(project_root) + mutant_survives = _execute(project_root, output_path, approach, mutant_file) mutant_output.append(str(mutant_survives)) mutants.append(mutant_output) From 7210d7f1635204892a09a2fc40eac2bcf927e6aa Mon Sep 17 00:00:00 2001 From: MgnMtn Date: Sat, 1 Aug 2026 17:45:53 +0100 Subject: [PATCH 2/2] feat: pytest output --- multiplex/execute/pytest_runner.py | 2 +- tests/execute/test_pytest_runner.py | 39 ++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/multiplex/execute/pytest_runner.py b/multiplex/execute/pytest_runner.py index 1736bbb..518ed31 100644 --- a/multiplex/execute/pytest_runner.py +++ b/multiplex/execute/pytest_runner.py @@ -43,7 +43,7 @@ def _execute(project_root, output_path=None, approach=None, label=None): if output_path is not None and approach and label: test_dir = Path(output_path, approach + "-test") test_dir.mkdir(parents=True, exist_ok=True) - safe = str(label).replace(os.sep, "_") + safe = Path(str(label).replace(os.sep, "_")).stem (test_dir / f"{safe}_test.txt").write_text( f"$ {' '.join(command)}\n# exit code: {result.returncode}\n\n" f"{result.stdout}" diff --git a/tests/execute/test_pytest_runner.py b/tests/execute/test_pytest_runner.py index f2fddc0..c0a1a28 100644 --- a/tests/execute/test_pytest_runner.py +++ b/tests/execute/test_pytest_runner.py @@ -65,6 +65,37 @@ def raise_fnf(*a, **k): assert "pytest" in str(exc.value).lower() +def _run_reporting(returncode, stdout): + def run(command, *args, **kwargs): + return types.SimpleNamespace(returncode=returncode, stdout=stdout) + + return run + + +def test_execute_writes_test_output_file_when_labelled(tmp_path, monkeypatch): + # Given an output path, approach and label, _execute persists the run's + # captured output to -test/