"""Tests for muse.core.test_runner — subprocess pytest adapter. Coverage: - _build_env strips non-allowed env vars. - _check_json_report returns a bool. - _parse_text_output extracts pass/fail counts from pytest text output. - _partition splits lists into roughly equal parts. - run_tests executes tests and returns RunResult with correct structure. - run_tests with a failing test returns exit_code != 0. - run_tests with no targets runs pytest discovery. - run_tests respects timeout_s (TimeoutExpired → timed_out=True). - run_tests with workers > 1 partitions correctly. - RunResult has all required fields. """ from __future__ import annotations import os import pathlib import sys import pytest from muse.core.test_runner import ( RunConfig, RunResult, CaseResult, _build_env, _check_json_report, _parse_text_output, _partition, run_tests, ) # --------------------------------------------------------------------------- # Unit tests — _build_env # --------------------------------------------------------------------------- class TestBuildEnv: def test_strips_sensitive_vars(self, monkeypatch: pytest.MonkeyPatch) -> None: """SECRET_TOKEN is not forwarded unless explicitly in allowlist.""" monkeypatch.setenv("SECRET_TOKEN", "top-secret") env = _build_env([]) assert "SECRET_TOKEN" not in env def test_mandatory_vars_forwarded(self, monkeypatch: pytest.MonkeyPatch) -> None: """PATH is always forwarded (mandatory).""" monkeypatch.setenv("PATH", "/usr/bin") env = _build_env([]) assert "PATH" in env assert env["PATH"] == "/usr/bin" def test_allowlist_forwarded(self, monkeypatch: pytest.MonkeyPatch) -> None: """Explicitly allowed vars are forwarded.""" monkeypatch.setenv("MY_CUSTOM_VAR", "value") env = _build_env(["MY_CUSTOM_VAR"]) assert "MY_CUSTOM_VAR" in env assert env["MY_CUSTOM_VAR"] == "value" def test_allowlist_missing_var_skipped( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Allowlisted var that doesn't exist in env is silently skipped.""" monkeypatch.delenv("NOT_SET_VAR", raising=False) env = _build_env(["NOT_SET_VAR"]) assert "NOT_SET_VAR" not in env def test_returns_dict_str_str(self) -> None: """_build_env returns dict[str, str].""" env = _build_env([]) assert isinstance(env, dict) for k, v in env.items(): assert isinstance(k, str) assert isinstance(v, str) # --------------------------------------------------------------------------- # Unit tests — _check_json_report # --------------------------------------------------------------------------- class TestCheckJsonReport: def test_returns_bool(self) -> None: result = _check_json_report() assert isinstance(result, bool) # --------------------------------------------------------------------------- # Unit tests — _parse_text_output # --------------------------------------------------------------------------- class TestParseTextOutput: def test_parse_all_passed(self) -> None: stdout = "3 passed in 0.12s" counts = _parse_text_output(stdout) assert counts["passed"] == 3 assert counts["failed"] == 0 assert counts["total"] == 3 def test_parse_mixed(self) -> None: stdout = "2 passed, 1 failed in 0.55s" counts = _parse_text_output(stdout) assert counts["passed"] == 2 assert counts["failed"] == 1 assert counts["total"] == 3 def test_parse_error(self) -> None: stdout = "1 passed, 1 error in 0.1s" counts = _parse_text_output(stdout) assert counts["errored"] == 1 def test_parse_empty(self) -> None: counts = _parse_text_output("") assert counts["total"] == 0 # --------------------------------------------------------------------------- # Unit tests — _partition # --------------------------------------------------------------------------- class TestPartition: def test_single_partition(self) -> None: result = _partition(["a", "b", "c"], 1) assert result == [["a", "b", "c"]] def test_three_partitions(self) -> None: items = ["a", "b", "c", "d", "e", "f"] parts = _partition(items, 3) assert len(parts) == 3 flat = [x for p in parts for x in p] assert sorted(flat) == sorted(items) def test_empty_list(self) -> None: assert _partition([], 4) == [[]] def test_more_workers_than_items(self) -> None: """When workers > len(items), we still get at least 1 partition.""" parts = _partition(["a"], 10) assert len(parts) >= 1 assert parts[0] == ["a"] # --------------------------------------------------------------------------- # Integration tests — run_tests # --------------------------------------------------------------------------- @pytest.fixture() def passing_test_file(tmp_path: pathlib.Path) -> pathlib.Path: """Create a minimal passing test file.""" f = tmp_path / "test_pass.py" f.write_text( "def test_always_passes() -> None:\n assert True\n" ) return f @pytest.fixture() def failing_test_file(tmp_path: pathlib.Path) -> pathlib.Path: """Create a minimal failing test file.""" f = tmp_path / "test_fail.py" f.write_text( "def test_always_fails() -> None:\n assert False, 'intentional'\n" ) return f class TestRunTests: def test_run_passing_test( self, passing_test_file: pathlib.Path, tmp_path: pathlib.Path ) -> None: """run_tests with a passing test returns exit_code=0.""" config = RunConfig( targets=[str(passing_test_file)], workers=1, timeout_s=30.0, extra_args=[], env_allowlist=[], cwd=tmp_path, ) result = run_tests(config) assert result["exit_code"] == 0 assert result["passed"] >= 1 assert result["timed_out"] is False assert isinstance(result["run_id"], str) assert isinstance(result["duration_ms"], float) assert result["duration_ms"] > 0 def test_run_failing_test( self, failing_test_file: pathlib.Path, tmp_path: pathlib.Path ) -> None: """run_tests with a failing test returns exit_code != 0.""" config = RunConfig( targets=[str(failing_test_file)], workers=1, timeout_s=30.0, extra_args=[], env_allowlist=[], cwd=tmp_path, ) result = run_tests(config) assert result["exit_code"] != 0 assert result["failed"] >= 1 def test_result_structure( self, passing_test_file: pathlib.Path, tmp_path: pathlib.Path ) -> None: """RunResult has all required fields with correct types.""" config = RunConfig( targets=[str(passing_test_file)], workers=1, timeout_s=30.0, extra_args=[], env_allowlist=[], cwd=tmp_path, ) result = run_tests(config) assert isinstance(result["run_id"], str) assert isinstance(result["targets"], list) assert isinstance(result["exit_code"], int) assert isinstance(result["duration_ms"], float) assert isinstance(result["results"], list) assert isinstance(result["total"], int) assert isinstance(result["passed"], int) assert isinstance(result["failed"], int) assert isinstance(result["errored"], int) assert isinstance(result["skipped"], int) assert isinstance(result["timed_out"], bool) assert isinstance(result["json_report_available"], bool) assert isinstance(result["stdout"], str) assert isinstance(result["stderr"], str) def test_progress_callback_called( self, passing_test_file: pathlib.Path, tmp_path: pathlib.Path ) -> None: """progress_cb is invoked once per test case result.""" seen: list[CaseResult] = [] def cb(r: CaseResult) -> None: seen.append(r) config = RunConfig( targets=[str(passing_test_file)], workers=1, timeout_s=30.0, extra_args=[], env_allowlist=[], cwd=tmp_path, ) run_tests(config, progress_cb=cb) # If json report is available, progress_cb should have been called. # If not available, we can't assert count — just assert it's a list. assert isinstance(seen, list) def test_run_with_workers_2( self, tmp_path: pathlib.Path ) -> None: """workers=2 runs tests in parallel partitions without error.""" # Write two test files. (tmp_path / "test_one.py").write_text( "def test_one() -> None:\n assert True\n" ) (tmp_path / "test_two.py").write_text( "def test_two() -> None:\n assert True\n" ) config = RunConfig( targets=[ str(tmp_path / "test_one.py"), str(tmp_path / "test_two.py"), ], workers=2, timeout_s=30.0, extra_args=[], env_allowlist=[], cwd=tmp_path, ) result = run_tests(config) assert result["exit_code"] == 0 assert result["timed_out"] is False def test_timeout_returns_timed_out_flag( self, tmp_path: pathlib.Path ) -> None: """A test that sleeps longer than timeout_s is killed and timed_out=True.""" slow_test = tmp_path / "test_slow.py" slow_test.write_text( "import time\n" "def test_slow() -> None:\n" " time.sleep(30)\n" ) config = RunConfig( targets=[str(slow_test)], workers=1, timeout_s=0.1, # 100 ms — definitely not enough extra_args=[], env_allowlist=[], cwd=tmp_path, ) result = run_tests(config) assert result["timed_out"] is True def test_run_id_is_unique_per_call( self, passing_test_file: pathlib.Path, tmp_path: pathlib.Path ) -> None: """Each run_tests call produces a distinct run_id.""" config = RunConfig( targets=[str(passing_test_file)], workers=1, timeout_s=30.0, extra_args=[], env_allowlist=[], cwd=tmp_path, ) r1 = run_tests(config) r2 = run_tests(config) assert r1["run_id"] != r2["run_id"]