mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
069308bbe6
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>
98 lines
3.5 KiB
Python
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")]))
|