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).
134 lines
4.2 KiB
Python
134 lines
4.2 KiB
Python
import io
|
|
import json
|
|
import os
|
|
import sys
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tui_gateway.compute_host import ComputeHost, _default_workers
|
|
from tui_gateway.host_supervisor import (
|
|
MUTATOR_ROUTE_TABLE,
|
|
HostSupervisor,
|
|
append_log_record,
|
|
)
|
|
|
|
|
|
def _json_lines(out: io.StringIO) -> list[dict]:
|
|
frames = []
|
|
for line in out.getvalue().splitlines():
|
|
if line.strip():
|
|
frames.append(json.loads(line))
|
|
return frames
|
|
|
|
|
|
def _wait_for_frame(out: io.StringIO, predicate, timeout: float = 2.0) -> dict:
|
|
deadline = time.monotonic() + timeout
|
|
while time.monotonic() < deadline:
|
|
for frame in _json_lines(out):
|
|
if predicate(frame):
|
|
return frame
|
|
time.sleep(0.01)
|
|
raise AssertionError(f"timed out waiting for frame; saw={_json_lines(out)}")
|
|
|
|
|
|
def test_compute_host_workers_inherit_tui_pool_env_or_8(monkeypatch):
|
|
monkeypatch.delenv("HERMES_TUI_RPC_POOL_WORKERS", raising=False)
|
|
monkeypatch.delenv("HERMES_COMPUTE_HOST_WORKERS", raising=False)
|
|
assert _default_workers() == 8
|
|
|
|
monkeypatch.setenv("HERMES_TUI_RPC_POOL_WORKERS", "11")
|
|
assert _default_workers() == 11
|
|
|
|
# Dead-RC tombstone: malformed env falls back to 8, not the old except-branch 4.
|
|
monkeypatch.setenv("HERMES_TUI_RPC_POOL_WORKERS", "not-an-int")
|
|
assert _default_workers() == 8
|
|
|
|
|
|
def test_mutator_route_table_matches_prd_inventory():
|
|
assert MUTATOR_ROUTE_TABLE == {
|
|
"prompt.submit": "turn-path",
|
|
"session.interrupt": "turn-path",
|
|
"reload.mcp": "run-concurrent",
|
|
"session.save": "run-concurrent",
|
|
"session.compress": "idle-gated",
|
|
"prompt.submit.truncate": "idle-gated",
|
|
"slash.model": "idle-gated",
|
|
"slash.personality": "idle-gated",
|
|
"slash.prompt": "idle-gated",
|
|
"slash.compress": "idle-gated",
|
|
"session.reset": "idle-gated",
|
|
"session.history.reload": "idle-gated",
|
|
"slash.retry": "idle-gated",
|
|
}
|
|
|
|
|
|
def test_append_log_record_single_write_lines(tmp_path):
|
|
path = tmp_path / "agent.log"
|
|
|
|
def writer(i: int) -> None:
|
|
append_log_record(path, f"line-{i:03d}-" + ("x" * 2000))
|
|
|
|
threads = [threading.Thread(target=writer, args=(i,)) for i in range(32)]
|
|
for thread in threads:
|
|
thread.start()
|
|
for thread in threads:
|
|
thread.join()
|
|
|
|
lines = path.read_text(encoding="utf-8").splitlines()
|
|
assert len(lines) == 32
|
|
assert sorted(line.split("-", 2)[1] for line in lines) == [f"{i:03d}" for i in range(32)]
|
|
assert all(line.endswith("x" * 2000) for line in lines)
|
|
|
|
|
|
def test_supervisor_startup_reconcile_pid_reuse_guard(tmp_path, monkeypatch):
|
|
registry = tmp_path / "dashboard-compute-host.json"
|
|
registry.write_text(json.dumps({"host_pid": os.getpid(), "boot_id": "stale"}), encoding="utf-8")
|
|
|
|
killed: list[int] = []
|
|
supervisor = HostSupervisor(registry_path=registry, argv=[sys.executable, "-c", ""], autostart=False)
|
|
monkeypatch.setattr(supervisor, "_pid_matches_compute_host", lambda _pid: False)
|
|
monkeypatch.setattr(supervisor, "_terminate_pid", lambda pid, **_kw: killed.append(pid))
|
|
|
|
result = supervisor.reconcile_startup_orphan()
|
|
|
|
assert result == "pid-reuse-ignored"
|
|
assert killed == []
|
|
assert not registry.exists()
|
|
|
|
|
|
def _make_compress_host_session(events: list) -> dict:
|
|
class _Agent:
|
|
model = "host-model"
|
|
provider = "host-provider"
|
|
tools = []
|
|
_cached_system_prompt = ""
|
|
session_input_tokens = 1
|
|
session_output_tokens = 1
|
|
session_prompt_tokens = 1
|
|
session_completion_tokens = 1
|
|
session_total_tokens = 2
|
|
session_api_calls = 1
|
|
session_id = "rotated-id"
|
|
|
|
agent = _Agent()
|
|
agent.context_compressor = type("ContextEngineStub", (), {})()
|
|
agent.context_compressor.on_session_start = (
|
|
lambda *_args, **_kwargs: events.append("notify")
|
|
)
|
|
return {
|
|
"agent": agent,
|
|
"session_key": "before-key",
|
|
"history": [
|
|
{"role": "user", "content": "before"},
|
|
{"role": "assistant", "content": "before"},
|
|
],
|
|
"history_lock": threading.Lock(),
|
|
"history_version": 2,
|
|
"running": False,
|
|
"manual_compression_lock": threading.Lock(),
|
|
}
|
|
|
|
|