cmux/tests/test_cli_socket_autodiscovery.py

150 lines
4.5 KiB
Python
Executable file

#!/usr/bin/env python3
"""Regression test: CLI should auto-discover tagged debug sockets from CMUX_TAG."""
from __future__ import annotations
import glob
import os
import shutil
import socket
import subprocess
import threading
def resolve_cmux_cli() -> str:
explicit = os.environ.get("CMUX_CLI_BIN") or os.environ.get("CMUX_CLI")
if explicit and os.path.exists(explicit) and os.access(explicit, os.X_OK):
return explicit
candidates: list[str] = []
candidates.extend(glob.glob(os.path.expanduser("~/Library/Developer/Xcode/DerivedData/*/Build/Products/Debug/cmux")))
candidates.extend(glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux"))
candidates = [p for p in candidates if os.path.exists(p) and os.access(p, os.X_OK)]
if candidates:
candidates.sort(key=os.path.getmtime, reverse=True)
return candidates[0]
in_path = shutil.which("cmux")
if in_path:
return in_path
raise RuntimeError("Unable to find cmux CLI binary. Set CMUX_CLI_BIN.")
class PingServer:
def __init__(self, socket_path: str):
self.socket_path = socket_path
self.ready = threading.Event()
self.error: Exception | None = None
self._thread = threading.Thread(target=self._run, daemon=True)
def start(self) -> None:
self._thread.start()
def wait_ready(self, timeout: float) -> bool:
return self.ready.wait(timeout)
def join(self, timeout: float) -> None:
self._thread.join(timeout=timeout)
def _run(self) -> None:
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
if os.path.exists(self.socket_path):
os.remove(self.socket_path)
server.bind(self.socket_path)
server.listen(1)
server.settimeout(6.0)
self.ready.set()
# The CLI may probe candidate sockets with a connect-only check before
# issuing the actual command, so handle more than one connection.
for _ in range(4):
conn, _ = server.accept()
with conn:
conn.settimeout(2.0)
data = b""
while b"\n" not in data:
chunk = conn.recv(4096)
if not chunk:
break
data += chunk
if b"ping" in data:
conn.sendall(b"PONG\n")
return
raise RuntimeError("Did not receive ping command on test socket")
except Exception as exc: # pragma: no cover - explicit surface on failure
self.error = exc
self.ready.set()
finally:
server.close()
def main() -> int:
try:
cli_path = resolve_cmux_cli()
except Exception as exc:
print(f"FAIL: {exc}")
return 1
tag = f"cli-autodiscover-{os.getpid()}"
socket_path = f"/tmp/cmux-debug-{tag}.sock"
server = PingServer(socket_path)
server.start()
if not server.wait_ready(2.0):
print("FAIL: socket server did not become ready")
return 1
if server.error is not None:
print(f"FAIL: socket server failed to start: {server.error}")
return 1
env = os.environ.copy()
env["CMUX_SOCKET_PATH"] = "/tmp/cmux.sock"
env["CMUX_TAG"] = tag
env["CMUX_CLI_SENTRY_DISABLED"] = "1"
env["CMUX_CLAUDE_HOOK_SENTRY_DISABLED"] = "1"
try:
proc = subprocess.run(
[cli_path, "ping"],
text=True,
capture_output=True,
env=env,
timeout=8,
check=False,
)
except Exception as exc:
print(f"FAIL: invoking cmux ping failed: {exc}")
return 1
finally:
server.join(timeout=2.0)
try:
os.remove(socket_path)
except OSError:
pass
if server.error is not None:
print(f"FAIL: socket server error: {server.error}")
return 1
if proc.returncode != 0:
print("FAIL: cmux ping returned non-zero status")
print(f"stdout={proc.stdout!r}")
print(f"stderr={proc.stderr!r}")
return 1
if proc.stdout.strip() != "PONG":
print("FAIL: cmux ping did not use auto-discovered socket")
print(f"stdout={proc.stdout!r}")
print(f"stderr={proc.stderr!r}")
return 1
print("PASS: cmux ping auto-discovers tagged socket from CMUX_TAG")
return 0
if __name__ == "__main__":
raise SystemExit(main())