项目文件夹

文件
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

892 行
36 KiB
Python

"""Tests for the local-host pub/sub agent bus over a Unix-domain socket."""
from __future__ import annotations
import importlib.util
import queue
import socket
import threading
import time
import uuid
from collections.abc import Iterator
from contextlib import suppress
from pathlib import Path
import pytest
_POSIX_FCNTL_AVAILABLE = importlib.util.find_spec("fcntl") is not None
from tools.system.fleet_monitoring import bus as bus_module
from tools.system.fleet_monitoring.bus import (
BUS_SCHEMA_VERSION,
BusMessage,
BusServer,
_pid_file_for,
_read_broker_pid,
_socket_is_live,
publish,
subscribe,
)
@pytest.fixture
def sock_path() -> Iterator[Path]:
"""Unix domain socket path must stay short (sun_path length cap, often 104–108)."""
path = Path("/tmp") / f"opensre-bus-{uuid.uuid4().hex}.sock"
yield path
with suppress(OSError):
path.unlink(missing_ok=True)
@pytest.fixture(autouse=True)
def isolated_brokers(monkeypatch: pytest.MonkeyPatch) -> None:
"""Ensure each test starts with empty broker + publisher caches.
Without this, the publisher socket cached from a prior test would point at
a now-stopped broker's socket path, and the first ``publish()`` of the
next test would silently sendall onto a dead fd.
"""
monkeypatch.setattr(bus_module, "_brokers", {})
monkeypatch.setattr(bus_module, "_publishers", {})
def _drain_subscriber(
sock_path: Path,
into: queue.Queue[BusMessage],
stop_after: int = 1,
*,
attach_timeout: float = 3.0,
) -> threading.Thread:
"""Spawn a daemon subscriber and block until it has attached to the broker.
Avoids the flaky ``time.sleep(0.15)`` pattern: we busy-poll the broker's
in-memory subscriber set until it ticks up by exactly one. Works because
our process is the broker for the test path (the autouse fixture clears
the broker registry, so ``subscribe()`` self-elects on first attach).
"""
def _loop() -> None:
for count, msg in enumerate(subscribe(path=sock_path), start=1):
into.put(msg)
if count >= stop_after:
return
# Snapshot the pre-attach subscriber count so we don't race against
# subscribers spawned by other helpers in the same test.
broker_before = bus_module._brokers.get(sock_path)
before = len(broker_before._subscribers) if broker_before is not None else 0
thread = threading.Thread(target=_loop, daemon=True)
thread.start()
deadline = time.monotonic() + attach_timeout
while time.monotonic() < deadline:
broker = bus_module._brokers.get(sock_path)
if broker is not None:
with broker._lock:
if len(broker._subscribers) > before:
return thread
time.sleep(0.005)
raise AssertionError(f"_drain_subscriber: subscriber did not attach within {attach_timeout}s")
class TestBusMessage:
def test_round_trip_preserves_all_fields(self) -> None:
original = BusMessage(
agent="claude-code:8421",
topic="finding",
summary="null deref on missing token",
source="github",
path="services/auth.py:42",
data={"commit": "abc", "line": 42},
)
decoded = BusMessage.from_jsonl(original.to_jsonl())
assert decoded == original
def test_to_jsonl_ends_with_newline(self) -> None:
msg = BusMessage(agent="a:1", topic="finding", summary="x")
assert msg.to_jsonl().endswith(b"\n")
def test_from_jsonl_supplies_defaults_for_optional_fields(self) -> None:
# Minimum required fields only — source/path/data should default.
wire = b'{"agent":"a:1","topic":"finding","summary":"x"}\n'
msg = BusMessage.from_jsonl(wire)
assert msg.source == ""
assert msg.path == ""
assert msg.data == {}
assert msg.schema_version == BUS_SCHEMA_VERSION
def test_from_jsonl_rejects_non_object_payload(self) -> None:
with pytest.raises(ValueError):
BusMessage.from_jsonl(b'["not","an","object"]')
def test_from_jsonl_rejects_missing_required_field(self) -> None:
with pytest.raises(KeyError):
BusMessage.from_jsonl(b'{"agent":"a:1","topic":"finding"}')
def test_is_not_hashable(self) -> None:
# ``data`` is a mapping, so a value hash would be misleading and would
# fail at call time on the auto-generated ``__hash__``. We disable it
# explicitly. Assert the contract directly (``__hash__ is None``);
# Python's hash protocol guarantees ``TypeError`` from there, no need
# to re-test the language (and triggering ``hash()`` here trips
# CodeQL's "Unhashable object hashed" rule).
assert BusMessage.__hash__ is None
def test_data_is_read_only_post_construction(self) -> None:
msg = BusMessage(agent="a:1", topic="finding", summary="x", data={"k": 1})
with pytest.raises(TypeError):
msg.data["k"] = 2 # type: ignore[index]
def test_data_is_isolated_from_caller_mutation(self) -> None:
# External mutation of the originally-passed dict must not bleed into
# the message — defensive copy in ``__post_init__`` enforces this.
src: dict[str, object] = {"k": 1}
msg = BusMessage(agent="a:1", topic="finding", summary="x", data=src)
src["k"] = 999
assert msg.data["k"] == 1
class TestBusServerLifecycle:
def test_start_binds_socket_and_stop_unlinks(self, sock_path: Path) -> None:
server = BusServer(sock_path)
try:
server.start()
assert sock_path.exists()
assert _socket_is_live(sock_path)
assert server.is_running
finally:
server.stop()
assert not sock_path.exists()
assert not server.is_running
def test_start_is_idempotent(self, sock_path: Path) -> None:
server = BusServer(sock_path)
try:
server.start()
server.start() # second call should be a no-op, not raise
assert server.is_running
finally:
server.stop()
def test_stop_is_idempotent(self, sock_path: Path) -> None:
server = BusServer(sock_path)
server.start()
server.stop()
server.stop() # second call should be a no-op, not raise
def test_start_writes_pid_file_and_stop_removes_it(self, sock_path: Path) -> None:
import os
server = BusServer(sock_path)
server.start()
try:
pid_path = _pid_file_for(sock_path)
assert pid_path.exists()
assert _read_broker_pid(sock_path) == os.getpid()
finally:
server.stop()
assert not _pid_file_for(sock_path).exists()
def test_start_rolls_back_when_pid_file_write_fails(
self, sock_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# Simulate ENOSPC / EACCES during the PID-file write. ``start`` must
# raise *and* leave no orphaned socket file behind — otherwise peers
# would see a path with no live listener via ``_socket_is_live``,
# ``_unlink_stale`` it, and silently split the bus.
def _boom_enospc(*args: object) -> None:
del args
raise OSError(28, "No space left on device")
monkeypatch.setattr(bus_module, "_write_pid_file_atomic", _boom_enospc)
server = BusServer(sock_path)
with pytest.raises(OSError):
server.start()
assert not sock_path.exists()
assert not _pid_file_for(sock_path).exists()
assert not server.is_running
def test_ensure_broker_propagates_pid_write_failure(
self, sock_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# ``_ensure_broker`` swallows EADDRINUSE/EEXIST as a lost bind race,
# but real failures (disk full, permission denied) must propagate so
# callers see the error instead of silently splitting the bus.
def _boom_eacces(*args: object) -> None:
del args
raise OSError(13, "Permission denied")
monkeypatch.setattr(bus_module, "_write_pid_file_atomic", _boom_eacces)
with pytest.raises(OSError):
bus_module._ensure_broker(sock_path)
class TestLivenessProbe:
def test_socket_is_live_does_not_create_phantom_subscriber(self, sock_path: Path) -> None:
# _socket_is_live used to make a real connection on every probe; under
# publish/subscribe bursts that registered a short-lived subscriber +
# reader thread per call. Verify the side-channel probe makes none.
server = BusServer(sock_path)
server.start()
try:
for _ in range(20):
assert _socket_is_live(sock_path)
with server._lock:
assert len(server._subscribers) == 0
finally:
server.stop()
def test_socket_is_live_false_when_pid_missing(self, sock_path: Path) -> None:
sock_path.parent.mkdir(parents=True, exist_ok=True)
sock_path.touch() # socket file present but no pid sidecar
assert not _socket_is_live(sock_path)
def test_socket_is_live_false_when_pid_dead(self, sock_path: Path) -> None:
sock_path.parent.mkdir(parents=True, exist_ok=True)
sock_path.touch()
# PID 999999 is almost certainly not a real process. The probe must
# report not-live so the caller will unlink + rebind.
_pid_file_for(sock_path).write_text("999999")
assert not _socket_is_live(sock_path)
class TestPublisherCache:
def test_burst_of_publishes_reuses_one_connection(self, sock_path: Path) -> None:
# Each publish() previously opened a fresh UDS connection, which the
# broker accepted, registered as a subscriber, and ran a per-connection
# reader thread for. A burst of N publishes spawned N short-lived
# threads. With the cache, all publishes from one process share one
# persistent connection and one persistent broker reader thread.
received: queue.Queue[BusMessage] = queue.Queue()
_drain_subscriber(sock_path, received, stop_after=20)
for i in range(20):
publish(BusMessage(agent="a:1", topic="finding", summary=f"n{i}"), path=sock_path)
# All frames arrive in order.
for i in range(20):
msg = received.get(timeout=2.0)
assert msg.summary == f"n{i}"
# Exactly one cached publisher socket for our process.
assert len(bus_module._publishers) == 1
cached = next(iter(bus_module._publishers.values()))
assert cached.sock.fileno() >= 0
# Broker side: at most one publisher-origin connection (plus the one
# subscriber drain). Check by counting connections opened by us. The
# broker only sees what we sent it; if reuse is working, every publish
# came from the same socket and the broker registered exactly one
# publisher connection for the burst.
broker = bus_module._brokers[sock_path]
with broker._lock:
# 1 subscriber (the drain thread above) + 1 cached publisher = 2.
# If the cache were broken we'd see many transient connections,
# but most would have closed by now — so this is a weak check;
# the strong check is len(bus_module._publishers) == 1 above.
assert len(broker._subscribers) <= 2
def test_reconnects_after_cached_socket_breaks(self, sock_path: Path) -> None:
received: queue.Queue[BusMessage] = queue.Queue()
_drain_subscriber(sock_path, received, stop_after=2)
publish(BusMessage(agent="a:1", topic="finding", summary="first"), path=sock_path)
msg1 = received.get(timeout=2.0)
assert msg1.summary == "first"
# Forcibly break the cached publisher connection.
broken_cached = next(iter(bus_module._publishers.values()))
broken_sock = broken_cached.sock
broken_sock.close()
# Next publish must transparently reconnect (one retry on OSError).
publish(BusMessage(agent="a:1", topic="finding", summary="second"), path=sock_path)
msg2 = received.get(timeout=2.0)
assert msg2.summary == "second"
# The cache now holds a fresh socket object (the OS may recycle the
# underlying fd number, so compare object identity, not fileno()).
new_cached = next(iter(bus_module._publishers.values()))
assert new_cached.sock is not broken_sock
def test_publish_retries_on_initial_connect_failure(
self, sock_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# The first ``_get_or_open_publisher`` raises (e.g. broker exited
# between liveness check and connect). ``publish`` must run
# ``_ensure_broker`` and retry once instead of failing immediately.
real_get = bus_module._get_or_open_publisher
calls = {"n": 0}
def _flaky(target: Path, *, connect_timeout: float) -> bus_module._CachedPublisher:
calls["n"] += 1
if calls["n"] == 1:
raise ConnectionRefusedError(111, "Connection refused")
return real_get(target, connect_timeout=connect_timeout)
monkeypatch.setattr(bus_module, "_get_or_open_publisher", _flaky)
received: queue.Queue[BusMessage] = queue.Queue()
_drain_subscriber(sock_path, received, stop_after=1)
publish(BusMessage(agent="a:1", topic="finding", summary="retried"), path=sock_path)
msg = received.get(timeout=2.0)
assert msg.summary == "retried"
assert calls["n"] == 2, "publish() did not retry on initial connect failure"
def test_subscribe_retries_on_initial_connect_failure(
self, sock_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# ``subscribe`` previously raised on the first connect failure;
# symmetric with ``publish``, it now retries once.
real_connect = bus_module._connect_client
calls = {"n": 0}
def _flaky(target: Path, timeout: float) -> socket.socket:
calls["n"] += 1
if calls["n"] == 1:
raise ConnectionRefusedError(111, "Connection refused")
return real_connect(target, timeout=timeout)
monkeypatch.setattr(bus_module, "_connect_client", _flaky)
# Just calling subscribe() and pulling the first batch is enough —
# if the retry didn't happen, the call would raise.
received: queue.Queue[BusMessage] = queue.Queue()
def _loop() -> None:
for msg in subscribe(path=sock_path):
received.put(msg)
return
thread = threading.Thread(target=_loop, daemon=True)
thread.start()
# Wait for subscribe() to attach, give up to 2s.
deadline = time.monotonic() + 2.0
while time.monotonic() < deadline:
broker = bus_module._brokers.get(sock_path)
if broker is not None:
with broker._lock:
if broker._subscribers:
break
time.sleep(0.005)
broker = bus_module._brokers.get(sock_path)
assert broker is not None and broker._subscribers, "subscriber never attached after retry"
assert calls["n"] >= 2, "subscribe() did not retry on initial connect failure"
def test_concurrent_publishes_do_not_interleave_frames(self, sock_path: Path) -> None:
# If the per-socket send_lock weren't held around sendall(), concurrent
# publishes from different threads could interleave bytes mid-frame,
# corrupting the JSONL stream.
n_threads = 10
per_thread = 5
total = n_threads * per_thread
received: queue.Queue[BusMessage] = queue.Queue()
_drain_subscriber(sock_path, received, stop_after=total)
def _spam(tid: int) -> None:
for i in range(per_thread):
publish(
BusMessage(
agent=f"t{tid}:1",
topic="finding",
summary=f"t{tid}-n{i}",
),
path=sock_path,
)
threads = [threading.Thread(target=_spam, args=(t,)) for t in range(n_threads)]
for t in threads:
t.start()
for t in threads:
t.join(timeout=5.0)
# Every frame must be parseable (no byte interleaving) and unique.
seen: set[str] = set()
for _ in range(total):
msg = received.get(timeout=5.0)
seen.add(msg.summary)
assert len(seen) == total, f"frame loss or corruption: got {len(seen)} unique of {total}"
class TestPublishSubscribe:
def test_round_trip_one_publisher_one_subscriber(self, sock_path: Path) -> None:
received: queue.Queue[BusMessage] = queue.Queue()
_drain_subscriber(sock_path, received)
publish(
BusMessage(
agent="claude-code:8421",
topic="finding",
summary="null deref",
path="services/auth.py:42",
),
path=sock_path,
)
msg = received.get(timeout=2.0)
assert msg.agent == "claude-code:8421"
assert msg.summary == "null deref"
assert msg.path == "services/auth.py:42"
def test_one_publisher_multiple_subscribers_all_receive(self, sock_path: Path) -> None:
n_subs = 3
sub_queues: list[queue.Queue[BusMessage]] = [queue.Queue() for _ in range(n_subs)]
for q in sub_queues:
_drain_subscriber(sock_path, q)
publish(BusMessage(agent="a:1", topic="finding", summary="hello"), path=sock_path)
for q in sub_queues:
msg = q.get(timeout=2.0)
assert msg.summary == "hello"
def test_broadcast_holds_per_subscriber_write_lock(self, sock_path: Path) -> None:
# The bug: two reader-threads broadcasting concurrently to the same
# subscriber socket can interleave bytes mid-frame because
# ``sendall`` is multi-syscall under back-pressure. The fix is a
# per-subscriber write lock acquired around ``select`` + ``sendall``
# in ``_broadcast``.
#
# Reliably reproducing kernel-level byte interleaving across
# systems is fragile (depends on SNDBUF/RCVBUF tuning). Test the
# lock contract directly: hold the per-subscriber write_lock
# externally and confirm ``_broadcast`` blocks until release.
server = BusServer(sock_path)
server.start()
sub: socket.socket | None = None
try:
sub = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sub.connect(str(sock_path))
deadline = time.monotonic() + 2.0
while time.monotonic() < deadline:
with server._lock:
if server._subscribers:
break
time.sleep(0.005)
with server._lock:
assert server._subscribers, "subscriber never attached"
# Grab the broker-side (sub, write_lock) pair.
((_broker_sub, write_lock),) = server._subscribers.items()
# Hold the lock as if another reader-thread were mid-sendall.
assert write_lock.acquire(timeout=1.0)
broadcast_returned = threading.Event()
try:
def _bcast() -> None:
server._broadcast(b'{"x":"y"}\n', origin=None)
broadcast_returned.set()
t = threading.Thread(target=_bcast, daemon=True)
t.start()
# While we hold the lock, ``_broadcast`` must wait — without
# the lock it would race straight into ``sendall``.
assert not broadcast_returned.wait(timeout=0.2), (
"broadcast did not block on per-subscriber write lock"
)
finally:
write_lock.release()
# After release, broadcast should complete promptly.
assert broadcast_returned.wait(timeout=2.0), (
"broadcast did not return after lock release"
)
finally:
if sub is not None:
with suppress(OSError):
sub.close()
server.stop()
def test_publisher_does_not_receive_own_frame(self, sock_path: Path) -> None:
# Self-elect a broker so we can attach as a single client that
# both publishes and subscribes — and verify the broadcaster
# does not echo the frame back to its origin.
bus_module._ensure_broker(sock_path)
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(str(sock_path))
client.settimeout(0.5)
try:
client.sendall(BusMessage(agent="a:1", topic="finding", summary="x").to_jsonl())
with pytest.raises(socket.timeout):
client.recv(4096)
finally:
client.close()
def test_subscribe_skips_malformed_frames(self, sock_path: Path) -> None:
received: queue.Queue[BusMessage] = queue.Queue()
_drain_subscriber(sock_path, received)
# Connect a raw publisher and inject a malformed frame followed by a valid one.
bus_module._ensure_broker(sock_path)
raw = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
raw.connect(str(sock_path))
try:
raw.sendall(b"this-is-not-json\n")
raw.sendall(BusMessage(agent="a:1", topic="finding", summary="ok").to_jsonl())
finally:
# Keep the publisher alive briefly so the broker can forward both frames.
time.sleep(0.1)
raw.close()
msg = received.get(timeout=2.0)
assert msg.summary == "ok"
def test_unresponsive_subscriber_does_not_stall_broadcast(
self, sock_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# If one subscriber's recv buffer fills, ``sendall`` on a blocking UDS
# would wedge indefinitely with no exception, freezing fan-out for
# every publisher. Verify the write-readiness gate evicts the slow
# subscriber and lets healthy ones keep receiving.
monkeypatch.setattr(bus_module, "_BROADCAST_WRITE_TIMEOUT_SECONDS", 0.05)
server = BusServer(sock_path)
server.start()
slow: socket.socket | None = None
try:
# Healthy subscriber: drains until it sees the "alive" sentinel.
seen_alive = threading.Event()
def _healthy_drain() -> None:
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(str(sock_path))
client.settimeout(3.0)
buf = b""
try:
while not seen_alive.is_set():
chunk = client.recv(8192)
if not chunk:
return
buf += chunk
while b"\n" in buf:
line, buf = buf.split(b"\n", 1)
if not line:
continue
try:
msg = BusMessage.from_jsonl(line)
except (ValueError, KeyError, TypeError):
continue
if msg.summary == "alive":
seen_alive.set()
return
except OSError:
return
finally:
client.close()
t = threading.Thread(target=_healthy_drain, daemon=True)
t.start()
# Slow subscriber: shrink recv buffer so it fills fast, never reads.
slow = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
slow.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024)
slow.connect(str(sock_path))
# Wait for both to attach.
deadline = time.time() + 2.0
while time.time() < deadline:
with server._lock:
if len(server._subscribers) >= 2:
break
time.sleep(0.02)
# Pump enough large frames to fill ``slow``'s tiny recv buffer.
for _ in range(8):
publish(
BusMessage(agent="a:1", topic="finding", summary="x" * 4000),
path=sock_path,
)
# Final small frame. Healthy subscriber must receive it — that
# proves the broker did not stall behind the wedged ``slow``.
publish(BusMessage(agent="a:1", topic="finding", summary="alive"), path=sock_path)
assert seen_alive.wait(timeout=3.0), (
"broker stalled: healthy subscriber never received the post-fill frame"
)
# Slow subscriber should have been evicted from the fan-out set.
deadline = time.time() + 1.0
slow_fd = slow.fileno()
while time.time() < deadline:
with server._lock:
still_attached = any(s.fileno() == slow_fd for s in server._subscribers)
if not still_attached:
break
time.sleep(0.05)
with server._lock:
attached_fds = {s.fileno() for s in server._subscribers}
assert slow_fd not in attached_fds, (
"unresponsive subscriber was not evicted from fan-out set"
)
finally:
if slow is not None:
slow.close()
server.stop()
def test_subscriber_disconnects_on_oversized_unterminated_stream(self, sock_path: Path) -> None:
# Simulate a hostile broker that wins the bind race and streams unlimited
# bytes without newlines. The subscriber must cap its buffer and bail
# rather than grow memory unboundedly.
from tools.system.fleet_monitoring.bus import _MAX_FRAME_BYTES, BusServer, subscribe
# Stand up a fake "hostile" broker that pushes garbage to every client.
server = BusServer(sock_path)
server.start()
try:
done = threading.Event()
error: list[Exception] = []
def _consume() -> None:
try:
# Drain until subscribe() returns (which it should, on cap breach).
for _ in subscribe(path=sock_path):
pass
except Exception as exc:
error.append(exc)
finally:
done.set()
t = threading.Thread(target=_consume, daemon=True)
t.start()
# Wait for the subscriber to attach to the broker.
deadline = time.time() + 2.0
while time.time() < deadline:
with server._lock:
if server._subscribers:
break
time.sleep(0.02)
assert server._subscribers, "subscriber never attached"
# Push a single oversized chunk through to all subscribers.
payload = b"X" * (_MAX_FRAME_BYTES * 4 + 1024)
with server._lock:
victim = next(iter(server._subscribers))
victim.sendall(payload)
# Subscriber should disconnect on its own without raising.
assert done.wait(timeout=2.0), "subscriber did not disconnect on cap breach"
assert not error, f"subscribe() raised unexpectedly: {error}"
finally:
server.stop()
class TestBrokerElectionRace:
def test_concurrent_cold_start_election_does_not_orphan_a_broker(self, sock_path: Path) -> None:
# Cold-start race: two processes both observe ``_socket_is_live``
# → False, both call ``_unlink_stale``, both try to bind. The
# kernel only lets one bind succeed, but without cross-process
# serialization the loser is left holding a listener fd whose
# filesystem path the winner just unlinked, plus the accept/
# reader daemon threads. The election ``flock`` prevents that.
#
# We simulate the race by holding the flock from this test
# process and concurrently spawning a child that calls
# ``_ensure_broker``. The child must block on the flock until we
# release it, then return ``None`` (we elected first).
import os
import subprocess
import sys
import textwrap
# Bind a broker first; once it's live, any peer (including the
# subprocess we'll spawn) sees it via the PID-file side channel and
# backs off. The flock only matters during the check-unlink-bind
# window, which closes after ``server.start()`` returns.
server = bus_module.BusServer(sock_path)
server.start()
try:
assert sock_path.exists()
assert _pid_file_for(sock_path).exists()
# Spawn a real subprocess that calls ``_ensure_broker`` for
# the same path. With the election lock and the live PID
# sidecar, the child must observe our broker and return
# ``None``. Without the flock contract, a concurrent cold
# start could unlink our socket file and rebind, orphaning
# this server.
repo_root = Path(__file__).resolve().parents[2]
child = subprocess.run(
[
sys.executable,
"-c",
textwrap.dedent(
f"""
from pathlib import Path
from tools.system.fleet_monitoring import bus
result = bus._ensure_broker(Path({str(sock_path)!r}))
print("OWNER" if result is not None else "PEER")
"""
),
],
capture_output=True,
text=True,
timeout=10.0,
env={**os.environ, "PYTHONPATH": str(repo_root)},
)
assert child.returncode == 0, child.stderr
assert child.stdout.strip() == "PEER", (
f"child wrongly elected itself: stdout={child.stdout!r} stderr={child.stderr!r}"
)
# Our broker is unscathed: socket file + pid file still there,
# listener still accepting connections.
assert sock_path.exists(), "winner's socket file vanished"
assert _pid_file_for(sock_path).exists(), "winner's pid file vanished"
assert server.is_running
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.settimeout(2.0)
client.connect(str(sock_path))
client.close()
finally:
server.stop()
def test_election_lock_path_lives_next_to_socket(self, sock_path: Path) -> None:
# Sanity-check the sidecar location so future refactors don't
# silently move it (which would re-open the cross-process race).
lock_path = bus_module._election_lock_path(sock_path)
assert lock_path.parent == sock_path.parent
assert lock_path.name == sock_path.name + ".lock"
@pytest.mark.skipif(
not _POSIX_FCNTL_AVAILABLE,
reason="cross-process election flock requires POSIX fcntl (omitted on Windows)",
)
def test_ensure_broker_blocks_on_election_flock_held_by_peer(self, sock_path: Path) -> None:
# Direct test of the cross-process serialization: a child process
# holds the election flock; this process's ``_ensure_broker``
# must block on ``flock(LOCK_EX)`` until the child releases it.
# Without this, two concurrent cold-start callers can race
# through unlink+bind and leave one orphaned.
import os
import subprocess
import sys
import textwrap
repo_root = Path(__file__).resolve().parents[2]
# Ensure parent dir exists so the child can open the lock file.
sock_path.parent.mkdir(parents=True, exist_ok=True)
# Child: open + flock the election file, signal "READY", sleep,
# then exit (flock auto-released on close).
child = subprocess.Popen(
[
sys.executable,
"-u",
"-c",
textwrap.dedent(
f"""
import os, fcntl, sys, time
from pathlib import Path
from tools.system.fleet_monitoring import bus
fd = bus._acquire_election_flock(Path({str(sock_path)!r}))
print("READY", flush=True)
time.sleep(0.5)
bus._release_election_flock(fd)
"""
),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env={**os.environ, "PYTHONPATH": str(repo_root)},
)
try:
# Wait for the child to confirm it holds the lock.
assert child.stdout is not None
line = child.stdout.readline()
assert line.strip() == "READY", f"child did not signal ready: {line!r}"
# Now ``_ensure_broker`` here must wait on the flock. Time
# the call: it should take roughly the remaining sleep time.
t0 = time.monotonic()
server = bus_module._ensure_broker(sock_path)
elapsed = time.monotonic() - t0
try:
assert server is not None, "we should have elected after child released"
# The child slept 0.5s total starting before READY; we
# should have waited for some appreciable fraction of
# that. 0.1s is well under the 0.5s sleep but well above
# zero, so it cleanly distinguishes "blocked on flock"
# from "didn't block at all".
assert elapsed >= 0.1, (
f"_ensure_broker did not block on the election flock (elapsed={elapsed:.3f}s)"
)
finally:
if server is not None:
server.stop()
finally:
child.wait(timeout=5.0)
class TestBrokerSelfElection:
def test_stale_socket_file_is_unlinked_and_rebound(self, sock_path: Path) -> None:
sock_path.parent.mkdir(parents=True, exist_ok=True)
sock_path.touch() # stale: no listener
assert sock_path.exists() and not _socket_is_live(sock_path)
server = bus_module._ensure_broker(sock_path)
try:
assert server is not None
assert server.is_running
assert _socket_is_live(sock_path)
finally:
if server is not None:
server.stop()
def test_ensure_broker_returns_none_when_other_process_owns_it(self, sock_path: Path) -> None:
# Stand up an "other" broker via direct BusServer (simulating another process),
# then call _ensure_broker — it should detect the live socket and back off.
other = BusServer(sock_path)
other.start()
try:
# Pretend our process has not yet elected: empty the registry.
bus_module._brokers.clear()
result = bus_module._ensure_broker(sock_path)
assert result is None
finally:
other.stop()
def test_ensure_broker_idempotent_for_same_process(self, sock_path: Path) -> None:
first = bus_module._ensure_broker(sock_path)
second = bus_module._ensure_broker(sock_path)
try:
assert first is not None
assert first is second
finally:
if first is not None:
first.stop()
class TestSlashCommandFormatter:
def test_format_includes_agent_and_summary(self) -> None:
from surfaces.interactive_shell.command_registry.agents.core import _format_bus_message
msg = BusMessage(agent="claude-code:8421", topic="finding", summary="hi")
out = _format_bus_message(msg)
assert "claude-code:8421" in out
assert "hi" in out
def test_format_includes_path_when_present(self) -> None:
from surfaces.interactive_shell.command_registry.agents.core import _format_bus_message
msg = BusMessage(agent="a:1", topic="finding", summary="x", path="services/auth.py:42")
out = _format_bus_message(msg)
assert "services/auth.py:42" in out
assert "—" in out
def test_format_omits_separator_when_no_path(self) -> None:
from surfaces.interactive_shell.command_registry.agents.core import _format_bus_message
msg = BusMessage(agent="a:1", topic="finding", summary="x")
out = _format_bus_message(msg)
assert "—" not in out