Files
multi-agent-mux/tests/test_issue45_restore_unit.py
GodopuandClaude Opus 5.5 f19f94099f fix(issue-17): resume closes herdr 0.9.3's placeholder for a removed directory
With the cwd premise fixed, test_b still failed on herdr 0.9.3: a pane whose
saved directory is gone is restored as a placeholder - `restore_error`
"Saved directory is unavailable...", no process - and `pane process-info`
answers pane_not_found. close_restored_shells raised on it and exited 1
before looking at the other panes, so the resumed session's removed-worktree
pane (the case #45 closes on herdr 0.9.1, where it is a shell in $HOME) was
left behind.

Such a pane has nothing running, so it now meets condition 4. Only that
exact answer counts - restore_error set AND pane_not_found; any other
process-info failure still stops the run with exit 1 as before.

Tests: test_issue45_restore.py passes on herdr 0.9.3 (5/5). Unit tests with a
stub herdr: the placeholder is closed (fails at 56d0bfd); pane_not_found
without restore_error, or restore_error with another error, still raises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-08 07:53:58 +09:00

99 lines
4.3 KiB
Python

"""Issue #45 (B), review round 2 R2: resume closes a restored pane only when
herdr's answer proves the shell is in the foreground. Odd pid values - missing,
null, 0, negative, bool, string - must leave the pane alone. herdr is a stub
here; the real-server behaviour is tests/test_issue45_restore.py."""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / ".agents" / "skills"))
from lib_py import herdr_restore as hr # noqa: E402
SID = "probe-id"
def _stub(monkeypatch, process_info):
calls = []
def fake(herdr, session, *args):
calls.append(args)
if args[:2] == ("pane", "list"):
return {"panes": [{"pane_id": "probe-pane", "agent": None, "agent_session": {"value": SID}}]}
if args[:2] == ("pane", "process-info"):
return {"process_info": process_info}
return {}
monkeypatch.setattr(hr, "_herdr", fake)
return calls
@pytest.mark.parametrize("shell,fg", [(0, 0), (False, False), ("", ""), (-1, -1), (None, None),
("123", "123"), (True, True), (1.0, 1.0)],
ids=["zero", "false", "empty", "negative", "null", "string", "true", "float"])
def test_an_odd_pid_answer_leaves_the_pane_alone(monkeypatch, shell, fg):
calls = _stub(monkeypatch, {"shell_pid": shell, "foreground_process_group_id": fg})
assert hr.close_restored_shells(SID, session="m45-review-probe") == []
assert ("pane", "close", "probe-pane") not in calls
def test_a_missing_process_info_leaves_the_pane_alone(monkeypatch):
calls = _stub(monkeypatch, {})
assert hr.close_restored_shells(SID, session="m45-review-probe") == []
assert ("pane", "close", "probe-pane") not in calls
def test_a_real_shell_in_the_foreground_is_closed(monkeypatch):
calls = _stub(monkeypatch, {"shell_pid": 4242, "foreground_process_group_id": 4242})
assert hr.close_restored_shells(SID, session="m45-review-probe") == ["probe-pane"]
assert ("pane", "close", "probe-pane") in calls
def test_a_busy_pane_is_left_alone(monkeypatch):
calls = _stub(monkeypatch, {"shell_pid": 4242, "foreground_process_group_id": 4300})
assert hr.close_restored_shells(SID, session="m45-review-probe") == []
assert ("pane", "close", "probe-pane") not in calls
# herdr 0.9.3: a pane whose saved directory is gone is restored as a placeholder
# with no process; process-info answers pane_not_found (real-server check:
# test_issue45_restore.py test_b_only_the_resumed_sessions_restored_shell_is_closed).
PLACEHOLDER_ERR = '{"error":{"code":"pane_not_found","message":"pane not found"},"id":"cli:pane:process_info"}'
def _stub_failing(monkeypatch, restore_error, stderr):
calls = []
def fake(herdr, session, *args):
calls.append(args)
if args[:2] == ("pane", "list"):
pane = {"pane_id": "probe-pane", "agent": None, "agent_session": {"value": SID}}
if restore_error:
pane["restore_error"] = restore_error
return {"panes": [pane]}
if args[:2] == ("pane", "process-info"):
raise subprocess.CalledProcessError(1, ["herdr", *args], output="", stderr=stderr)
return {}
monkeypatch.setattr(hr, "_herdr", fake)
return calls
def test_a_placeholder_for_a_missing_directory_is_closed(monkeypatch):
calls = _stub_failing(monkeypatch, "Saved directory is unavailable. Restore the directory and restart "
"this session.", PLACEHOLDER_ERR)
assert hr.close_restored_shells(SID, session="m45-review-probe") == ["probe-pane"]
assert ("pane", "close", "probe-pane") in calls
@pytest.mark.parametrize("restore_error,stderr", [
(None, PLACEHOLDER_ERR), # pane_not_found without herdr's marker
("Saved directory is unavailable.", '{"error":{"code":"server_error"}}'), # another failure
], ids=["no-restore-error", "other-error"])
def test_any_other_process_info_failure_still_stops_the_run(monkeypatch, restore_error, stderr):
calls = _stub_failing(monkeypatch, restore_error, stderr)
with pytest.raises(subprocess.CalledProcessError):
hr.close_restored_shells(SID, session="m45-review-probe")
assert ("pane", "close", "probe-pane") not in calls