mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-30 19:09:28 +00:00
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).
84 lines
2.2 KiB
Python
84 lines
2.2 KiB
Python
"""Tests for agent.thread_scoped_output.thread_scoped_silence.
|
|
|
|
Behaviour contract: a thread inside ``thread_scoped_silence()`` has its
|
|
stdout/stderr routed to devnull, while every OTHER thread keeps writing to the
|
|
real stream — even concurrently, while the first thread is still inside the
|
|
context. This is the property the old process-global
|
|
``contextlib.redirect_stdout(devnull)`` violated (issue #55769 / #55925).
|
|
"""
|
|
|
|
import io
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
from agent.thread_scoped_output import thread_scoped_silence
|
|
|
|
|
|
def _run_with_real_stream(fn):
|
|
"""Bind a StringIO as the real stdout, run fn, return what reached it."""
|
|
real_out = io.StringIO()
|
|
orig = sys.stdout
|
|
sys.stdout = real_out
|
|
try:
|
|
fn()
|
|
finally:
|
|
sys.stdout = orig
|
|
return real_out.getvalue()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_stderr_is_also_routed_per_thread():
|
|
real_err = io.StringIO()
|
|
orig = sys.stderr
|
|
sys.stderr = real_err
|
|
try:
|
|
with thread_scoped_silence():
|
|
sys.stderr.write("err-dropped\n")
|
|
sys.stderr.write("err-kept\n")
|
|
finally:
|
|
sys.stderr = orig
|
|
out = real_err.getvalue()
|
|
assert "err-dropped" not in out
|
|
assert "err-kept" in out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_many_concurrent_silenced_and_loud_threads():
|
|
"""Stress: interleaved silenced/loud threads keep their respective fates."""
|
|
start = threading.Event()
|
|
results_lock = threading.Lock()
|
|
|
|
def silenced(i):
|
|
start.wait(timeout=2.0)
|
|
with thread_scoped_silence():
|
|
print(f"S{i}")
|
|
time.sleep(0.05)
|
|
|
|
def loud(i):
|
|
start.wait(timeout=2.0)
|
|
time.sleep(0.02)
|
|
print(f"L{i}")
|
|
|
|
def body():
|
|
threads = []
|
|
for i in range(5):
|
|
threads.append(threading.Thread(target=silenced, args=(i,)))
|
|
threads.append(threading.Thread(target=loud, args=(i,)))
|
|
for t in threads:
|
|
t.start()
|
|
start.set()
|
|
for t in threads:
|
|
t.join(timeout=15.0)
|
|
assert not any(t.is_alive() for t in threads), "straggler thread would truncate captured output"
|
|
|
|
captured = _run_with_real_stream(body)
|
|
for i in range(5):
|
|
assert f"S{i}" not in captured, f"silenced S{i} leaked"
|
|
assert f"L{i}" in captured, f"loud L{i} swallowed"
|