test(cursor): stabilize test_no_repost_when_unchanged on idle signal (#2477)

The test synchronized on the wrong signal. `_run_loop_until(...)` exited as
soon as the usage POST landed (`_usage_posts`), but the assertions read the
idle POST (`_idle_posts`). Between the usage POST and the idle POST the loop
does `await asyncio.to_thread(_write_usage_state, ...)`, a real event-loop
yield. Under xdist load the driver poll could slip into that window, so
`_run_loop_until` returned and its `finally: task.cancel()` killed the
forwarder before the idle POST was emitted → `_idle_posts` empty → assert
0 == 1.

Gate on `_idle_posts` instead. The idle POST is the last side effect of
processing turn 1, so once it lands both the usage POST and the state write
have already completed and both assertions become race-free. The
`asyncio.sleep(0.1)` upper-bound check is unchanged.

Co-authored-by: omnigent <noreply@omnigent.ai>
This commit is contained in:
Pat Sukprasert
2026-07-13 18:45:25 +08:00
committed by GitHub
parent 317592e2af
commit 0838d5f7cc
+4 -1
View File
@@ -330,7 +330,10 @@ class TestForwardLoop:
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
usage.record_usage_payload(tmp_path, _TURN1)
client = await _run_loop_until(monkeypatch, tmp_path, _usage_posts)
# Gate on the idle POST: it is the LAST side effect of processing turn 1
# (usage POST, then the state write, then idle), so once it lands both
# assertions below read fully-settled state instead of racing the write.
client = await _run_loop_until(monkeypatch, tmp_path, _idle_posts)
# Let several more polls run; with no new turns there must be no 2nd
# usage POST and no further idle edge.
await asyncio.sleep(0.1)