"""Unit tests for opensquilla.contrib.codetask.verification.""" import json import shutil import subprocess import sys from pathlib import Path import pytest from opensquilla.contrib.codetask import verification from opensquilla.contrib.codetask.config import VERIFICATION_MANIFEST_NAME from opensquilla.contrib.codetask.types import ( AcceptanceCheck, RegressionResult, TaskState, ) class TestManifestLoading: def test_missing_manifest(self, tmp_path): assert verification.load_manifest(tmp_path) is None def test_malformed_manifest(self, tmp_path): (tmp_path / VERIFICATION_MANIFEST_NAME).write_text("not json{") assert verification.load_manifest(tmp_path) is None def test_valid_manifest(self, tmp_path): (tmp_path / VERIFICATION_MANIFEST_NAME).write_text(json.dumps({"testable": True})) assert verification.load_manifest(tmp_path) == {"testable": True} class TestStateDecision: def _green(self, before): return AcceptanceCheck(name="t", command="c", before=before, after="pass") def test_red_then_green_is_verified(self): state, _ = verification._decide_state([self._green("fail")], None, None) assert state == TaskState.VERIFIED def test_green_on_base_is_already_satisfied(self): state, _ = verification._decide_state([self._green("pass")], None, None) assert state == TaskState.ALREADY_SATISFIED def test_after_fail_is_failed(self): check = AcceptanceCheck(name="t", command="c", before="fail", after="fail") state, _ = verification._decide_state([check], None, None) assert state == TaskState.FAILED def test_regression_new_failures_is_failed(self): reg = RegressionResult(command="pytest", ran=True, new_failures=2) state, _ = verification._decide_state([self._green("fail")], reg, None) assert state == TaskState.FAILED def test_regression_clean_keeps_verified(self): reg = RegressionResult(command="pytest", ran=True, new_failures=0) state, _ = verification._decide_state([self._green("fail")], reg, None) assert state == TaskState.VERIFIED def test_unprovable_red_is_not_verified(self): # Green but red never established (no test_paths) must FAIL CLOSED, # never claim VERIFIED (codex review #2). state, detail = verification._decide_state([self._green(None)], None, "missing_test_paths") assert state == TaskState.INVALID_ACCEPTANCE_TEST assert "red state could not be proven" in detail def test_worktree_failure_is_environment_blocked(self): state, _ = verification._decide_state([self._green(None)], None, "worktree_failed") assert state == TaskState.ENVIRONMENT_BLOCKED class TestParseHelpers: def test_parse_pytest_failures(self): assert verification._parse_failures("3 passed, 2 failed", 1) == 2 def test_parse_failures_returncode_zero(self): assert verification._parse_failures("all good", 0) == 0 def test_parse_failures_unparseable_nonzero(self): assert verification._parse_failures("boom", 1) is None def test_parse_passes(self): assert verification._parse_passes("10 passed, 0 failed") == 10 def test_failing_names_set(self): out = "FAILED tests/test_a.py::test_x - boom\nFAILED tests/test_b.py::test_y" names = verification._failing_names(out) assert names == {"tests/test_a.py::test_x", "tests/test_b.py::test_y"} def test_failing_names_none_when_absent(self): assert verification._failing_names("3 passed") is None class TestPathSafety: def test_rejects_absolute_and_parent_escape(self): safe = verification._safe_rel_paths( ["tests/ok.py", "/etc/passwd", "../../secret", "a/../b", ""] ) assert safe == ["tests/ok.py"] class TestRegressionFailClosed: def test_unparseable_nonzero_is_treated_as_regressed(self, monkeypatch): # npm/go-style failure with no parseable count must NOT report clean # (codex review #3). def fake_shell(command, *, cwd, timeout, repo=None): return 1, "npm ERR! test failed" monkeypatch.setattr(verification, "_run_shell", fake_shell) # Force the base worktree to be unavailable so only the head run counts. monkeypatch.setattr( verification, "_BaseWorktree", _raise_worktree, ) from pathlib import Path reg = verification._run_regression( "npm test", repo=Path("/x"), base_commit="abc", timeout=10 ) assert reg is not None assert reg.new_failures == 1 def test_named_diff_does_not_mask_new_failure(self, monkeypatch): # base fails test_old; head fails test_new. Counts both = 1, but the # NEW failure must still be detected (codex review #4). calls = {"n": 0} def fake_shell(command, *, cwd, timeout, repo=None): calls["n"] += 1 if calls["n"] == 1: # head return 1, "FAILED tests/t.py::test_new\n1 failed" return 1, "FAILED tests/t.py::test_old\n1 failed" # base monkeypatch.setattr(verification, "_run_shell", fake_shell) class _OkWorktree: def __init__(self, *a): pass def __enter__(self): from pathlib import Path return Path("/base") def __exit__(self, *a): return None monkeypatch.setattr(verification, "_BaseWorktree", _OkWorktree) from pathlib import Path reg = verification._run_regression("pytest", repo=Path("/x"), base_commit="abc", timeout=10) assert reg.new_failures == 1 def _raise_worktree(*a): class _W: def __enter__(self): raise verification._WorktreeError("unavailable") def __exit__(self, *a): return None return _W() class TestVerifyEndToEnd: def test_no_manifest_is_invalid(self, tmp_path): out = verification.verify(repo=tmp_path, base_commit="x", scratch_dir=tmp_path) assert out.state == TaskState.INVALID_ACCEPTANCE_TEST def test_not_testable(self, tmp_path): (tmp_path / VERIFICATION_MANIFEST_NAME).write_text( json.dumps({"testable": False, "not_testable_reason": "docs only"}) ) out = verification.verify(repo=tmp_path, base_commit="x", scratch_dir=tmp_path) assert out.state == TaskState.NOT_TESTABLE assert "docs only" in out.detail def test_testable_but_no_tests_is_invalid(self, tmp_path): (tmp_path / VERIFICATION_MANIFEST_NAME).write_text( json.dumps({"testable": True, "acceptance_tests": []}) ) out = verification.verify(repo=tmp_path, base_commit="x", scratch_dir=tmp_path) assert out.state == TaskState.INVALID_ACCEPTANCE_TEST @pytest.mark.skipif(sys.platform == "win32", reason="code-task Windows support is WIP") class TestLocalizeCommand: """Guard the absolute-cd contamination fix (flask src-layout case).""" def test_rewrites_absolute_cd_to_worktree(self, tmp_path): repo = tmp_path / "run" / "repo" wt = tmp_path / "base-worktree" repo.mkdir(parents=True) wt.mkdir() # The exact shape the flask agent emitted: cd into the task repo, then # PYTHONPATH=src pytest. The absolute cd must be redirected to the wt. cmd = f"cd {repo} && PYTHONPATH=src python3 -m pytest tests/test_x.py::t -v" out = verification._localize_command(cmd, repo, wt) assert str(repo) not in out assert f"cd {wt} &&" in out # The relative PYTHONPATH/test path is untouched (resolves against wt). assert "PYTHONPATH=src python3 -m pytest tests/test_x.py::t" in out def test_rewrites_absolute_subpath_before_repo(self, tmp_path): # PYTHONPATH pointing at an absolute repo subdir must also be redirected. repo = tmp_path / "repo" wt = tmp_path / "wt" repo.mkdir() wt.mkdir() cmd = f"PYTHONPATH={repo}/src python -m pytest {repo}/tests/t.py" out = verification._localize_command(cmd, repo, wt) assert str(repo) not in out assert f"PYTHONPATH={wt}/src" in out assert f"{wt}/tests/t.py" in out def test_relative_command_unchanged(self, tmp_path): repo = tmp_path / "repo" wt = tmp_path / "wt" repo.mkdir() wt.mkdir() cmd = "PYTHONPATH=src python -m pytest tests/test_x.py" assert verification._localize_command(cmd, repo, wt) == cmd def test_sibling_path_not_corrupted(self, tmp_path): # /abs/repo must NOT rewrite a sibling like /abs/repo-fixture or # /abs/repo2 (codex review: raw substring replace would corrupt them). repo = tmp_path / "repo" wt = tmp_path / "wt" repo.mkdir() wt.mkdir() sibling = f"{repo}-fixture" sibling2 = f"{repo}2" cmd = f"cat {sibling}/data && ls {sibling2} && cd {repo} && pytest" out = verification._localize_command(cmd, repo, wt) # The sibling paths survive intact... assert sibling in out assert sibling2 in out # ...but the exact repo path (followed by a space) is rewritten. assert f"cd {wt} && pytest" in out def test_punctuation_siblings_not_corrupted(self, tmp_path): # Filename-legal chars beyond [A-Za-z0-9._-] (codex review #2): the # boundary must NOT fire on these, so the siblings stay intact. repo = tmp_path / "repo" wt = tmp_path / "wt" repo.mkdir() wt.mkdir() for ch in "+@=,~.": sibling = f"{repo}{ch}fixture" out = verification._localize_command(f"cat {sibling}/x", repo, wt) assert sibling in out, f"sibling with '{ch}' was corrupted: {out}" assert str(wt) not in out, f"'{ch}' wrongly treated as a boundary: {out}" def test_real_boundaries_rewrite(self, tmp_path): # The genuine path boundaries DO rewrite: '/', space, quote, colon, EOL. repo = tmp_path / "repo" wt = tmp_path / "wt" repo.mkdir() wt.mkdir() for tail in ["/src && x", " && x", '"', ":/other", ";", ")"]: out = verification._localize_command(f"cmd {repo}{tail}", repo, wt) assert str(repo) not in out, f"boundary '{tail}' failed to rewrite: {out}" assert str(wt) in out @pytest.mark.skipif(sys.platform == "win32", reason="code-task Windows support is WIP") def test_red_phase_uses_localized_command(monkeypatch, tmp_path): """End-to-end: the red-phase run must receive the worktree-localized command. Reproduces the flask bug: agent's acceptance command hardcodes `cd `; without localization the red run executes against the fixed task repo. """ repo = tmp_path / "repo" repo.mkdir() scratch = tmp_path / "scratch" scratch.mkdir() (scratch / VERIFICATION_MANIFEST_NAME).write_text( json.dumps( { "testable": True, "acceptance_tests": [ { "name": "t", "command": f"cd {repo} && python -m pytest tests/t.py", "test_paths": ["tests/t.py"], } ], } ) ) (repo / "tests").mkdir() (repo / "tests" / "t.py").write_text("def test_ok():\n assert True\n") seen = {"green": None, "red": None} calls = {"n": 0} def fake_run_shell(command, *, cwd, timeout, repo=None): calls["n"] += 1 if calls["n"] == 1: seen["green"] = (command, str(cwd)) else: seen["red"] = (command, str(cwd)) return 0, "" class _OkWorktree: def __init__(self, repo, base): self.repo = repo def __enter__(self): wt = tmp_path / "base-wt" wt.mkdir(exist_ok=True) return wt def __exit__(self, *a): return None monkeypatch.setattr(verification, "_run_shell", fake_run_shell) monkeypatch.setattr(verification, "_BaseWorktree", _OkWorktree) monkeypatch.setattr(verification, "_overlay_paths", lambda r, w, p: True) verification.verify(repo=repo, base_commit="abc", scratch_dir=scratch) # GREEN ran in the task repo with the original (absolute-cd) command. assert str(repo) in seen["green"][0] # RED ran with the localized command: the absolute repo path is gone, # redirected to the worktree, so it can no longer teleport into the fix. assert str(repo) not in seen["red"][0] assert str(tmp_path / "base-wt") in seen["red"][0] def test_run_shell_resolves_python_from_repo_venv_in_foreign_cwd(tmp_path): """Even when cwd has NO venv (the base worktree), repo= makes bare python AND python3 resolve to the run repo's .venv interpreter.""" from opensquilla.contrib.codetask import verification repo = tmp_path / "repo" venv_bin = repo / ".venv" / "bin" venv_bin.mkdir(parents=True) fake = venv_bin / "python" fake.write_text("#!/bin/sh\necho VENV_PY_OK\n") fake.chmod(0o755) foreign = tmp_path / "wt" # like the base worktree: no .venv here foreign.mkdir() rc, out = verification._run_shell("python", cwd=foreign, timeout=30, repo=repo) assert rc == 0 and "VENV_PY_OK" in out, (rc, out) rc, out = verification._run_shell("python3", cwd=foreign, timeout=30, repo=repo) assert rc == 0 and "VENV_PY_OK" in out, (rc, out) # python3 too (uv-venv safety) def test_repo_venv_python_candidates_include_windows_scripts(tmp_path): from opensquilla.contrib.codetask import verification repo = tmp_path / "repo" scripts = repo / ".venv" / "Scripts" scripts.mkdir(parents=True) exe = scripts / "python.exe" exe.write_text("fake", encoding="utf-8") got = list(verification._repo_venv_python_candidates(repo)) assert exe in got def test_repo_venv_python_candidates_keep_posix_first(tmp_path): from opensquilla.contrib.codetask import verification repo = tmp_path / "repo" assert verification._repo_venv_python_candidates(repo) == ( repo / ".venv" / "bin" / "python", repo / ".venv" / "Scripts" / "python.exe", repo / ".venv" / "Scripts" / "python", ) def test_bash_path_entry_converts_windows_drive_path_to_msys(monkeypatch): from opensquilla.contrib.codetask import verification monkeypatch.setattr(verification.os, "name", "nt") assert ( verification._bash_path_entry(Path(r"C:\repo\.venv\Scripts\python.exe")) == "/c/repo/.venv/Scripts/python.exe" ) assert ( verification._bash_path_entry(Path("D:/Work/repo/.venv/Scripts")) == "/d/Work/repo/.venv/Scripts" ) def test_bash_path_entry_preserves_posix_path(monkeypatch): from opensquilla.contrib.codetask import verification monkeypatch.setattr(verification.os, "name", "posix") path = Path("/tmp/repo/.venv/bin/python") assert verification._bash_path_entry(path) == str(path) def test_write_python_shim_falls_back_to_wrapper_when_symlink_fails(tmp_path, monkeypatch): from opensquilla.contrib.codetask import verification chmod_calls = [] def fail_symlink(self, target): raise OSError("symlinks unavailable") def record_chmod(self, mode): chmod_calls.append((self.name, mode)) monkeypatch.setattr(verification.Path, "symlink_to", fail_symlink) monkeypatch.setattr(verification.Path, "chmod", record_chmod) monkeypatch.setattr(verification.os, "name", "nt") shim_dir = tmp_path / "shim" shim_dir.mkdir() verification._write_python_shim( shim_dir, "python", Path(r"C:\repo\.venv\Scripts\python.exe"), ) shim = shim_dir / "python" assert shim.read_text(encoding="utf-8") == ( "#!/usr/bin/env bash\n" 'exec /c/repo/.venv/Scripts/python.exe "$@"\n' ) assert chmod_calls == [("python", 0o755)] def test_run_shell_sets_uv_project_for_uv_repo(tmp_path): """For a uv project (has uv.lock), _run_shell exports UV_PROJECT= so `uv run` reuses the run repo's env even from the base worktree; non-uv repos get no UV_PROJECT.""" from opensquilla.contrib.codetask import verification uv_repo = tmp_path / "uvrepo" uv_repo.mkdir() (uv_repo / "uv.lock").write_text("", encoding="utf-8") foreign = tmp_path / "wt" # like the base worktree foreign.mkdir() rc, out = verification._run_shell( 'echo "UVP=[$UV_PROJECT]"', cwd=foreign, timeout=30, repo=uv_repo ) assert rc == 0 and f"UVP=[{uv_repo}]" in out, out plain = tmp_path / "plainrepo" plain.mkdir() # no uv.lock rc, out = verification._run_shell( 'echo "UVP=[$UV_PROJECT]"', cwd=foreign, timeout=30, repo=plain ) assert rc == 0 and "UVP=[]" in out, out @pytest.mark.skipif(sys.platform == "win32", reason="code-task Windows support is WIP") @pytest.mark.skipif(shutil.which("uv") is None, reason="uv not installed") def test_uv_run_from_worktree_reuses_repo_venv(tmp_path): """End-to-end: with UV_PROJECT injected by _run_shell, `uv run` from a base worktree (which has NO .venv) reuses the RUN REPO's .venv -- deps are present and no separate wt/.venv is built. Skips offline (uv sync needs the cache).""" from opensquilla.contrib.codetask import verification def g(cmd, cwd): return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True) proj = tmp_path / "proj" proj.mkdir() g(["git", "init", "-q"], proj) g(["git", "config", "user.email", "s@s"], proj) g(["git", "config", "user.name", "s"], proj) (proj / "pyproject.toml").write_text( '[project]\nname = "p"\nversion = "0.1.0"\nrequires-python = ">=3.10"\n' 'dependencies = []\n[dependency-groups]\ndev = ["pytest>=8"]\n', encoding="utf-8", ) (proj / "calc.py").write_text("def add(a, b):\n return a + b\n", encoding="utf-8") (proj / "test_calc.py").write_text( "from calc import add\n\ndef test_add():\n assert add(1, 2) == 3\n", encoding="utf-8", ) sync = g(["uv", "sync", "--all-groups"], proj) if sync.returncode != 0: pytest.skip(f"uv sync unavailable (offline?): {(sync.stderr or '')[-160:]}") g(["git", "add", "-A"], proj) g(["git", "commit", "-qm", "init"], proj) wt = tmp_path / "wt" g(["git", "worktree", "add", "-q", "--detach", str(wt)], proj) assert not (wt / ".venv").exists() rc, out = verification._run_shell( "uv run --locked python -c 'import sys, pytest; print(\"PREFIX=\" + sys.prefix)'", cwd=wt, timeout=180, repo=proj, ) assert rc == 0, out assert f"PREFIX={proj}/.venv" in out, out # reused the repo venv, not wt/.venv assert not (wt / ".venv").exists(), "uv built a separate worktree venv" def test_tail_bounds_output(): assert verification._tail("") == "" assert verification._tail("a\nb\nc") == "a\nb\nc" many = "\n".join(str(i) for i in range(100)) out = verification._tail(many, max_lines=10) assert out.splitlines() == [str(i) for i in range(90, 100)] big = "x" * 9000 assert len(verification._tail(big, max_chars=4000)) == 4000 # ─────────────── bash resolution (probe-past-fake-stub) ────────────────── # Field report: on Windows a fake `bash.cmd` ahead of real Git Bash on PATH # hijacks `shutil.which("bash")` and the verifier dies on the stub instead # of falling through to the real shell. The class also covers WSL launchers # that exist but exit non-zero when WSL is not configured. POSIX is bit- # equivalent to the old shutil.which path (the bug is Windows-only), so the # enumeration/probe tests below are skipped there. import os # noqa: E402 import sys # noqa: E402 -- used by the busybox-mimic test below @pytest.fixture(autouse=True) def _reset_bash_cache_between_tests(): verification._reset_bash_cache() yield verification._reset_bash_cache() def _real_bash() -> str: real = shutil.which("bash") if real is None: pytest.skip("real bash not available") return real @pytest.mark.skipif(os.name != "nt", reason="fake bash.cmd hijack is Windows-only") def test_resolve_bash_skips_fake_cmd_stub(tmp_path, monkeypatch): """Mirror the field report: fake bash.cmd ahead of real bash on PATH. Without the probe, `shutil.which("bash")` returns the .cmd stub and the verifier crashes with exit 17 + "FAKE BASH STUB". `_resolve_bash` must fall past it to the real Git Bash entry further down PATH. """ real = _real_bash() fake_dir = tmp_path / "fake-bin" fake_dir.mkdir() (fake_dir / "bash.cmd").write_text( "@echo FAKE BASH STUB\r\n@exit /b 17\r\n", encoding="ascii" ) monkeypatch.setenv("PATH", f"{fake_dir}{os.pathsep}{os.path.dirname(real)}") monkeypatch.delenv("OPENSQUILLA_BASH", raising=False) resolved = verification._resolve_bash() assert resolved is not None assert os.path.normcase(resolved) != os.path.normcase(str(fake_dir / "bash.cmd")) # And it actually runs the verifier command: rc, out = verification._run_shell("echo real_bash_ran", cwd=tmp_path, timeout=10) assert rc == 0 and "real_bash_ran" in out, (rc, out) @pytest.mark.skipif(os.name != "nt", reason="enumerate-and-probe is Windows-only") def test_resolve_bash_skips_failing_probe(tmp_path, monkeypatch): """A bash whose -lc probe exits non-zero (e.g. an unconfigured WSL stub that prints an error and exits 1) must be skipped in favor of a later candidate that actually runs.""" real = _real_bash() broken_dir = tmp_path / "broken-bin" broken_dir.mkdir() stub = broken_dir / "bash.cmd" stub.write_text("@echo broken-bash\r\n@exit /b 99\r\n", encoding="ascii") monkeypatch.setenv("PATH", f"{broken_dir}{os.pathsep}{os.path.dirname(real)}") monkeypatch.delenv("OPENSQUILLA_BASH", raising=False) resolved = verification._resolve_bash() assert resolved is not None assert os.path.normcase(resolved) != os.path.normcase(str(stub)) @pytest.mark.skipif(os.name != "nt", reason="busybox-vs-bash discrimination is Windows-only") def test_resolve_bash_rejects_non_bash_with_clear_sentinel(tmp_path, monkeypatch): """The probe must reject a shell that runs ``-lc`` cleanly but isn't actually bash (e.g. busybox-w32's bash.exe), because verification commands lean on bash-only syntax (``[[ ]]``, arrays, ``BASH_SOURCE``). Discrimination test: this stub correctly handles a bare ``echo X`` script — so it would pass the naive ``echo SENTINEL`` probe — but fails the bash-specific ``$BASH_VERSION`` guard the way a real busybox would. Proves the strengthened probe is doing real work. """ real = _real_bash() not_bash = tmp_path / "not-bash" not_bash.mkdir() mimic_py = not_bash / "busybox_mimic.py" mimic_py.write_text( "import sys\n" "argv = sys.argv[1:]\n" "# Mimic only what is needed: a shell that handles `-lc \"echo X\"` but\n" "# does not set BASH_VERSION, so the probe's test-guard fails and the\n" "# && short-circuits -- same observable shape as busybox-w32 bash.\n" "if len(argv) >= 2 and argv[0] in ('-lc', '-c'):\n" " script = argv[1].strip()\n" " if script.startswith('echo '):\n" " print(script[5:].strip().strip('\\\"').strip(\"'\"))\n" " sys.exit(0)\n" " sys.exit(1)\n" "sys.exit(2)\n", encoding="ascii", ) stub = not_bash / "bash.cmd" stub.write_text( f'@"{sys.executable}" "{mimic_py}" %*\r\n', encoding="ascii" ) monkeypatch.setenv("PATH", f"{not_bash}{os.pathsep}{os.path.dirname(real)}") monkeypatch.delenv("OPENSQUILLA_BASH", raising=False) # Sanity: the stub does pass the naive probe (proves the test is honest). import subprocess as _sp naive = _sp.run( [str(stub), "-lc", f"echo {verification._BASH_PROBE_SENTINEL}"], capture_output=True, text=True, timeout=10, ) assert naive.returncode == 0 assert verification._BASH_PROBE_SENTINEL in (naive.stdout or "") # And the strengthened probe rejects it, falling through to real bash. resolved = verification._resolve_bash() assert resolved is not None assert os.path.normcase(resolved) != os.path.normcase(str(stub)) @pytest.mark.skipif(os.name != "nt", reason="OPENSQUILLA_BASH override is Windows-only") def test_resolve_bash_honors_env_override(tmp_path, monkeypatch): """OPENSQUILLA_BASH wins over PATH discovery — including over a real bash already on PATH — so operators can force a specific shell without rearranging PATH.""" real = _real_bash() monkeypatch.setenv("OPENSQUILLA_BASH", real) # Remove all bash candidates from PATH so the only way to find it is via override. monkeypatch.setenv("PATH", str(tmp_path)) assert verification._resolve_bash() == real @pytest.mark.skipif(os.name != "nt", reason="enumerate-and-probe is Windows-only") def test_resolve_bash_returns_none_when_no_working_bash(tmp_path, monkeypatch): """If no candidate probes successfully, return None and let `_run_shell` surface the actionable OSERROR hint (instead of running a broken stub).""" monkeypatch.setenv("PATH", str(tmp_path)) # empty dir, no bash monkeypatch.delenv("OPENSQUILLA_BASH", raising=False) # Also blank out the Git-Bash fallback locations our resolver checks. for env_var in ("ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"): monkeypatch.setenv(env_var, str(tmp_path)) assert verification._resolve_bash() is None rc, out = verification._run_shell("true", cwd=tmp_path, timeout=5) assert rc == -1 and "OSERROR" in out and "bash" in out, out def test_resolve_bash_memoizes(tmp_path, monkeypatch): """Once resolved, repeated calls must return the cached path and skip re-probing — the gateway runs many _run_shell calls per task and probes cost ~50ms each on Windows.""" real = _real_bash() if os.name == "nt": monkeypatch.setenv("OPENSQUILLA_BASH", real) # POSIX: cache short-circuits on shutil.which("bash") which is `real`. first = verification._resolve_bash() calls = {"n": 0} real_probe = verification._probe_bash def counting_probe(p): calls["n"] += 1 return real_probe(p) monkeypatch.setattr(verification, "_probe_bash", counting_probe) second = verification._resolve_bash() assert first == second == real assert calls["n"] == 0, "cached resolution must not re-probe"