tmux-amq/tests/test_install_target.py
tegwick 9ed8b62b45
Some checks failed
tamq-ci / test (push) Has been cancelled
feat: add experimental pushy delivery mode
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
2026-08-24 23:41:09 +02:00

335 lines
11 KiB
Python

import json
import os
import shutil
import subprocess
import time
from pathlib import Path
import pytest
def test_make_install_uses_isolated_uv_tool_install():
makefile = Path("Makefile").read_text(encoding="utf-8")
assert "install:" in makefile
assert "uv tool install --force --reinstall --refresh ." in makefile
assert "tamq --version" in makefile
assert "uninstall:" in makefile
assert "uv tool uninstall tmux-amq" in makefile
@pytest.mark.skipif(
shutil.which("uv") is None or shutil.which("tmux") is None,
reason="installed-package smoke requires uv and tmux",
)
def test_isolated_installed_tool_session_smoke(tmp_path):
project = Path(__file__).resolve().parents[1]
tool_dir = tmp_path / "tools"
bin_dir = tmp_path / "bin"
runtime_dir = tmp_path / "runtime"
state_dir = tmp_path / "state"
repo_a = tmp_path / "railiance-platform"
repo_b = tmp_path / "activity-core"
for path in (bin_dir, runtime_dir, repo_a, repo_b):
path.mkdir(parents=True)
gita = bin_dir / "gita"
gita.write_text(
"#!/usr/bin/env python3\n"
"import sys\n"
f"paths = {{'railiance-platform': {str(repo_a)!r}, 'activity-core': {str(repo_b)!r}}}\n"
"if sys.argv[1:] == ['ls']:\n"
" print('railiance-platform activity-core')\n"
"elif sys.argv[1:] == ['freeze']:\n"
" for slug, path in paths.items(): print(f'local,{slug},{path}')\n"
"else:\n"
" raise SystemExit(2)\n",
encoding="utf-8",
)
agent = bin_dir / "alpha-agent"
agent.write_text(
"#!/bin/sh\nprintf 'installed-alpha-ready\\n'\nexec sleep 30\n",
encoding="utf-8",
)
gita.chmod(0o755)
agent.chmod(0o755)
socket_name = f"tamq-installed-{os.getpid()}"
env = os.environ.copy()
env.update(
{
"PATH": f"{bin_dir}:{env['PATH']}",
"UV_TOOL_DIR": str(tool_dir),
"UV_TOOL_BIN_DIR": str(bin_dir),
"UV_CACHE_DIR": str(tmp_path / "uv-cache"),
"XDG_RUNTIME_DIR": str(runtime_dir),
"TAMQ_STATE_DIR": str(state_dir),
"TAMQ_TMUX_SOCKET": socket_name,
}
)
tamq = bin_dir / "tamq"
tmux = ["tmux", "-L", socket_name]
def run(*args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
args,
cwd=tmp_path,
env=env,
text=True,
capture_output=True,
check=True,
timeout=30,
)
try:
subprocess.run(
["uv", "tool", "install", "--force", "--reinstall", "--refresh", str(project)],
cwd=project,
env=env,
text=True,
capture_output=True,
check=True,
timeout=60,
)
assert run(str(tamq), "--version").stdout.strip() == "0.1.0"
root_help = run(str(tamq), "--help").stdout
assert "tamq [--detach] [--command COMMAND] [--mode MODE] REPO" in root_help
started = json.loads(
run(
str(tamq),
"railiance-platform",
"activity-core",
"--detach",
).stdout
)
assert started["repos"] == ["railiance-platform", "activity-core"]
assert started["delivery_mode"] == "output"
rows = run(
*tmux,
"list-windows",
"-t",
"tamq",
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}|#{pane_current_command}",
).stdout.splitlines()
parsed = {
name: (path, pid, command)
for name, path, pid, command in (row.split("|", 3) for row in rows)
}
assert parsed["railiance-platform"][0] == str(repo_a)
assert parsed["activity-core"][0] == str(repo_b)
assert all(command != "alpha-agent" for _, _, command in parsed.values())
assert all(
"installed-alpha-ready"
not in run(*tmux, "capture-pane", "-p", "-t", f"tamq:{repo}").stdout
for repo in ("railiance-platform", "activity-core")
)
for repo in ("railiance-platform", "activity-core"):
run(
*tmux,
"send-keys",
"-t",
f"tamq:{repo}",
"printf 'TAMQ_REPO=%s\\n' \"$TAMQ_REPO\"",
"C-m",
)
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
identity_captures = {
repo: run(*tmux, "capture-pane", "-p", "-t", f"tamq:{repo}").stdout
for repo in ("railiance-platform", "activity-core")
}
if all(f"TAMQ_REPO={repo}" in output for repo, output in identity_captures.items()):
break
time.sleep(0.05)
assert all(f"TAMQ_REPO={repo}" in output for repo, output in identity_captures.items())
repeated = json.loads(
run(
str(tamq),
"start",
"--detach",
"railiance-platform",
"activity-core",
).stdout
)
assert repeated["instance_id"] == started["instance_id"]
repeated_rows = run(
*tmux,
"list-windows",
"-t",
"tamq",
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}|#{pane_current_command}",
).stdout.splitlines()
assert repeated_rows == rows
pre_delivery_status = json.loads(run(str(tamq), "status").stdout)
assert pre_delivery_status["service"] is True
assert [item["endpoint_id"] for item in pre_delivery_status["endpoints"]] == [
started["instance_id"]
]
assert pre_delivery_status["endpoints"][0]["delivery_mode"] == "output"
target_before = run(
*tmux, "capture-pane", "-p", "-t", "tamq:activity-core"
).stdout
run(
*tmux,
"send-keys",
"-t",
"tamq:railiance-platform",
"@activity-core: installed-message",
"C-m",
)
deadline = time.monotonic() + 5
inbox = []
target_after = target_before
while time.monotonic() < deadline:
inbox = [
json.loads(line)
for line in run(
str(tamq), "inbox", "--repo", "activity-core", "--json"
).stdout.splitlines()
]
target_after = run(
*tmux, "capture-pane", "-p", "-t", "tamq:activity-core"
).stdout
if inbox and inbox[-1]["displayed_at"] is not None and "#railiance-platform: installed-message" in target_after:
break
time.sleep(0.05)
assert inbox, run(
*tmux, "capture-pane", "-p", "-t", "tamq:railiance-platform"
).stdout
assert inbox[-1]["sender_repo"] == "railiance-platform"
assert inbox[-1]["body"] == "installed-message"
assert inbox[-1]["state"] == "pending"
assert inbox[-1]["displayed_at"] is not None
message_id = inbox[-1]["message_id"]
assert run(str(tamq), "inbox", "--repo", "activity-core").stdout == (
f"#railiance-platform: installed-message [{message_id}]\n"
)
assert target_after != target_before
assert f"#railiance-platform: installed-message [{message_id}]" in target_after
assert run(str(tamq), "ack", message_id).stdout.strip() == message_id
assert run(str(tamq), "inbox", "--repo", "activity-core").stdout == ""
all_messages = [
json.loads(line)
for line in run(
str(tamq), "inbox", "--repo", "activity-core", "--all", "--json"
).stdout.splitlines()
]
assert all_messages[-1]["state"] == "acknowledged"
run(
*tmux,
"send-keys",
"-t",
"tamq:railiance-platform",
"@activity-core filtered-message",
"C-m",
)
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
pending = run(
str(tamq), "inbox", "--repo", "activity-core", "--json"
).stdout
if "filtered-message" in pending:
break
time.sleep(0.05)
message_log = tmp_path / "msg.log"
run(
str(tamq), "inbox", "--repo", "activity-core", "--filter",
f"cat >> {message_log}",
)
assert "#railiance-platform: filtered-message [m-" in message_log.read_text(
encoding="utf-8"
)
assert run(str(tamq), "inbox", "--repo", "activity-core").stdout == ""
status = json.loads(run(str(tamq), "status").stdout)
assert status["service"] is True
assert [item["endpoint_id"] for item in status["endpoints"]] == [started["instance_id"]]
assert run(str(tamq), "stop").returncode == 0
assert json.loads(run(str(tamq), "status").stdout)["service"] is False
restarted = json.loads(
run(
str(tamq),
"start",
"--detach",
"railiance-platform",
"activity-core",
).stdout
)
assert restarted["instance_id"] == started["instance_id"]
assert run(
*tmux,
"list-windows",
"-t",
"tamq",
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}|#{pane_current_command}",
).stdout.splitlines() == rows
run(*tmux, "kill-session", "-t", "tamq")
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
status = json.loads(run(str(tamq), "status").stdout)
if status["endpoints"] == []:
break
time.sleep(0.05)
assert status["endpoints"] == []
recovered = json.loads(
run(
str(tamq),
"start",
"--detach",
"--command",
"alpha-agent",
"railiance-platform",
"activity-core",
).stdout
)
assert recovered["instance_id"] != started["instance_id"]
assert recovered["delivery_mode"] == "output"
recovered_rows = run(
*tmux,
"list-windows",
"-t",
"tamq",
"-F",
"#{window_name}|#{pane_current_path}",
).stdout.splitlines()
assert recovered_rows == [
f"railiance-platform|{repo_a}",
f"activity-core|{repo_b}",
]
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
captures = [
run(*tmux, "capture-pane", "-p", "-t", f"tamq:{repo}").stdout
for repo in ("railiance-platform", "activity-core")
]
if all("installed-alpha-ready" in output for output in captures):
break
time.sleep(0.05)
assert all("installed-alpha-ready" in output for output in captures)
assert run(str(tamq), "stop").returncode == 0
finally:
subprocess.run(
[str(tamq), "stop"],
cwd=tmp_path,
env=env,
text=True,
capture_output=True,
check=False,
timeout=5,
)
subprocess.run(
[*tmux, "kill-server"],
env=env,
text=True,
capture_output=True,
check=False,
)