Files
hermes-webui/tests/test_conftest_server_boot.py
T
nesquena-hermes 7b60151cc1 Release v0.51.314 — Release KD (test infra: reliable test-server boot + diagnostics) (#3782)
Test-harness only. conftest test-server fixture now captures server output, fails fast on early subprocess death, retries once, raises timeout to 45s, and surfaces the log tail on failure — fixing the recurring ConnectionRefused boot-cascade flake. Full suite 8187 passed, Codex SAFE, Opus SHIP-IT. No app code changed.
2026-06-07 09:38:29 -07:00

83 lines
3.0 KiB
Python

"""Tests for the test-server boot reliability helpers in conftest.py.
These guard the diagnostics that turn an opaque "test server did not start"
timeout (which previously cascaded into hundreds of ConnectionRefused failures
with no clue as to the cause) into a single actionable failure:
* ``_wait_for_server`` returns ``(ok, reason)`` and fails FAST when the server
subprocess has already exited, instead of polling out the whole timeout.
* ``_server_boot_diagnostic`` appends the tail of the captured server log so
an import error / bind failure / traceback is visible in the failure.
"""
from __future__ import annotations
import tests.conftest as conftest
class _FakeProc:
"""Minimal stand-in for subprocess.Popen with a controllable poll()."""
def __init__(self, returncode=None):
self._returncode = returncode
def poll(self):
return self._returncode
@property
def returncode(self):
return self._returncode
def test_wait_for_server_fails_fast_on_early_exit(tmp_path):
"""A dead subprocess must short-circuit the wait, not poll the full timeout."""
log = tmp_path / "server.log"
log.write_text("Traceback (most recent call last):\nImportError: boom\n", encoding="utf-8")
dead = _FakeProc(returncode=1)
# Point at a port nothing is listening on; with proc dead we must return
# almost immediately rather than waiting out the (large) timeout.
import time
start = time.time()
ok, reason = conftest._wait_for_server(
"http://127.0.0.1:9", timeout=30, proc=dead, log_path=str(log)
)
elapsed = time.time() - start
assert ok is False
assert elapsed < 5, f"should fail fast on early exit, took {elapsed:.1f}s"
assert "exited early with code 1" in reason
# The captured server output must be surfaced in the diagnostic.
assert "ImportError: boom" in reason
def test_wait_for_server_times_out_with_log_tail(tmp_path):
"""When the process stays alive but never serves, surface the log tail."""
log = tmp_path / "server.log"
log.write_text("starting up...\nstill binding...\n", encoding="utf-8")
alive = _FakeProc(returncode=None)
ok, reason = conftest._wait_for_server(
"http://127.0.0.1:9", timeout=1, proc=alive, log_path=str(log)
)
assert ok is False
assert "timed out" in reason
assert "still binding" in reason
def test_server_boot_diagnostic_handles_missing_log():
"""A missing/None log path must not raise — diagnostics are best-effort."""
msg = conftest._server_boot_diagnostic("headline only", None)
assert msg == "headline only"
msg2 = conftest._server_boot_diagnostic("hl", "/nonexistent/path/12345.log")
assert "hl" in msg2 # does not raise; appends a soft note
def test_server_boot_diagnostic_reports_empty_output(tmp_path):
"""An empty server log should say so rather than appear truncated."""
log = tmp_path / "empty.log"
log.write_text("", encoding="utf-8")
msg = conftest._server_boot_diagnostic("hl", str(log))
assert "no output" in msg