Some checks failed
tamq-ci / test (push) Has been cancelled
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
335 lines
11 KiB
Python
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,
|
|
)
|