6b81590c55
Systematic prune per AGENTS.md test policy, one pass over every major test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli, cron, tui_gateway, honcho/openviking, root-level): - DELETE: source-reading tests (read_text/getsource on prod files), change-detector tests (exact catalog counts, model-name snapshots, config version literals), mock-echo tests (assert a mock returns what it was told), assertion-free/trivial tests, near-duplicate parametrizations (boundaries + one representative kept), async/sync twin duplicates, cosmetic within-file variations. - KEEP (mandatory): security/redaction/approval guards, message-role alternation invariants, prompt-caching/deterministic-call-id invariants, issue-number regression tests (deduped), E2E tests. - 6 test files deleted outright (script-style/no-assert or fully redundant); conftest.py, fakes/, fixtures/ untouched. - tests/acp/conftest.py added: autouse fixture stubs the live models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server tests performed on every session create — test_server.py 147s → 3.4s, and the tests are now genuinely hermetic. - Sleep-based slowness shrunk where safe (codex_ttfb_watchdog, compression_concurrent_fork, etc.); no wall-clock assertion tightened. Verification: full hermetic suite via scripts/run_tests.sh — 2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall (baseline: 583s wall, 13,564s subprocess CPU).
85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
"""write_txn BUSY-retry behaviour.
|
|
|
|
These tests target the transaction boundary (BEGIN IMMEDIATE / COMMIT) only.
|
|
On unmodified main write_txn has no application-level retry, so the
|
|
"transient BUSY is absorbed" and "persistent BUSY is bounded" cases fail until
|
|
the fix lands. No real DB is touched: a fake connection records and replays
|
|
scripted boundary outcomes.
|
|
"""
|
|
|
|
import sqlite3
|
|
|
|
import pytest
|
|
|
|
from hermes_cli import kanban_db as kb
|
|
|
|
|
|
class _FakeConn:
|
|
"""Records execute() calls and replays a scripted result per SQL statement.
|
|
|
|
script maps an uppercased SQL prefix to a list of outcomes consumed in
|
|
order. An outcome is either an Exception (raised) or None (success).
|
|
"""
|
|
|
|
def __init__(self, script):
|
|
self._script = {k: list(v) for k, v in script.items()}
|
|
self.calls = []
|
|
|
|
def execute(self, sql, *args):
|
|
self.calls.append(sql)
|
|
key = sql.strip().split()[0].upper()
|
|
outcomes = self._script.get(key)
|
|
if outcomes:
|
|
outcome = outcomes.pop(0)
|
|
if isinstance(outcome, Exception):
|
|
raise outcome
|
|
return None
|
|
|
|
def count(self, prefix):
|
|
prefix = prefix.upper()
|
|
return sum(1 for c in self.calls if c.strip().upper().startswith(prefix))
|
|
|
|
|
|
def _busy():
|
|
return sqlite3.OperationalError("database is locked")
|
|
|
|
|
|
def _other():
|
|
return sqlite3.OperationalError("no such table: tasks")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _no_file_check(monkeypatch):
|
|
# Isolate the boundary behaviour from the post-commit invariant.
|
|
monkeypatch.setattr(kb, "_check_file_length_invariant", lambda conn: None)
|
|
|
|
|
|
def test_retry_sleep_respects_floor(monkeypatch):
|
|
# The jitter has a floor so a retry can't busy-spin back into the collision.
|
|
slept = []
|
|
monkeypatch.setattr(kb.time, "sleep", lambda s: slept.append(s))
|
|
conn = _FakeConn({"BEGIN": [_busy(), _busy(), None]})
|
|
with kb.write_txn(conn):
|
|
pass
|
|
assert slept
|
|
assert all(s >= kb._BUSY_RETRY_MIN_S for s in slept)
|
|
assert all(s <= kb._BUSY_RETRY_MAX_S for s in slept)
|
|
|
|
|
|
def test_transient_busy_at_begin_is_absorbed():
|
|
conn = _FakeConn({"BEGIN": [_busy(), None]})
|
|
with kb.write_txn(conn):
|
|
pass
|
|
assert conn.count("BEGIN") == 2
|
|
assert conn.count("COMMIT") == 1
|
|
|
|
|
|
def test_persistent_busy_at_commit_rolls_back():
|
|
# Exhausted COMMIT leaves the txn open; write_txn must ROLLBACK before
|
|
# re-raising so the connection isn't poisoned for the next transaction.
|
|
conn = _FakeConn({"COMMIT": [_busy()] * 50})
|
|
with pytest.raises(sqlite3.OperationalError, match="database is locked"):
|
|
with kb.write_txn(conn):
|
|
pass
|
|
assert conn.count("ROLLBACK") == 1
|