Hermes-agent
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Tests for tui_gateway.entry.wait_for_mcp_discovery (PR #35245).
|
||||
|
||||
MCP tool discovery runs in a background daemon thread so a slow/dead server
|
||||
can't freeze ``gateway.ready``. The agent snapshots its tool list once at
|
||||
build time and never re-reads it, so ``_make_agent`` briefly joins the
|
||||
discovery thread before building — bounded, so a dead server can't re-introduce
|
||||
the startup hang, and a no-op once discovery has finished.
|
||||
"""
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
import tui_gateway.entry as entry
|
||||
|
||||
|
||||
def _restore_thread_slot(saved):
|
||||
entry._mcp_discovery_thread = saved
|
||||
|
||||
|
||||
def test_no_thread_is_noop():
|
||||
"""When no discovery thread was started (the common no-MCP case), the
|
||||
helper returns immediately and never blocks."""
|
||||
saved = entry._mcp_discovery_thread
|
||||
try:
|
||||
entry._mcp_discovery_thread = None
|
||||
start = time.monotonic()
|
||||
entry.wait_for_mcp_discovery(timeout=5.0)
|
||||
assert time.monotonic() - start < 0.1
|
||||
finally:
|
||||
_restore_thread_slot(saved)
|
||||
|
||||
|
||||
def test_already_finished_thread_is_noop():
|
||||
"""A thread that has already finished is not joined-on (dead thread)."""
|
||||
saved = entry._mcp_discovery_thread
|
||||
try:
|
||||
t = threading.Thread(target=lambda: None, daemon=True)
|
||||
t.start()
|
||||
t.join() # ensure it's finished
|
||||
entry._mcp_discovery_thread = t
|
||||
start = time.monotonic()
|
||||
entry.wait_for_mcp_discovery(timeout=5.0)
|
||||
assert time.monotonic() - start < 0.1
|
||||
finally:
|
||||
_restore_thread_slot(saved)
|
||||
|
||||
|
||||
def test_fast_thread_is_joined():
|
||||
"""A reachable-but-still-connecting (fast) server lands before the agent
|
||||
snapshots tools — the helper waits for it to finish."""
|
||||
saved = entry._mcp_discovery_thread
|
||||
try:
|
||||
t = threading.Thread(target=lambda: time.sleep(0.05), daemon=True)
|
||||
t.start()
|
||||
entry._mcp_discovery_thread = t
|
||||
entry.wait_for_mcp_discovery(timeout=1.0)
|
||||
assert not t.is_alive() # joined to completion
|
||||
finally:
|
||||
_restore_thread_slot(saved)
|
||||
|
||||
|
||||
def test_hung_thread_is_bounded_by_timeout():
|
||||
"""A slow/dead server must NOT re-introduce the startup hang — the join is
|
||||
bounded by the timeout and returns even though the thread is still alive."""
|
||||
saved = entry._mcp_discovery_thread
|
||||
stop = threading.Event()
|
||||
try:
|
||||
t = threading.Thread(target=stop.wait, daemon=True) # blocks until set
|
||||
t.start()
|
||||
entry._mcp_discovery_thread = t
|
||||
start = time.monotonic()
|
||||
entry.wait_for_mcp_discovery(timeout=0.3)
|
||||
elapsed = time.monotonic() - start
|
||||
assert 0.25 <= elapsed < 1.0 # bounded near the timeout, not forever
|
||||
assert t.is_alive() # thread still running; we did not block on it
|
||||
finally:
|
||||
stop.set()
|
||||
_restore_thread_slot(saved)
|
||||
Reference in New Issue
Block a user