chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:12:33 +08:00
commit aacb60a4af
3387 changed files with 981307 additions and 0 deletions
@@ -0,0 +1,686 @@
"""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 <repo>`;
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=<repo> 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"