Some checks failed
tamq-ci / test (push) Failing after 7s
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
398 lines
15 KiB
Python
398 lines
15 KiB
Python
import os
|
|
import shutil
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from tamq.broker import BrokerIdentity, InputBroker
|
|
from tamq.control import ControlModeClient
|
|
from tamq.service import Service
|
|
from tamq.store import Store
|
|
from tamq.terminal import terminal_frame, write_terminal_output
|
|
from tamq.tmux import LaunchPlan, TmuxManager
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
|
|
def test_real_tmux_starts_two_repo_windows_and_reuses_them(tmp_path, monkeypatch):
|
|
repo_a = tmp_path / "railiance-platform"
|
|
repo_b = tmp_path / "activity-core"
|
|
repo_a.mkdir()
|
|
repo_b.mkdir()
|
|
project_src = str(Path(__file__).resolve().parents[1] / "src")
|
|
existing_pythonpath = os.environ.get("PYTHONPATH")
|
|
monkeypatch.setenv("PYTHONPATH", project_src if not existing_pythonpath else f"{project_src}:{existing_pythonpath}")
|
|
monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state"))
|
|
|
|
socket_name = f"tamq-pytest-{os.getpid()}-{uuid4().hex[:8]}"
|
|
session = f"tamq-test-{uuid4().hex[:8]}"
|
|
manager = TmuxManager(
|
|
session,
|
|
tmux_command=("tmux", "-L", socket_name),
|
|
tamq_command=(sys.executable, "-m", "tamq.cli"),
|
|
command_dir=tmp_path / "commands",
|
|
)
|
|
plan = LaunchPlan(
|
|
("railiance-platform", "activity-core"),
|
|
{"railiance-platform": str(repo_a), "activity-core": str(repo_b)},
|
|
("sh", "-c", "printf 'alpha-ready\\n'; exec sleep 30"),
|
|
)
|
|
|
|
try:
|
|
endpoint = manager.ensure_plan(plan, tap=True)
|
|
assert endpoint.created_session is True
|
|
assert endpoint.created_windows == ("railiance-platform", "activity-core")
|
|
|
|
rows = manager._run(
|
|
"list-windows",
|
|
"-t",
|
|
session,
|
|
"-F",
|
|
"#{window_name}|#{pane_current_path}|#{pane_pid}",
|
|
).splitlines()
|
|
parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in rows)}
|
|
assert set(parsed) == {"railiance-platform", "activity-core"}
|
|
assert parsed["railiance-platform"][0] == str(repo_a)
|
|
assert parsed["activity-core"][0] == str(repo_b)
|
|
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
captures = [
|
|
manager._run("capture-pane", "-p", "-t", f"{session}:{repo}")
|
|
for repo in plan.repos
|
|
]
|
|
if all("alpha-ready" in capture for capture in captures):
|
|
break
|
|
time.sleep(0.05)
|
|
assert all("alpha-ready" in capture for capture in captures)
|
|
|
|
reused = manager.ensure_plan(plan, tap=True)
|
|
assert reused.created_session is False
|
|
assert reused.created_windows == ()
|
|
reused_rows = manager._run(
|
|
"list-windows",
|
|
"-t",
|
|
session,
|
|
"-F",
|
|
"#{window_name}|#{pane_current_path}|#{pane_pid}",
|
|
).splitlines()
|
|
reused_parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in reused_rows)}
|
|
assert reused_parsed == parsed
|
|
assert reused.instance_id == endpoint.instance_id
|
|
|
|
store = Store(tmp_path / "state" / "tamq.sqlite3")
|
|
control = ControlModeClient(
|
|
session,
|
|
tmux_command=("tmux", "-L", socket_name),
|
|
)
|
|
try:
|
|
message_id = store.add("local", "activity-core", "integration-message")
|
|
control.start()
|
|
delivered = InputBroker(
|
|
store,
|
|
BrokerIdentity(endpoint.instance_id, "railiance-platform"),
|
|
).deliver_pending(
|
|
control,
|
|
window_for_repo=lambda repo: f"{session}:{repo}",
|
|
)
|
|
assert delivered == 1
|
|
assert store.list(state="injected")[0]["message_id"] == message_id
|
|
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
capture = manager._run(
|
|
"capture-pane", "-p", "-t", f"{session}:activity-core"
|
|
)
|
|
if "#local: integration-message" in capture:
|
|
break
|
|
time.sleep(0.05)
|
|
assert "#local: integration-message" in capture
|
|
finally:
|
|
control.close()
|
|
store.close()
|
|
finally:
|
|
manager._run("kill-server", check=False)
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
|
|
def test_real_tmux_output_preserves_partial_input_line_and_cursor(tmp_path):
|
|
repo = tmp_path / "audit-core"
|
|
repo.mkdir()
|
|
socket_name = f"tamq-output-{os.getpid()}-{uuid4().hex[:8]}"
|
|
session = f"tamq-output-{uuid4().hex[:8]}"
|
|
manager = TmuxManager(
|
|
session,
|
|
tmux_command=("tmux", "-L", socket_name),
|
|
tamq_command=(sys.executable, "-m", "tamq.cli"),
|
|
command_dir=tmp_path / "commands",
|
|
)
|
|
target = f"{session}:audit-core"
|
|
plan = LaunchPlan(("audit-core",), {"audit-core": str(repo)}, ())
|
|
|
|
try:
|
|
manager.ensure_plan(plan)
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
current_command = manager._run(
|
|
"display-message", "-p", "-t", target, "#{pane_current_command}"
|
|
)
|
|
if current_command in {"bash", "sh", "zsh", "fish"}:
|
|
break
|
|
time.sleep(0.05)
|
|
assert current_command in {"bash", "sh", "zsh", "fish"}
|
|
manager._run(
|
|
"send-keys", "-t", target, "-l", "--", "printf 'line-one\\nline-two\\n'"
|
|
)
|
|
manager._run("send-keys", "-t", target, "Enter")
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
initial_capture = manager._run("capture-pane", "-p", "-t", target)
|
|
initial_lines = initial_capture.splitlines()
|
|
if "line-one" in initial_lines and "line-two" in initial_lines:
|
|
break
|
|
time.sleep(0.05)
|
|
assert "line-one" in initial_lines and "line-two" in initial_lines
|
|
manager._run("send-keys", "-t", target, "-l", "--", "PARTIAL-INPUT")
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
before_capture = manager._run("capture-pane", "-p", "-t", target)
|
|
if "PARTIAL-INPUT" in before_capture:
|
|
break
|
|
time.sleep(0.05)
|
|
assert "PARTIAL-INPUT" in before_capture
|
|
|
|
control = ControlModeClient(
|
|
session,
|
|
tmux_command=("tmux", "-L", socket_name),
|
|
)
|
|
before = control.pane_display(target)
|
|
write_terminal_output(
|
|
before.tty_path,
|
|
terminal_frame(
|
|
"flex-auth",
|
|
"stable-message",
|
|
"m-stable",
|
|
cursor_y=before.cursor_y,
|
|
pane_height=before.pane_height,
|
|
alternate_on=before.alternate_on,
|
|
),
|
|
)
|
|
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
after_capture = manager._run("capture-pane", "-p", "-t", target)
|
|
if "#flex-auth: stable-message [m-stable]" in after_capture:
|
|
break
|
|
time.sleep(0.05)
|
|
after = control.pane_display(target)
|
|
assert "#flex-auth: stable-message [m-stable]" in after_capture
|
|
before_input = next(line for line in before_capture.splitlines() if "PARTIAL-INPUT" in line)
|
|
after_lines = after_capture.splitlines()
|
|
assert any("PARTIAL-INPUT" in line for line in after_lines), repr(after_capture)
|
|
after_input_index = next(
|
|
index for index, line in enumerate(after_lines) if "PARTIAL-INPUT" in line
|
|
)
|
|
assert after_lines[after_input_index] == before_input
|
|
assert after_lines[after_input_index - 1] == "#flex-auth: stable-message [m-stable]"
|
|
assert (after.cursor_x, after.cursor_y) == (before.cursor_x, before.cursor_y)
|
|
finally:
|
|
manager._run("kill-server", check=False)
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
|
|
def test_real_tmux_pushy_mode_submits_one_shell_safe_input(tmp_path, monkeypatch):
|
|
repo = tmp_path / "audit-core"
|
|
repo.mkdir()
|
|
socket_name = f"tamq-pushy-{os.getpid()}-{uuid4().hex[:8]}"
|
|
session = f"tamq-pushy-{uuid4().hex[:8]}"
|
|
monkeypatch.setenv("TAMQ_TMUX_SOCKET", socket_name)
|
|
manager = TmuxManager(
|
|
session,
|
|
tmux_command=("tmux", "-L", socket_name),
|
|
tamq_command=(sys.executable, "-m", "tamq.cli"),
|
|
command_dir=tmp_path / "commands",
|
|
)
|
|
target = f"{session}:audit-core"
|
|
store = Store(tmp_path / "queue.sqlite3")
|
|
|
|
try:
|
|
endpoint = manager.ensure_plan(
|
|
LaunchPlan(("audit-core",), {"audit-core": str(repo)}, ())
|
|
)
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
current_command = manager._run(
|
|
"display-message", "-p", "-t", target, "#{pane_current_command}"
|
|
)
|
|
if current_command in {"bash", "sh", "zsh", "fish"}:
|
|
break
|
|
time.sleep(0.05)
|
|
assert current_command in {"bash", "sh", "zsh", "fish"}
|
|
|
|
store.register_endpoint(
|
|
endpoint.instance_key,
|
|
endpoint.pid,
|
|
endpoint.session,
|
|
endpoint.repos,
|
|
"pushy",
|
|
)
|
|
message_id = store.add("flex-auth", "audit-core", "What's next?\nsecond")
|
|
service = Service(store=store)
|
|
service._deliver_once()
|
|
|
|
expected = f"# from flex-auth: What's next?\\x0asecond [{message_id}]"
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
capture = manager._run("capture-pane", "-p", "-J", "-t", target)
|
|
if expected in capture:
|
|
break
|
|
time.sleep(0.05)
|
|
assert expected in capture
|
|
assert store.list()[0]["state"] == "injected"
|
|
assert store.db.execute("SELECT COUNT(*) FROM leases").fetchone()[0] == 0
|
|
|
|
service._deliver_once()
|
|
repeated_capture = manager._run("capture-pane", "-p", "-J", "-t", target)
|
|
assert repeated_capture.count(expected) == 1
|
|
finally:
|
|
store.close()
|
|
manager._run("kill-server", check=False)
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
|
|
def test_real_tmux_hash_route_pushes_once_without_feedback(tmp_path, monkeypatch):
|
|
repo_a = tmp_path / "railiance-platform"
|
|
repo_b = tmp_path / "activity-core"
|
|
bin_dir = tmp_path / "bin"
|
|
for path in (repo_a, repo_b, bin_dir):
|
|
path.mkdir()
|
|
gita = bin_dir / "gita"
|
|
gita.write_text(
|
|
"#!/bin/sh\n"
|
|
"if [ \"$1\" = ls ]; then printf 'railiance-platform activity-core\\n'; exit 0; fi\n"
|
|
"exit 2\n",
|
|
encoding="utf-8",
|
|
)
|
|
gita.chmod(0o755)
|
|
fixture = tmp_path / "agent_fixture.py"
|
|
fixture.write_text(
|
|
"import sys\n"
|
|
"print('AGENT-READY', flush=True)\n"
|
|
"for line in sys.stdin:\n"
|
|
" print('AGENT:' + line.rstrip('\\r\\n'), flush=True)\n",
|
|
encoding="utf-8",
|
|
)
|
|
project_src = str(Path(__file__).resolve().parents[1] / "src")
|
|
existing_pythonpath = os.environ.get("PYTHONPATH")
|
|
monkeypatch.setenv(
|
|
"PYTHONPATH",
|
|
project_src if not existing_pythonpath else f"{project_src}:{existing_pythonpath}",
|
|
)
|
|
monkeypatch.setenv("PATH", f"{bin_dir}:{os.environ['PATH']}")
|
|
monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state"))
|
|
socket_name = f"tamq-hash-{os.getpid()}-{uuid4().hex[:8]}"
|
|
session = f"tamq-hash-{uuid4().hex[:8]}"
|
|
monkeypatch.setenv("TAMQ_TMUX_SOCKET", socket_name)
|
|
manager = TmuxManager(
|
|
session,
|
|
tmux_command=("tmux", "-L", socket_name),
|
|
tamq_command=(sys.executable, "-m", "tamq.cli"),
|
|
command_dir=tmp_path / "commands",
|
|
)
|
|
plan = LaunchPlan(
|
|
("railiance-platform", "activity-core"),
|
|
{
|
|
"railiance-platform": str(repo_a),
|
|
"activity-core": str(repo_b),
|
|
},
|
|
(sys.executable, str(fixture)),
|
|
)
|
|
store = None
|
|
|
|
try:
|
|
endpoint = manager.ensure_plan(plan, tap=True)
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
captures = {
|
|
repo: manager._run("capture-pane", "-p", "-t", f"{session}:{repo}")
|
|
for repo in plan.repos
|
|
}
|
|
if all("AGENT-READY" in output for output in captures.values()):
|
|
break
|
|
time.sleep(0.05)
|
|
assert all("AGENT-READY" in output for output in captures.values())
|
|
|
|
store = Store(tmp_path / "state" / "tamq.sqlite3")
|
|
store.register_endpoint(
|
|
endpoint.instance_key,
|
|
endpoint.pid,
|
|
endpoint.session,
|
|
endpoint.repos,
|
|
"pushy",
|
|
)
|
|
manager._run(
|
|
"send-keys",
|
|
"-t",
|
|
f"{session}:railiance-platform",
|
|
"-l",
|
|
"--",
|
|
"#activity-core: Hello!",
|
|
)
|
|
manager._run("send-keys", "-t", f"{session}:railiance-platform", "Enter")
|
|
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
rows = store.list()
|
|
if rows:
|
|
break
|
|
time.sleep(0.05)
|
|
assert len(rows) == 1
|
|
row = rows[0]
|
|
assert row["sender_repo"] == "railiance-platform"
|
|
assert row["target_repo"] == "activity-core"
|
|
assert row["body"] == "Hello!"
|
|
assert row["endpoint_id"] == endpoint.instance_key
|
|
|
|
service = Service(store=store)
|
|
service._deliver_once()
|
|
expected = f"# from railiance-platform: Hello! [{row['message_id']}]"
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
target_capture = manager._run(
|
|
"capture-pane", "-p", "-J", "-t", f"{session}:activity-core"
|
|
)
|
|
if f"AGENT:{expected}" in target_capture:
|
|
break
|
|
time.sleep(0.05)
|
|
assert f"AGENT:{expected}" in target_capture
|
|
assert store.list()[0]["state"] == "injected"
|
|
|
|
legacy_envelope = (
|
|
f"#railiance-platform: Hello! [{row['message_id']}]"
|
|
)
|
|
manager._run(
|
|
"send-keys",
|
|
"-t",
|
|
f"{session}:activity-core",
|
|
"-l",
|
|
"--",
|
|
legacy_envelope,
|
|
)
|
|
manager._run(
|
|
"send-keys", "-t", f"{session}:activity-core", "Enter"
|
|
)
|
|
time.sleep(0.1)
|
|
assert len(store.list()) == 1
|
|
|
|
service._deliver_once()
|
|
time.sleep(0.1)
|
|
assert len(store.list()) == 1
|
|
assert manager._run(
|
|
"capture-pane", "-p", "-J", "-t", f"{session}:activity-core"
|
|
).count(f"AGENT:{expected}") == 1
|
|
finally:
|
|
if store is not None:
|
|
store.close()
|
|
manager._run("kill-server", check=False)
|