From 682be0861f102d58d5fbb4c47e33b308dc8f360a Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 15 Aug 2026 20:25:39 +0200 Subject: [PATCH] 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 --- tests/conftest.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index dbfc8ef0..4b6b8f46 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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