From d198dd50aaf6119dc21f36d88704e64850e67413 Mon Sep 17 00:00:00 2001 From: Godopu Date: Thu, 8 Oct 2026 23:10:35 +0900 Subject: [PATCH] test(issue-17): item-4 helpers run lib.sh in the test's own WORKSPACE_ROOT _env kept an inherited WORKSPACE_ROOT, so every helper that sources the wrapper (lib.sh's _init_herdr_isolation) rewrote /.mam/shim/ herdr - another workspace's during the PR #51 review (v4.8.7 review, claude). Only dropping it is not enough: lib.sh then derives the checkout itself, and conftest keeps a WORKSPACE_ROOT that names this checkout, so the run would rewrite the checkout's own shim (the real one when this is the main checkout). _env now sets WORKSPACE_ROOT to the test's tmp dir. Test: with a sentinel WORKSPACE_ROOT in the environment, _run_both and a direct _env run leave the sentinel empty and the checkout's shim untouched, and the shell sees the tmp dir. With the old _env the sentinel gets .mam/shim/herdr. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe --- tests/test_issue17_item4_decisions.py | 38 +++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/test_issue17_item4_decisions.py b/tests/test_issue17_item4_decisions.py index fb592d4..342bf29 100644 --- a/tests/test_issue17_item4_decisions.py +++ b/tests/test_issue17_item4_decisions.py @@ -222,10 +222,13 @@ def _shim(tmp_path: Path) -> Path: def _env(tmp_path: Path, **extra) -> dict: + # WORKSPACE_ROOT is this test's tmp_path, never inherited: sourcing the + # wrapper sources lib.sh, which rewrites $WORKSPACE_ROOT/.mam/shim/herdr - + # and unset, lib.sh would derive the checkout itself (Issue #17 follow-up). env = {k: v for k, v in os.environ.items() - if not k.startswith(("HERDR_", "MAM_", "SKS_")) and k not in ("PYTHONPATH",)} + if not k.startswith(("HERDR_", "MAM_", "SKS_")) and k not in ("PYTHONPATH", "WORKSPACE_ROOT")} env.update(MAM_DELEGATE_JOB_NO_MAIN="1", MAM_ENV_FILE=str(tmp_path / ".mam.env.absent"), HOME=str(tmp_path), - DELEGATE_JOB_LOGS_DIR=str(tmp_path / "logs")) + WORKSPACE_ROOT=str(tmp_path), DELEGATE_JOB_LOGS_DIR=str(tmp_path / "logs")) env.update({k: str(v) for k, v in extra.items() if v is not None}) return env @@ -464,6 +467,37 @@ def test_c_mam_int_and_knob(tmp_path, locale, minimum): assert outs[0] == outs[1], outs +def _mtime(path: Path): + try: + return path.stat().st_mtime_ns + except OSError: + return None + + +def test_c_helpers_write_nothing_to_an_inherited_workspace(tmp_path, monkeypatch): + """v4.8.7 review (claude): _env kept an inherited WORKSPACE_ROOT, so every + helper that sources the wrapper (lib.sh) rewrote /.mam/ + shim/herdr - another workspace's, during the PR #51 review. With a sentinel + workspace in the environment, nothing appears under it, the checkout's own + shim is not rewritten either, and the shell sees the test's tmp dir.""" + sentinel = tmp_path / "sentinel-ws" + sentinel.mkdir() + monkeypatch.setenv("WORKSPACE_ROOT", str(sentinel)) + checkout_shim = REPO_ROOT / ".mam" / "shim" / "herdr" + before = _mtime(checkout_shim) + both = _run_both(tmp_path / "both", 'echo "ws=$WORKSPACE_ROOT"\n', {}) + work = tmp_path / "direct" + work.mkdir() + res = subprocess.run(["bash", "-c", f'source "{WRAPPER}"; echo "ws=$WORKSPACE_ROOT"'], capture_output=True, + text=True, cwd=str(work), timeout=60, env=_env(work)) + assert sorted(str(p.relative_to(sentinel)) for p in sentinel.rglob("*")) == [], "written into the sentinel" + assert _mtime(checkout_shim) == before, "the checkout's shim was rewritten" + for rc, out, err, _ in both: + assert rc == 0 and "ws=" in out, (rc, out, err) + assert res.returncode == 0 and f"ws={work}" in res.stdout, (res.stdout, res.stderr) + assert (work / ".mam" / "shim" / "herdr").is_file(), "lib.sh wrote its shim into the test's own root" + + # --------------------------------------------------------------------------- # S - static guard # ---------------------------------------------------------------------------