forked from Zakaria/hermes-agent
Hermes-agent
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
"""Tests for the retry/fallback status buffer helpers on AIAgent.
|
||||
|
||||
These helpers defer noisy retry chatter (rate-limit retries, fallback
|
||||
switches, compression attempts) so users only see the trace when
|
||||
everything ultimately fails. On successful recovery the buffer is
|
||||
silently dropped.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from run_agent import AIAgent
|
||||
|
||||
|
||||
def _make_bare_agent():
|
||||
"""Construct an AIAgent without running __init__ — we only need the
|
||||
buffered-status helpers, which are pure-Python and depend only on a
|
||||
handful of attributes."""
|
||||
agent = object.__new__(AIAgent)
|
||||
agent.log_prefix = ""
|
||||
agent.status_callback = None
|
||||
agent.suppress_status_output = False
|
||||
agent._mute_post_response = False
|
||||
agent._executing_tools = False
|
||||
agent._print_fn = None
|
||||
return agent
|
||||
|
||||
|
||||
def test_buffer_status_accumulates_then_flushes(capsys):
|
||||
agent = _make_bare_agent()
|
||||
emitted = []
|
||||
agent._emit_status = lambda msg: emitted.append(("status", msg))
|
||||
|
||||
agent._buffer_status("⏳ Retrying...")
|
||||
agent._buffer_status("⚠️ Fallback...")
|
||||
|
||||
# Nothing emitted yet — they are buffered.
|
||||
assert emitted == []
|
||||
assert agent._retry_status_buffer == [
|
||||
("status", "⏳ Retrying..."),
|
||||
("status", "⚠️ Fallback..."),
|
||||
]
|
||||
|
||||
# Flush surfaces them in order through _emit_status.
|
||||
agent._flush_status_buffer()
|
||||
assert emitted == [
|
||||
("status", "⏳ Retrying..."),
|
||||
("status", "⚠️ Fallback..."),
|
||||
]
|
||||
# Buffer is drained.
|
||||
assert agent._retry_status_buffer == []
|
||||
|
||||
|
||||
def test_clear_drops_buffered_messages_silently():
|
||||
agent = _make_bare_agent()
|
||||
emitted = []
|
||||
agent._emit_status = lambda msg: emitted.append(msg)
|
||||
|
||||
agent._buffer_status("⏳ Retrying...")
|
||||
agent._buffer_status("⚠️ Fallback...")
|
||||
agent._clear_status_buffer()
|
||||
|
||||
# Nothing was emitted — clear is the success path.
|
||||
assert emitted == []
|
||||
assert agent._retry_status_buffer == []
|
||||
|
||||
# Subsequent flush is a no-op.
|
||||
agent._flush_status_buffer()
|
||||
assert emitted == []
|
||||
|
||||
|
||||
def test_buffer_vprint_replays_via_vprint_with_log_prefix():
|
||||
agent = _make_bare_agent()
|
||||
agent.log_prefix = "[abc] "
|
||||
seen = []
|
||||
agent._vprint = lambda msg, force=False, **kw: seen.append((msg, force))
|
||||
|
||||
agent._buffer_vprint("⚠️ API call failed")
|
||||
agent._flush_status_buffer()
|
||||
|
||||
# Replays through _vprint with force=True and the agent's log_prefix
|
||||
# prepended (matching the original direct-emit format).
|
||||
assert seen == [("[abc] ⚠️ API call failed", True)]
|
||||
|
||||
|
||||
def test_flush_empty_buffer_is_noop():
|
||||
agent = _make_bare_agent()
|
||||
emitted = []
|
||||
agent._emit_status = lambda msg: emitted.append(msg)
|
||||
agent._vprint = lambda msg, force=False, **kw: emitted.append(msg)
|
||||
|
||||
# No buffer attribute yet — flush should be a quiet no-op.
|
||||
agent._flush_status_buffer()
|
||||
assert emitted == []
|
||||
|
||||
# Even after touching the buffer (via clear on an empty/missing buffer).
|
||||
agent._clear_status_buffer()
|
||||
agent._flush_status_buffer()
|
||||
assert emitted == []
|
||||
|
||||
|
||||
def test_re_buffer_after_flush_works():
|
||||
agent = _make_bare_agent()
|
||||
emitted = []
|
||||
agent._emit_status = lambda msg: emitted.append(msg)
|
||||
|
||||
agent._buffer_status("first")
|
||||
agent._flush_status_buffer()
|
||||
agent._buffer_status("second")
|
||||
agent._flush_status_buffer()
|
||||
|
||||
assert emitted == ["first", "second"]
|
||||
|
||||
|
||||
def test_mixed_kinds_replay_through_correct_channels():
|
||||
agent = _make_bare_agent()
|
||||
agent.log_prefix = ""
|
||||
statuses = []
|
||||
vprints = []
|
||||
warns = []
|
||||
agent._emit_status = lambda msg: statuses.append(msg)
|
||||
agent._vprint = lambda msg, force=False, **kw: vprints.append((msg, force))
|
||||
agent._emit_warning = lambda msg: warns.append(msg)
|
||||
|
||||
agent._buffer_status("status-1")
|
||||
agent._buffer_vprint("vprint-1")
|
||||
# Manually mix in a "warn" record to verify the dispatch still works.
|
||||
agent._retry_status_buffer.append(("warn", "warn-1"))
|
||||
agent._buffer_status("status-2")
|
||||
|
||||
agent._flush_status_buffer()
|
||||
|
||||
assert statuses == ["status-1", "status-2"]
|
||||
assert vprints == [("vprint-1", True)]
|
||||
assert warns == ["warn-1"]
|
||||
|
||||
|
||||
def test_flush_swallows_callback_exceptions():
|
||||
agent = _make_bare_agent()
|
||||
seen = []
|
||||
|
||||
def boom(msg):
|
||||
seen.append(msg)
|
||||
raise RuntimeError("simulated callback failure")
|
||||
|
||||
agent._emit_status = boom
|
||||
|
||||
agent._buffer_status("first")
|
||||
agent._buffer_status("second")
|
||||
# Should not raise even though _emit_status raises for every message.
|
||||
agent._flush_status_buffer()
|
||||
|
||||
# Both messages were attempted.
|
||||
assert seen == ["first", "second"]
|
||||
# Buffer drained regardless of failures.
|
||||
assert agent._retry_status_buffer == []
|
||||
Reference in New Issue
Block a user