Files
bullpen/tests/test_worker_reliability.py
Markus Fritsche e74b2f3832 @reviewer findings #2–#6: harden lurker + worker reliability
#4 a missing/corrupt .since OR a failed room_read now resumes from the newest id /
   backs off — never resets to 0 (which re-ran the ENTIRE history through the model).
   room_read/_read_since return None on failure to distinguish it from an empty room.
#2 posts are rc-checked; the offset advances past a message only after its reply
   actually lands (3 retries) — a transient lmcp/sic failure retries, never silently drops.
#3 dispatch/run exceptions no longer skip the message (offset is reply-gated, not
   advanced-then-maybe-fail).
#5 offset is persisted per fully-handled message — a restart mid-batch resumes exactly,
   no duplicate ack / re-run.
#6 lurker reply capped at 6000 chars (long Fable reviews no longer blow room limits).
tests/test_worker_reliability.py locks the #4/#2 core (read-fail != empty; rc checks). 6/6.
Also: bp timeouts for testdesigner/py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-21 08:09:15 +02:00

52 lines
1.9 KiB
Python

"""Reviewer findings #2 / #4 — bullpen_worker must distinguish a FAILED room_read from an
empty room, and check post success. (#4: `except: since=0` replayed all history when the
offset read failed; the root cause was room_read swallowing failure as [].)"""
import importlib.util
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
_spec = importlib.util.spec_from_file_location("bw", REPO / "lib" / "bullpen_worker.py")
bw = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(bw)
class FakeCP:
def __init__(self, rc=0, stdout=""):
self.returncode = rc
self.stdout = stdout
def test_ok_checks_returncode():
assert bw._ok(FakeCP(rc=0)) is True
assert bw._ok(FakeCP(rc=1)) is False
assert bw._ok(None) is False
def test_read_failure_is_none_not_empty(monkeypatch):
# #4: a failed read must NOT look like an empty room (which seeded since=0 -> replay)
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=1, stdout=""))
assert bw._read_since(0) is None
def test_read_success_empty_is_empty_list(monkeypatch):
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout=""))
assert bw._read_since(0) == []
def test_read_parses_jsonl(monkeypatch):
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout='{"id":1}\n \n{"id":2}\n'))
assert [m["id"] for m in bw._read_since(0)] == [1, 2]
def test_newest_id_from_room(monkeypatch):
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout='{"id":5}\n{"id":9}\n{"id":3}\n'))
assert bw._newest_id() == 9
def test_newest_id_is_zero_only_on_empty_not_on_failure(monkeypatch):
# empty room -> 0 is fine; a FAILED read -> 0 too, but never a replay because room_read=None
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=1))
assert bw._newest_id() == 0
monkeypatch.setattr(bw, "lmcp", lambda *a, **k: FakeCP(rc=0, stdout=""))
assert bw._newest_id() == 0