150 lines
4.5 KiB
Python
Executable file
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())
|