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" started = json.loads( run( str(tamq), "start", "--detach", "--command", "alpha-agent", "railiance-platform", "activity-core", ).stdout ) assert started["repos"] == ["railiance-platform", "activity-core"] rows = run( *tmux, "list-windows", "-t", "tamq", "-F", "#{window_name}|#{pane_current_path}|#{pane_pid}", ).stdout.splitlines() parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in rows)} assert parsed["railiance-platform"][0] == str(repo_a) assert parsed["activity-core"][0] == str(repo_b) repeated = json.loads( run( str(tamq), "start", "--detach", "--command", "alpha-agent", "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}", ).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"] ] message_id = run( str(tamq), "send", "@activity-core:", "installed-message" ).stdout.strip() deadline = time.monotonic() + 5 history = [] while time.monotonic() < deadline: history = [ json.loads(line) for line in run(str(tamq), "history", "--repo", "activity-core").stdout.splitlines() ] if history and history[-1]["state"] == "injected": break time.sleep(0.05) assert history[-1]["message_id"] == message_id assert history[-1]["state"] == "injected" 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", "--command", "alpha-agent", "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}", ).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"] 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}", ] 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, )