Problem
tldw_Server_API/tests/helpers/app_main_state.py::reload_app_main() replaces sys.modules["tldw_Server_API.app.main"] with a freshly imported module (new app object) and never restores the original, even though the helper ships snapshot_app_main() / restore_app_main() for exactly that.
Consequence chain (empirically verified during #2581 / PR #2583 review, with a probe plugin logging app identities — three distinct app objects observed in one Embeddings run):
- All test modules import
app at collection time → they pin the ORIGINAL app object.
- Any test calling
reload_app_main() (e.g. tests/Embeddings/test_backpressure_and_quotas.py:106,254; ~15 files repo-wide use main reloads) swaps sys.modules permanently.
- A later
with TestClient(app) lifespan exit marks the pinned ORIGINAL app draining (mark_lifecycle_shutdown).
- The root conftest's autouse reset (
tests/conftest.py::_reset_main_app_lifecycle_state_between_tests) re-imports app.main at reset time — so it resets only the NEW app. The drained original stays drained, and every subsequent request through a pinned client gets 503 {"status":"not_ready","reason":"shutdown_in_progress"} from DrainGateMiddleware.
This caused all 46 Embeddings directory-run failures (every file passed solo). PR #2583 adds a suite-local mitigation (tests/Embeddings/conftest.py::_reset_app_lifecycle_state resetting the pinned instance), but the hazard remains for ANY suite that mixes reload_app_main() callers with pinned-app lifespan tests.
Suggested fix
Make reload_app_main() callers restore the snapshot (fixture-style):
@pytest.fixture
def reloaded_app_main():
snap = snapshot_app_main()
try:
yield reload_app_main()
finally:
restore_app_main(snap)
or have the root conftest reset track all app instances it has seen (e.g. a WeakSet registered at app construction) instead of only the current sys.modules one.
Repro
Remove the _reset_app_lifecycle_state fixture from tests/Embeddings/conftest.py and run:
pytest tldw_Server_API/tests/Embeddings -q # 46 failed, all 503 shutdown_in_progress
Problem
tldw_Server_API/tests/helpers/app_main_state.py::reload_app_main()replacessys.modules["tldw_Server_API.app.main"]with a freshly imported module (newappobject) and never restores the original, even though the helper shipssnapshot_app_main()/restore_app_main()for exactly that.Consequence chain (empirically verified during #2581 / PR #2583 review, with a probe plugin logging app identities — three distinct
appobjects observed in one Embeddings run):appat collection time → they pin the ORIGINAL app object.reload_app_main()(e.g.tests/Embeddings/test_backpressure_and_quotas.py:106,254; ~15 files repo-wide use main reloads) swapssys.modulespermanently.with TestClient(app)lifespan exit marks the pinned ORIGINAL app draining (mark_lifecycle_shutdown).tests/conftest.py::_reset_main_app_lifecycle_state_between_tests) re-importsapp.mainat reset time — so it resets only the NEW app. The drained original stays drained, and every subsequent request through a pinned client gets 503{"status":"not_ready","reason":"shutdown_in_progress"}fromDrainGateMiddleware.This caused all 46 Embeddings directory-run failures (every file passed solo). PR #2583 adds a suite-local mitigation (
tests/Embeddings/conftest.py::_reset_app_lifecycle_stateresetting the pinned instance), but the hazard remains for ANY suite that mixesreload_app_main()callers with pinned-app lifespan tests.Suggested fix
Make
reload_app_main()callers restore the snapshot (fixture-style):or have the root conftest reset track all app instances it has seen (e.g. a WeakSet registered at app construction) instead of only the current
sys.modulesone.Repro
Remove the
_reset_app_lifecycle_statefixture fromtests/Embeddings/conftest.pyand run: