diff --git a/tests/conftest.py b/tests/conftest.py index 5dfef27..0caf9f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -275,3 +275,34 @@ def _fast_sleep(seconds): except Exception: pass yield + + + +@pytest.fixture(autouse=True) +def _isolated_wal(monkeypatch, tmp_path): + # CI flakefix (Sprint 0 follow-up): every test gets a private + # ``NULLRUN_WAL_PATH`` so ``Transport._replay_from_wal`` cannot + # replay events from a previous run / parallel xdist worker / + # failed teardown against the real backend. + # + # Root cause (observed on run 29809829695 job 88568154484): + # ``NullRunRuntime.__init__`` calls ``self._transport.start()`` + # which calls ``_replay_from_wal()``. With no monkeypatched + # ``NULLRUN_WAL_PATH``, the SDK reads the default + # ``tempfile.gettempdir()/nullrun.wal`` and tries to drain any + # events found there against the real ``api_url``. The + # real-backend httpx call hits ``/api/v1/track/batch`` with a + # placeholder test key, the backend returns 401, and + # ``NullRunAuthError`` propagates back into the test fixture + # setup — failing any test that builds a runtime via + # ``NullRunRuntime(api_key=..., _test_mode=True)`` without the + # ``make_test_runtime`` fixture. CI 3.12 hits this race more + # often than 3.10/3.11 due to thread-scheduling differences + # in ``Transport.start()``. + # + # ``make_test_runtime`` already pins ``NULLRUN_WAL_PATH`` per + # factory call; this autouse covers tests that build a runtime + # inline (e.g. ``test_state_compare_case_insensitive.py:28`` + # and ``test_v3_wire_contract.py::TestPingChainScheduler``). + monkeypatch.setenv("NULLRUN_WAL_PATH", str(tmp_path / "sdk.wal")) + yield diff --git a/tests/test_webhook_backoff.py b/tests/test_webhook_backoff.py index fa67c16..efa45dc 100644 --- a/tests/test_webhook_backoff.py +++ b/tests/test_webhook_backoff.py @@ -8,6 +8,22 @@ Post-fix the schedule is ``0.5 * 2**attempt`` capped at 30s: 0.5s, 1.0s, 2.0s, 4.0s, 8.0s, 16.0s, 30.0s (cap). + +These tests mock ``nullrun.actions.time.sleep`` directly via +``unittest.mock.patch``. The conftest autouse ``_fast_sleep`` +fixture caps test-code ``time.sleep`` at 1ms, which does NOT +interfere with the per-test ``patch`` (the patch goes through +``unittest.mock`` and replaces the sleep function inside +``with``; the autouse cap is active outside the ``with`` block). +However, the singleton ``_action_handler`` module-level +webhook-delivery thread started by another test in the same +process may call ``time.sleep(0.5)`` (its idle poll) at exactly +the moment this test enters the assertion — and on Python 3.11 +under xdist the singleton's ``sleeps`` collection was visible +on the assertion path in CI run 29814323742. Marking the whole +module ``@pytest.mark.slow_sleep`` opts out of the autouse +cap so the sleep calls in the test body and the singleton +idle poll use real wall-clock sleeps. """ import time @@ -18,6 +34,9 @@ from nullrun.actions import ActionHandler, WebhookConfig +pytestmark = pytest.mark.slow_sleep + + def _make_handler_with_webhook(retries: int = 7) -> ActionHandler: """Build an ActionHandler with one registered webhook.