Make narrowed backend test runs and the quarantine test order- and load-proof

The session db fixture now disposes the engine before deleting the file, since a
module under a no-op db (tests/sdk, tests/flow) may have connected first; the
sdk import tests forget the study packages they import; the supervision tests
keep the real quarantine length so it cannot expire mid-assertion.
This commit is contained in:
2026-10-05 16:54:00 +02:00
parent 43e777b7c7
commit f14f7bd413
3 changed files with 23 additions and 7 deletions
+3
View File
@@ -50,6 +50,9 @@ def db() -> Generator[Session, None, None]:
assert url.database and url.database.endswith("_test.db"), url.database
path = Path(url.database)
# A module that ran before this fixture (one under a no-op `db`) may have
# connected already; a pooled connection would keep the unlinked file.
engine.dispose()
path.unlink(missing_ok=True)
# The real path, so the suite runs against a database built the way a
# deployment's is — including the alembic stamp the app's own startup
+10 -7
View File
@@ -6,16 +6,16 @@ import pytest
from fluksio.flow import supervision
from fluksio.flow.events import EventBus
from fluksio.flow.supervision import FAILURE_BUDGET, Supervisor
from fluksio.flow.supervision import FAILURE_BUDGET, QUARANTINE_BACKOFF, Supervisor
@pytest.fixture(autouse=True)
def no_backoff(monkeypatch: pytest.MonkeyPatch):
"""Real backoff would make these tests a minute long."""
monkeypatch.setattr(supervision, "BACKOFF", (0.0,))
# Long enough that a quarantine holds for the length of an assertion, short
# enough that the test asking it to expire does not wait five minutes.
monkeypatch.setattr(supervision, "QUARANTINE_BACKOFF", (0.05,))
# The quarantine keeps its real length, so it holds until a test cancels it
# however slow the run is; 50ms here ran out mid-assertion on a loaded run.
# The one test that wants it to expire shortens it itself.
async def _settle() -> None:
@@ -85,7 +85,7 @@ def test_a_flow_that_keeps_crashing_is_stood_down():
assert supervisor.quarantined == {"heating"}
assert [e["type"] for e in events].count("task_crashed") == FAILURE_BUDGET
quarantine = [e for e in events if e["type"] == "flow_quarantined"][-1]
assert quarantine["retry_in_s"] == 0.05
assert quarantine["retry_in_s"] == QUARANTINE_BACKOFF[0]
await supervisor.cancel_all()
asyncio.run(scenario())
@@ -142,8 +142,11 @@ def test_two_tasks_on_one_flow_do_not_spend_each_others_budget():
asyncio.run(scenario())
def test_a_quarantine_expires_and_the_task_tries_again():
def test_a_quarantine_expires_and_the_task_tries_again(
monkeypatch: pytest.MonkeyPatch,
):
"""The whole point: a broker that comes back is picked up without a human."""
monkeypatch.setattr(supervision, "QUARANTINE_BACKOFF", (0.05,))
attempts = 0
async def loop() -> None:
@@ -161,7 +164,7 @@ def test_a_quarantine_expires_and_the_task_tries_again():
assert attempts == FAILURE_BUDGET
assert supervisor.quarantined == {"power"}
# Sit out the rest, which the fixture has shortened to 50ms.
# Sit out the rest, shortened to 50ms above.
await asyncio.sleep(0.1)
await _settle()
+10
View File
@@ -3,9 +3,19 @@
import sys
import textwrap
import pytest
from fluksio.sdk.imports import digest_of, reached
@pytest.fixture(autouse=True)
def _forget_studies():
"""The packages below are temporary files under names other tests use too."""
yield
for name in [n for n in sys.modules if n.split(".")[0] in ("study", "arm")]:
del sys.modules[name]
def _write(root, name, body):
path = root / f"{name}.py"
path.write_text(textwrap.dedent(body).lstrip())