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