mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-18 21:50:55 +00:00
7b60151cc1
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.
83 lines
3.0 KiB
Python
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
|