Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
3075c63e01
commit
82cfe3da5a
10 changed files with 660 additions and 4 deletions
187
tests/test_cleanup.py
Normal file
187
tests/test_cleanup.py
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
import socket
|
||||
|
||||
from tamq.cleanup import SHIM_MARKER, cleanup_runtime
|
||||
from tamq.cli import main
|
||||
from tamq.store import Store
|
||||
|
||||
|
||||
class FakeManager:
|
||||
def __init__(self, command_dir: Path):
|
||||
self.command_dir = command_dir
|
||||
self.identity = (42, "tmux-amq-42-test")
|
||||
self.commands = []
|
||||
|
||||
def _existing_session_identity(self):
|
||||
return self.identity
|
||||
|
||||
def _run(self, *args, check=True):
|
||||
self.commands.append(args)
|
||||
if args[0] == "show-options":
|
||||
return str(self.command_dir)
|
||||
if args[0] == "kill-session":
|
||||
self.identity = None
|
||||
return ""
|
||||
|
||||
|
||||
def configure_paths(tmp_path, monkeypatch):
|
||||
state = tmp_path / "state"
|
||||
runtime = tmp_path / "runtime"
|
||||
runtime.mkdir()
|
||||
monkeypatch.setenv("TAMQ_STATE_DIR", str(state))
|
||||
monkeypatch.setenv("XDG_RUNTIME_DIR", str(runtime))
|
||||
monkeypatch.setenv("TAMQ_SOCKET", str(runtime / "tamq.sock"))
|
||||
monkeypatch.setenv("TMUX_TMPDIR", str(tmp_path / "tmux"))
|
||||
return state, runtime
|
||||
|
||||
|
||||
def test_cleanup_is_dry_run_then_removes_only_owned_runtime(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
state, runtime = configure_paths(tmp_path, monkeypatch)
|
||||
command_dir = state / "commands" / "tamq"
|
||||
command_dir.mkdir(parents=True)
|
||||
generated = command_dir / "@audit-core"
|
||||
generated.write_text(f"#!/bin/sh\n{SHIM_MARKER}\n", encoding="utf-8")
|
||||
unrelated = command_dir / "keep-me"
|
||||
unrelated.write_text("operator file\n", encoding="utf-8")
|
||||
for name in ("tamq.sock", "tamq.pid", "tamq.lock"):
|
||||
(runtime / name).write_text("stale\n", encoding="utf-8")
|
||||
tmux_socket_dir = tmp_path / "tmux" / f"tmux-{os.getuid()}"
|
||||
tmux_socket_dir.mkdir(parents=True)
|
||||
stale_tmux_socket = tmux_socket_dir / "tamq-old-test"
|
||||
socket_handle = socket.socket(socket.AF_UNIX)
|
||||
socket_handle.bind(str(stale_tmux_socket))
|
||||
socket_handle.close()
|
||||
live_tmux_socket = tmux_socket_dir / "tamq-live-test"
|
||||
live_socket_handle = socket.socket(socket.AF_UNIX)
|
||||
live_socket_handle.bind(str(live_tmux_socket))
|
||||
live_socket_handle.listen()
|
||||
|
||||
store = Store(state / "tamq.sqlite3")
|
||||
message_id = store.add("a", "b", "preserve me")
|
||||
store.register_endpoint("ep", 42, "tamq", ["a", "b"])
|
||||
assert store.claim(message_id, "ep") is not None
|
||||
store.close()
|
||||
manager = FakeManager(command_dir)
|
||||
|
||||
async def not_running():
|
||||
return False
|
||||
|
||||
monkeypatch.setattr("tamq.cleanup.ping", not_running)
|
||||
stopped = []
|
||||
dry_run = cleanup_runtime(
|
||||
apply=False,
|
||||
manager=manager,
|
||||
stop_service=lambda: stopped.append(True),
|
||||
)
|
||||
assert dry_run["managed_session"] is True
|
||||
assert dry_run["active_endpoints"] == 1
|
||||
assert dry_run["leases"] == 1
|
||||
assert dry_run["history_preserved"] == 1
|
||||
assert dry_run["stale_tmux_socket_count"] == 1
|
||||
assert generated.exists()
|
||||
assert (runtime / "tamq.sock").exists()
|
||||
assert stale_tmux_socket.exists()
|
||||
assert manager.identity is not None
|
||||
|
||||
applied = cleanup_runtime(
|
||||
apply=True,
|
||||
manager=manager,
|
||||
stop_service=lambda: stopped.append(True),
|
||||
)
|
||||
assert applied["errors"] == []
|
||||
assert stopped == []
|
||||
assert manager.identity is None
|
||||
assert not generated.exists()
|
||||
assert unrelated.exists()
|
||||
assert not any((runtime / name).exists() for name in ("tamq.sock", "tamq.pid", "tamq.lock"))
|
||||
assert not stale_tmux_socket.exists()
|
||||
assert live_tmux_socket.exists()
|
||||
reopened = Store(state / "tamq.sqlite3")
|
||||
assert len(reopened.list()) == 1
|
||||
assert reopened.endpoints() == []
|
||||
assert reopened.db.execute("SELECT COUNT(*) FROM leases").fetchone()[0] == 0
|
||||
reopened.close()
|
||||
|
||||
repeated = cleanup_runtime(
|
||||
apply=True,
|
||||
manager=manager,
|
||||
stop_service=lambda: stopped.append(True),
|
||||
)
|
||||
assert repeated["errors"] == []
|
||||
assert unrelated.exists()
|
||||
live_socket_handle.close()
|
||||
live_tmux_socket.unlink()
|
||||
|
||||
|
||||
def test_cleanup_refuses_unverified_running_service(tmp_path, monkeypatch):
|
||||
state, runtime = configure_paths(tmp_path, monkeypatch)
|
||||
(runtime / "tamq.pid").write_text("41", encoding="utf-8")
|
||||
manager = FakeManager(state / "commands" / "tamq")
|
||||
|
||||
async def running():
|
||||
return True
|
||||
|
||||
async def request(_payload):
|
||||
return {"ok": True, "pid": 42}
|
||||
|
||||
monkeypatch.setattr("tamq.cleanup.ping", running)
|
||||
monkeypatch.setattr("tamq.cleanup.request", request)
|
||||
stopped = []
|
||||
report = cleanup_runtime(
|
||||
apply=True,
|
||||
manager=manager,
|
||||
stop_service=lambda: stopped.append(True),
|
||||
)
|
||||
|
||||
assert report["errors"] == ["refusing to stop an unverified service PID"]
|
||||
assert stopped == []
|
||||
assert manager.identity is not None
|
||||
|
||||
|
||||
def test_cleanup_stops_verified_service_before_removing_runtime(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
state, runtime = configure_paths(tmp_path, monkeypatch)
|
||||
(runtime / "tamq.pid").write_text("42", encoding="utf-8")
|
||||
manager = FakeManager(state / "commands" / "tamq")
|
||||
liveness = iter((True, False))
|
||||
|
||||
async def ping():
|
||||
return next(liveness)
|
||||
|
||||
async def request(_payload):
|
||||
return {"ok": True, "pid": 42}
|
||||
|
||||
monkeypatch.setattr("tamq.cleanup.ping", ping)
|
||||
monkeypatch.setattr("tamq.cleanup.request", request)
|
||||
stopped = []
|
||||
report = cleanup_runtime(
|
||||
apply=True,
|
||||
manager=manager,
|
||||
stop_service=lambda: stopped.append(42) or 42,
|
||||
)
|
||||
|
||||
assert report["errors"] == []
|
||||
assert stopped == [42]
|
||||
assert manager.identity is None
|
||||
assert not (runtime / "tamq.pid").exists()
|
||||
|
||||
|
||||
def test_cleanup_cli_is_dry_run_by_default(monkeypatch, capsys):
|
||||
calls = []
|
||||
|
||||
def cleanup_runtime(**kwargs):
|
||||
calls.append(kwargs)
|
||||
return {"errors": [], "apply": kwargs["apply"]}
|
||||
|
||||
monkeypatch.setattr("tamq.cli.cleanup_runtime", cleanup_runtime)
|
||||
assert main(["cleanup", "--session", "test-session"]) == 0
|
||||
assert calls[0]["apply"] is False
|
||||
assert calls[0]["session"] == "test-session"
|
||||
assert '"apply": false' in capsys.readouterr().out
|
||||
|
||||
assert main(["cleanup", "--yes"]) == 0
|
||||
assert calls[1]["apply"] is True
|
||||
|
|
@ -27,3 +27,26 @@ def test_purge_confirmed_deletes(tmp_path, monkeypatch, capsys):
|
|||
assert main(["purge", "--yes"]) == 0
|
||||
assert "Purged" in capsys.readouterr().out
|
||||
assert len(Store(state / "tamq.sqlite3").list()) == 0
|
||||
|
||||
|
||||
def test_feedback_chain_purge_selects_only_reflected_direction(
|
||||
tmp_path, monkeypatch, capsys
|
||||
):
|
||||
state = tmp_path / "state"
|
||||
monkeypatch.setenv("TAMQ_STATE_DIR", str(state))
|
||||
store = Store(state / "tamq.sqlite3")
|
||||
root = store.add("a", "b", "body")
|
||||
child = store.add("b", "a", f"body [{root}]")
|
||||
grandchild = store.add("a", "b", f"body [{root}] [{child}]")
|
||||
unrelated = store.add("a", "b", f"not reflected [{root}]")
|
||||
store.close()
|
||||
|
||||
args = ["purge", "--feedback-chain", root]
|
||||
assert main(args) == 0
|
||||
assert "Dry run: 3 message(s)" in capsys.readouterr().out
|
||||
assert len(Store(state / "tamq.sqlite3").list()) == 4
|
||||
|
||||
assert main([*args, "--yes"]) == 0
|
||||
assert "Purged 3" in capsys.readouterr().out
|
||||
remaining = Store(state / "tamq.sqlite3").list()
|
||||
assert [row["message_id"] for row in remaining] == [unrelated]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue