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
158 lines
5.4 KiB
Python
158 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""CLI parser and planner-resolution tests for multi-agent-mux-loop (Rev.4)."""
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
import pytest
|
|
|
|
# Issue #17: shells started here inherit a WORKSPACE_ROOT of this test's own
|
|
# (lib.sh would otherwise rewrite the checkout's .mam/shim/herdr).
|
|
pytestmark = pytest.mark.usefixtures("own_workspace_root")
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
RUN_LOOP_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-loop" / "scripts" / "run_loop.sh"
|
|
SKILL_MD = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-loop" / "SKILL.md"
|
|
|
|
|
|
def _run_loop(args, env_extra=None, cwd=None):
|
|
env = dict(os.environ)
|
|
if env_extra:
|
|
env.update(env_extra)
|
|
return subprocess.run(
|
|
["bash", str(RUN_LOOP_SH), *args],
|
|
capture_output=True,
|
|
text=True,
|
|
env=env,
|
|
cwd=str(cwd or REPO_ROOT),
|
|
)
|
|
|
|
|
|
def _combined(res):
|
|
return (res.stdout or "") + (res.stderr or "")
|
|
|
|
|
|
def test_help_lists_creator_not_target_agent():
|
|
res = _run_loop(["--help"])
|
|
assert res.returncode != 0
|
|
text = _combined(res)
|
|
assert "--creator" in text
|
|
assert "--planner" in text
|
|
assert "--target-agent" not in text
|
|
|
|
|
|
def test_missing_creator_and_task_fail_fast():
|
|
res = _run_loop([])
|
|
assert res.returncode != 0
|
|
assert "ERROR: --creator and --task are mandatory fields." in _combined(res)
|
|
|
|
|
|
def test_creator_parses_and_reaches_session_check(tmp_path):
|
|
res = _run_loop(
|
|
["--creator", "nonexistent-agent", "--task", "t"],
|
|
env_extra={"MAM_LOOP_NO_FREEZE": "1", "MAM_LOOP_MARKER": str(tmp_path / "loop-guard")},
|
|
)
|
|
assert res.returncode != 0
|
|
text = _combined(res)
|
|
assert "was removed" not in text
|
|
assert "mandatory fields" not in text
|
|
assert "not registered" in text or "already running" in text
|
|
|
|
|
|
def test_target_agent_is_rejected():
|
|
res = _run_loop(["--target-agent", "any-session", "--task", "t"])
|
|
assert res.returncode == 1
|
|
text = _combined(res)
|
|
assert "ERROR: --target-agent was removed. Use --creator <session> instead." in text
|
|
assert "mandatory fields" not in text
|
|
|
|
|
|
def test_planner_without_plan_fail_fast():
|
|
res = _run_loop(["--creator", "c1", "--planner", "p1", "--task", "t"])
|
|
assert res.returncode == 1
|
|
text = _combined(res)
|
|
assert "ERROR: --planner was specified without --plan." in text
|
|
assert "--plan --planner" in text
|
|
|
|
|
|
def test_skill_md_uses_creator_flag():
|
|
content = SKILL_MD.read_text(encoding="utf-8")
|
|
assert "--creator" in content
|
|
assert "--planner" in content
|
|
assert "--target-agent" not in content
|
|
|
|
|
|
def _seed_sessions(yaml_path, sessions):
|
|
lines = ["herdr_sessions:"]
|
|
for s in sessions:
|
|
lines.append(f"- name: {s['name']}")
|
|
lines.append(f" status: {s['status']}")
|
|
lines.append(f" role: {s['role']}")
|
|
yaml_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
db_path = yaml_path.with_suffix(".db")
|
|
if db_path.exists():
|
|
db_path.unlink()
|
|
|
|
|
|
def _loop_env(mam_sandbox, marker_path):
|
|
return {
|
|
"MAM_LOOP_NO_FREEZE": "1",
|
|
"MAM_LOOP_MARKER": str(marker_path),
|
|
"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"),
|
|
"WORKSPACE_ROOT": str(mam_sandbox),
|
|
"MAM_REAL_ROOT": str(mam_sandbox),
|
|
}
|
|
|
|
|
|
def test_explicit_planner_unregistered(mam_sandbox, tmp_path):
|
|
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
|
_seed_sessions(yaml_path, [{"name": "creator-1", "status": "running", "role": "creator"}])
|
|
marker = tmp_path / "loop-guard-unreg"
|
|
res = _run_loop(
|
|
["--creator", "creator-1", "--plan", "--planner", "missing-planner", "--task", "t"],
|
|
env_extra=_loop_env(mam_sandbox, marker),
|
|
cwd=mam_sandbox,
|
|
)
|
|
assert res.returncode != 0
|
|
assert "Specified planner session 'missing-planner' is not registered in the session registry." in _combined(res)
|
|
|
|
|
|
def test_explicit_planner_not_running(mam_sandbox, tmp_path):
|
|
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
|
_seed_sessions(
|
|
yaml_path,
|
|
[
|
|
{"name": "creator-1", "status": "running", "role": "creator"},
|
|
{"name": "planner-stopped", "status": "stopped", "role": "planner"},
|
|
],
|
|
)
|
|
marker = tmp_path / "loop-guard-stopped"
|
|
res = _run_loop(
|
|
["--creator", "creator-1", "--plan", "--planner", "planner-stopped", "--task", "t"],
|
|
env_extra=_loop_env(mam_sandbox, marker),
|
|
cwd=mam_sandbox,
|
|
)
|
|
assert res.returncode != 0
|
|
assert "Specified planner session 'planner-stopped' is not running (current status: 'stopped')." in _combined(res)
|
|
|
|
|
|
def test_explicit_planner_skips_auto_resolve(mam_sandbox, tmp_path):
|
|
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
|
_seed_sessions(
|
|
yaml_path,
|
|
[
|
|
{"name": "creator-1", "status": "running", "role": "creator"},
|
|
{"name": "planner-first", "status": "running", "role": "planner"},
|
|
{"name": "planner-explicit", "status": "running", "role": "planner"},
|
|
],
|
|
)
|
|
marker = tmp_path / "loop-guard-skip"
|
|
res = _run_loop(
|
|
["--creator", "creator-1", "--plan", "--planner", "planner-explicit", "--task", "t"],
|
|
env_extra=_loop_env(mam_sandbox, marker),
|
|
cwd=mam_sandbox,
|
|
)
|
|
text = _combined(res)
|
|
assert "Resolved Planner session: planner-explicit" in text
|
|
assert "Resolved Planner session: planner-first" not in text
|