Repository navigation
test(npu_pipeline): test_dispatcher.py's async mock_post breaks dispatcher.py's sync-call contract #13004
Description
What
Discovered while verifying issue #12979 batch 9 (unrelated scope — services/npu_pipeline/dispatcher.py's HTTP session is a documented RAW carve-out in that batch, not converted). Confirmed pre-existing via a cp-swapped baseline against origin/Dev_new_gui — identical failures with the unmodified file.
autobot-backend/services/npu_pipeline/test_dispatcher.py fails 5/5 of its tests when run standalone:
FAILED test_happy_path_three_workers
FAILED test_worker_drop_mid_pass_failover
FAILED test_single_worker_produces_tokens
FAILED test_worker_drop_no_peer_raises
FAILED test_context_manager_closes_session
Root cause
services/npu_pipeline/test_dispatcher.py:78,:175,:198—mock_postis declaredasync def mock_post(url, json=None): ...and assigned assession_mock.post = mock_post. Production code (dispatcher.py:151,:193) calls it synchronously —async with self._session.post(url, json=payload) as resp:— expecting.post(...)to return an async context manager directly, not a coroutine. Calling anasync deffunction returns a coroutine object without executing its body, so:test_happy_path_three_workers,test_single_worker_produces_tokens:TypeError: 'async for' requires an object with __aiter__ method, got coroutine(the coroutine is passed toasync with/async forunexecuted).test_worker_drop_no_peer_raises: theraiseinsidemock_postnever executes (coroutine body doesn't run until awaited), so the expectedWorkerDropErrorchain doesn't fire as designed; observed failure isTypeError: Expected a BaseException type, but got 'MagicMock'in thepytest.raises(WorkerDropError, ...)context —WorkerDropErroritself resolves to aMagicMock, which also implicates the module-loading indirection below.
services/npu_pipeline/test_dispatcher.py:22-30(_load_module) — the file bypassessys.modulesstubs the root conftest installs forservices.npu_pipeline.*, but the reuse guard (if name in sys.modules and hasattr(sys.modules[name], "__file__")) can pick up a stub module rather than the freshly-loaded one depending on import order, which would explainWorkerDropErrorbinding to aMagicMockattribute instead of the real exception class intest_context_manager_closes_session/test_worker_drop_no_peer_raises.
Evidence
$ python3 -m pytest autobot-backend/services/npu_pipeline/test_dispatcher.py -q
5 failed in 1.00s
Same 5 failures reproduce against origin/Dev_new_gui's unmodified dispatcher.py (cp-swap baseline, no diff).
Suggested fix
Change mock_post in each of the 3 affected tests from async def to a plain def returning the pre-built resp (or an AsyncMock with side_effect), matching the synchronous-call / async-context-manager-return contract HTTPClientManager-style clients use. Separately, audit _load_module's stub-reuse guard against the root conftest's services.npu_pipeline.* stubs.
Model Used
Sonnet (claude-sonnet-5)
Activity
mrveiss commentedon Sep 2, 2026
Closing — the defect is gone from merged base, and every criterion is met. Verified against
origin/Dev_new_gui@cf2d29d54e.Claimed 3
async def mock_postbreakingdispatcher.py's sync-call contract. Measured: 0 remain; 4 syncdef mock_post.mock_postbecomes a plaindefreturning the pre-built responseautobot-backend/services/npu_pipeline/test_dispatcher.py:114, :162, :211, :234— four syncdef mock_post(url, json=None).git grep "async def mock_post"on that file → 0 hits._load_module's stub-reuse guard against the root conftest stubs:35now readsif existing is not None and existing.__dict__.get("__file__"), probing__dict__rather thanhasattr. The reason is stated inline at:30-33: the PEP-562 catch-all__getattr__on the conftest stubs makeshasattrunconditionally true, so the old guard could never distinguish a stub from a real module.WorkerDropErrorbinds the real exception rather than a MagicMock:76WorkerDropError = _dispatcher_mod.WorkerDropError, taken from the real-loaded module and used at:218inpytest.raises(WorkerDropError, match="w0"). Against a MagicMock thatraiseswould not have been meaningful._load_real_modules()at:46-68snapshots_REAL_LOAD_KEYSand restores them in afinally, so the real modules it loads do not leak into later tests. Landed under #13361..test_durations:12226-12230lists all five, so CI collects and runs them. A duration proves collection rather than a green result, but base CI is green and the sync contract the issue describes is now what the code does.Resolved by #13361 rather than by work under this number.
Worth noting for anyone reading this issue later: the
hasattr-vs-__dict__detail at:30-35is the same PEP-562 trap that #13234 is open about — there,_make_pkg_stubfabricates dunders so everygetattr-based stub check silently returns a MagicMock. This file works around it locally; #13234 is the general fix.