"""Issue #17 follow-up (v4.8.7 review): no test writes into the workspace it inherited. Every `source lib.sh` rewrites $WORKSPACE_ROOT/.mam/shim/herdr, and lib.sh derives the checkout when the variable is unset; conftest keeps a WORKSPACE_ROOT that names this checkout. So the danger is the checkout itself - the live workspace when the suite runs in the main checkout from an agent pane. - W1: a copy of this checkout runs one test of every module whose shells used to inherit the root, with WORKSPACE_ROOT naming the copy: the copy gets no .mam at all (the modules now use own_workspace_root or pin a root). - W2: test_orc_onboard's environment, taken at import, does not carry the import-time WORKSPACE_ROOT into a later call. """ from __future__ import annotations import os import re import shutil import subprocess import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent TESTS = REPO_ROOT / "tests" # one test per module that sourced lib.sh (or ran the wrapper / run_loop.sh) # with the inherited root; each wrote /.mam/shim/herdr at 6b00b6e NODES = [ "tests/test_version_consistency.py::test_mam_version_is_not_env_overridable", "tests/test_b19_headless_reconcile_fixes.py::test_fullscreen_tip_is_not_a_blocking_dialog", "tests/test_b19_headless_reconcile_fixes.py::test_bug4_slow_settling_pane_success", "tests/test_b8_send_keys_verification.py::test_send_keys_safe_b8_verification", "tests/test_issue6_fail_closed.py::test_t1_25_stop_session_cancels_redispatched_job_with_pinned_token", "tests/test_loop_cli.py::test_help_lists_creator_not_target_agent", "tests/test_issue5_phase3_loop.py::test_p3_02_run_loop_wrapper_help_lists_same_as_bash", "tests/test_issue5_phase4_runtime.py::test_p4_03_freeze_bypass_by_default", "tests/test_o3_scoped_guard.py::test_b13_no_freeze_switch_disables_reexec", "tests/test_issue25_plan_reuse.py::test_p7b_the_legacy_shell_loop_refuses_the_option", "tests/test_orc_onboard.py::test_o12_onboard_add_uuid", "tests/test_workspace_scope.py::TestWorkspaceScope::test_derived_herdr_session_name", "tests/test_issue17_item4_decisions.py::test_c_cmd_wait", "tests/test_o2_race_free_lock.py::test_o2_12_run_loop_exits_on_lock_failure", "tests/test_o1_rebuttal.py::test_v3_rebut_option_parsing_and_budget", ] def _copy_checkout(dst: Path) -> None: files = subprocess.run(["git", "-C", str(REPO_ROOT), "ls-files", "-co", "--exclude-standard"], capture_output=True, text=True, check=True).stdout.splitlines() for rel in files: src = REPO_ROOT / rel if src.is_file() and not rel.startswith(("nats-docker/", ".mam/")): (dst / rel).parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, dst / rel) def test_w1_no_test_writes_into_the_checkout_it_runs_in(tmp_path): copy = tmp_path / "checkout" _copy_checkout(copy) assert not (copy / ".mam").exists() env = {k: v for k, v in os.environ.items() if not k.startswith(("HERDR_", "MAM_", "MQTT_", "SKS_"))} env.update(WORKSPACE_ROOT=str(copy), AGENT_PYTHON_BIN=sys.executable, PYTHONDONTWRITEBYTECODE="1") env.pop("PYTHONPATH", None) res = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-q", "-x", f"--basetemp={tmp_path / 'bt'}", *NODES], capture_output=True, text=True, cwd=str(copy), env=env, timeout=900) assert res.returncode == 0, res.stdout[-3000:] + res.stderr[-2000:] summary = res.stdout.strip().splitlines()[-1] # some nodes are parametrized: at least one test each assert int(re.search(r"(\d+) passed", summary).group(1)) >= len(NODES) and "skipped" not in summary, summary written = sorted(str(p.relative_to(copy)) for p in (copy / ".mam").rglob("*")) if (copy / ".mam").exists() else [] assert written == [], f"tests wrote into the checkout they ran in: {written}" _ONBOARD_PROBE = r""" import os, sys from pathlib import Path sys.path.insert(0, sys.argv[1]) import test_orc_onboard as m # imported while WORKSPACE_ROOT is the sentinel ws = Path(sys.argv[2]) os.environ["WORKSPACE_ROOT"] = str(ws) # what mam_sandbox sets later m.run_find_uuid(str(ws), "claude") """ def test_w2_onboard_does_not_carry_its_import_time_root(tmp_path): sentinel, ws = tmp_path / "sentinel", tmp_path / "fixture-ws" sentinel.mkdir() ws.mkdir() env = {k: v for k, v in os.environ.items() if not k.startswith(("HERDR_", "MAM_", "MQTT_", "SKS_"))} env.update(WORKSPACE_ROOT=str(sentinel), HOME=str(tmp_path), PYTHONDONTWRITEBYTECODE="1") res = subprocess.run([sys.executable, "-c", _ONBOARD_PROBE, str(TESTS), str(ws)], capture_output=True, text=True, env=env, cwd=str(tmp_path), timeout=120) assert res.returncode == 0, res.stderr[-2000:] assert sorted(str(p.relative_to(sentinel)) for p in sentinel.rglob("*")) == [], "written into the import-time root" assert (ws / ".mam" / "shim" / "herdr").is_file(), "the call's own root got lib.sh's shim"