Fail fast with one clear diagnostic when app_server dies mid-session

A shared-server crash mid-suite previously cascaded into hundreds of
opaque connection-refused errors across every later api/e2e test,
making the real cause invisible. pytest_runtest_setup now polls the
tracked subprocess and, on the first test after it exits, reports the
exit code plus the server's own log tail once instead of forcing every
subsequent test to fail blind.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
retoor 2026-08-15 20:25:39 +02:00
parent 72e11db185
commit 682be0861f

View File

@ -125,6 +125,13 @@ def _port_in_use(port):
return sock.connect_ex(("127.0.0.1", port)) == 0
# Set once app_server has successfully started, so pytest_runtest_setup can
# detect a mid-session crash (e.g. resource exhaustion on a constrained CI
# runner) and fail every subsequent HTTP-dependent test with one clear
# diagnostic instead of a wall of opaque connection-refused errors.
_APP_SERVER_STATE = {"proc": None, "log_path": None}
@pytest.fixture(scope="session")
def app_server(test_db_path):
if _port_in_use(PORT):
@ -192,7 +199,11 @@ def app_server(test_db_path):
_set_setting("rate_limit_per_minute", "1000000")
_set_setting("rate_limit_window_seconds", "60")
_APP_SERVER_STATE["proc"] = proc
_APP_SERVER_STATE["log_path"] = log_file.name
yield proc
_APP_SERVER_STATE["proc"] = None
try:
proc.terminate()
proc.wait(timeout=10)
@ -207,6 +218,27 @@ def app_server(test_db_path):
pass
def pytest_runtest_setup(item):
proc = _APP_SERVER_STATE["proc"]
if proc is None or "tests/unit/" in item.nodeid:
return
exit_code = proc.poll()
if exit_code is None:
return
log_path = _APP_SERVER_STATE["log_path"]
try:
with open(log_path, "r", errors="replace") as f:
log_tail = f.read()[-5000:]
except OSError:
log_tail = "(log unavailable)"
pytest.fail(
f"app_server died mid-session (exit code {exit_code}). It was healthy "
f"earlier in this run and has since crashed - this is a shared-server "
f"failure, not a bug in {item.nodeid}. Server log tail:\n{log_tail}",
pytrace=False,
)
@pytest.fixture(scope="session")
def playwright_instance():
from playwright.sync_api import sync_playwright