Files
rea/tests/fixtures/hopperBridgeReconnectProbe.py
T
morluto 069308bbe6 fix(hopper): preserve startup failures and recover denied launches
Preserve the primary Hopper startup failure and launcher diagnostics while retaining resources when cleanup cannot be confirmed. Reauthenticate matching external application sessions before document cleanup, keep the Python bridge available for bounded reconnects, and expose the provider-owned Hopper startup deadline.

Real headless Linux Hopper semantic analysis and 65 focused configuration, handshake, cleanup, and bridge tests passed. macOS authorization/crash causes and saved-HOP behavior require their own real-provider verification.

Refs #1808

Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-11 22:35:01 +08:00

98 lines
3.5 KiB
Python

"""Exercise the shipped socket lifecycle with a recording native dispatch port."""
import json
from pathlib import Path
import socket
import sys
import threading
import time
from hopperBridgeFacadeProbe import load_bridge
def probe(path, socket_path, mode):
bridge = load_bridge(path)
bridge.update(REA_SOCKET=socket_path, REA_TOKEN="owned-token", REA_RUN_ID="owned-run",
REA_RECONNECT_TIMEOUT_SECONDS=0.2)
requests = []
entered = threading.Event()
release = threading.Event()
health = bridge["_dispatch"]
def dispatch(method, params):
requests.append(method)
if method == "health":
if mode == "lost-reply" and requests == ["health"]:
entered.set()
assert release.wait(2), "Probe never released the blocked reply"
return health(method, params)
assert method == "shutdown_document", method
closed = not params.get("unconfirmed", False)
return {"shutdown": True, "analysis_stopped": True, "document_closed": closed}
bridge["_dispatch"] = dispatch
failures = []
def serve():
try:
bridge["_run"]()
except Exception as error:
failures.append(repr(error))
worker = threading.Thread(target=serve, daemon=True)
worker.start()
deadline = time.monotonic() + 2
while not Path(socket_path).exists():
assert time.monotonic() < deadline, failures
time.sleep(0.005)
def connect():
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.settimeout(2)
client.connect(socket_path)
return client, client.makefile("rwb")
def send(file, method, token="owned-token", params=None):
file.write((json.dumps({"id": 1, "method": method, "token": token, "params": params or {}}) + "\n").encode())
file.flush()
def reply(file):
while True:
line = file.readline()
assert line, "Bridge closed before its reply"
value = json.loads(line)
if "event" not in value:
return value
client, file = connect()
send(file, "health", "foreign-token" if mode == "unauthorized" else "owned-token")
if mode == "lost-reply":
assert entered.wait(2)
elif mode == "unauthorized":
assert reply(file)["error"]["type"] == "authorization"
else:
assert reply(file)["result"]["run_id"] == "owned-run"
file.close()
client.close()
release.set()
if mode != "expiry":
client, file = connect()
send(file, "health")
assert reply(file)["result"]["run_id"] == "owned-run"
send(file, "shutdown_document", params={"unconfirmed": True})
assert reply(file)["result"]["document_closed"] is False
send(file, "shutdown_document")
assert reply(file)["result"]["document_closed"] is True
file.close()
client.close()
worker.join(2)
assert not worker.is_alive(), "Bridge pinned its execution thread"
assert not failures, failures
assert not Path(socket_path).exists(), "Bridge retained its closed listener"
assert requests.count("shutdown_document") == (0 if mode == "expiry" else 2)
return {"mode": mode, "requests": requests, "listener_removed": True, "thread_released": True}
if __name__ == "__main__":
print(json.dumps([probe(sys.argv[1], str(Path(sys.argv[2]) / (mode + ".sock")), mode)
for mode in ("unauthorized", "disconnect", "lost-reply", "expiry")]))