d198dd5fixed one helper; the same defect was in eleven more modules (reviews grok and codex R1/R2): their shells inherited the process environment, so each `source lib.sh` rewrote <WORKSPACE_ROOT>/.mam/shim/herdr - the checkout's own when the variable is unset or names this checkout (which pytest_configure keeps), another workspace's under --noconftest. - conftest: own_workspace_root, an opt-in fixture setting WORKSPACE_ROOT to <tmp_path>/.mam-test-state/workspace. Not autouse: runner.py and mqtt_common read WORKSPACE_ROOT in-process, and some tests unset it on purpose. mam_sandbox and explicit envs run later and still win. - pytestmark usefixtures("own_workspace_root") in test_version_consistency, test_b19_headless_reconcile_fixes, test_b8_send_keys_verification, test_issue6_fail_closed, test_loop_cli, test_issue5_phase3_loop, test_issue5_phase4_runtime, test_o3_scoped_guard, test_issue25_plan_reuse, test_orc_onboard (their shells inherit os.environ, many with no env at all). - test_orc_onboard (R2): SCRUBBED_ENV is taken at import and no longer carries WORKSPACE_ROOT; every call builds _scrubbed_env(root) with the root of the call (ws / cwd / mam_sandbox, else this test's). - test_workspace_scope (unittest) and test_issue21_worktree_prereqs._mutate pin the root they already create. Tests (test_issue17_workspace_root_isolation.py): W1 copies the checkout and runs one test of each of those modules there with WORKSPACE_ROOT naming the copy - the copy gets no .mam; W2 imports test_orc_onboard under a sentinel root, then calls it under another - the sentinel stays empty. Atd198dd5both fail (the copy gets .mam/shim/herdr; the sentinel gets it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
50 lines
2.5 KiB
Python
50 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
tests/test_workspace_scope.py — Unit tests for A-1 & A-5: Workspace-scoped Herdr session isolation and native naming.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import subprocess
|
|
import unittest
|
|
|
|
class TestWorkspaceScope(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp_dir = tempfile.mkdtemp(prefix="mam_scope_test_")
|
|
self.ws_dir = os.path.join(self.tmp_dir, "my_project")
|
|
self.mam_dir = os.path.join(self.ws_dir, ".mam")
|
|
os.makedirs(self.mam_dir, exist_ok=True)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
|
|
def test_derived_herdr_session_name(self):
|
|
"""Verify that resolve_herdr_session derives 'mam-<slug>' per workspace."""
|
|
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
lib_sh = os.path.join(repo_root, ".agents", "skills", "lib.sh")
|
|
|
|
cmd = f"source {lib_sh} && env -u HERDR_SESSION_NAME -u HERDR_SERVER_NAME bash -c 'source {lib_sh} && resolve_herdr_session \"test-session\" \"{self.ws_dir}\"'"
|
|
# `source` is a bash builtin; on hosts where /bin/sh is dash `shell=True`
|
|
# fails with "source: not found" (Issue #28), so run the string under bash.
|
|
# Issue #17: lib.sh rewrites $WORKSPACE_ROOT/.mam/shim/herdr - this test's own, not an inherited one
|
|
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True,
|
|
env=dict(os.environ, WORKSPACE_ROOT=self.ws_dir))
|
|
self.assertEqual(res.returncode, 0, f"Command failed: {res.stderr}")
|
|
parent = os.path.basename(os.path.dirname(os.path.abspath(self.ws_dir))).lower().replace('_', '-')
|
|
work = os.path.basename(os.path.abspath(self.ws_dir)).lower().replace('_', '-')
|
|
expected_slug = f"mam-{parent}-{work}"
|
|
self.assertEqual(res.stdout.strip(), expected_slug)
|
|
|
|
def test_drift_b_cwd_gate(self):
|
|
"""Verify that reconcile.sh drift-B refuses to auto-register sessions with foreign cwd."""
|
|
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
reconcile_sh = os.path.join(repo_root, ".agents", "skills", "multi-agent-mux-monitor", "scripts", "reconcile.sh")
|
|
|
|
# Verify bash syntax for reconcile.sh
|
|
res = subprocess.run(["bash", "-n", reconcile_sh], capture_output=True, text=True)
|
|
self.assertEqual(res.returncode, 0, f"Syntax error in reconcile.sh: {res.stderr}")
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|