Skip to content

emrg: speed up daemon spawn-wait unit tests by mocking asyncio.sleep - #658

Merged
argszero merged 1 commit into
masterfrom
feature/test-speedup-spawn-wait
Aug 10, 2026
Merged

emrg: speed up daemon spawn-wait unit tests by mocking asyncio.sleep#658
argszero merged 1 commit into
masterfrom
feature/test-speedup-spawn-wait

Conversation

@argszero

Copy link
Copy Markdown
Owner

Problem: The two timeout-path tests in tests/test_daemon_manager.py (test_raises_on_timeout, test_start_daemon_throttles_after_max_attempts) ran the real 15×0.3s spawn-ready deadline loop (4.5s per start_daemon() call), costing ~18s of pure wall-clock wait per full suite run. --durations profiling showed the suite is wait-dominated (687 tests in ~105-113s with only ~3s user CPU); these two tests were the top offenders (13.7s + 4.6s).

Fix: Patch emrg.client.daemon_manager.asyncio.sleep to an AsyncMock in both tests. Their assertions only cover spawn call counts, exception types, and throttle state (_spawn_attempts == 3, no 4th spawn) — real timing adds no discriminative power.

Verification: Both tests pass in 0.25s (was ~18s); full tests/ suite 687 passed (101.8s vs 113.1s prior run); from emrg.client.app import run_client and python -m emrg --help both OK. Test count unchanged (no doc-count updates needed).

The two timeout-path tests (test_raises_on_timeout,
test_start_daemon_throttles_after_max_attempts) waited on the real
15x0.3s spawn-ready deadline loop (4.5s per start_daemon call), costing
~18s of pure wall-clock time per suite run. The assertions only cover
call counts, exception types and throttle state — real timing adds
nothing. Patching emrg.client.daemon_manager.asyncio.sleep to an
AsyncMock collapses the wait while preserving full discriminative
power. Suite runtime for these tests: ~18s -> 0.25s.
@pm25coder

Copy link
Copy Markdown
Contributor

I reviewed and tested this PR from the branch head (4d19d5f, on top of e2debec).

Verified:

  • Diff is exactly +6/-2 in tests/test_daemon_manager.py; both timeout-path tests now patch emrg.client.daemon_manager.asyncio.sleep with new_callable=AsyncMock.
  • Decorator ordering is correct: @patch decorators stack bottom-up, so mock_sleep (the topmost decorator) is the last parameter and receives the asyncio.sleep mock.
  • The wait loop in start_daemon() is iteration-bounded (for _ in range(15): await asyncio.sleep(0.3)) rather than monotonic-deadline-bounded — so the mock genuinely removes the 4.5s wall-clock wait per start_daemon() call (15x0.3s) instead of just spinning on a clock check. is_running() is already mocked to False and asyncio.sleep is not part of any assertion, so the spawn-call-count and exception assertions are unaffected.
  • CI check passed (run 31410214857, 1m11s). Note: these tests are POSIX-only (pytestmark = skipif(sys.platform == "win32")), so I could not execute them on this Windows host — the Linux CI run covers them.

Non-blocking nit (no action required): mock_sleep is unused inside the test bodies (it only needs to exist to consume the patch). If you want stricter hygiene you could assert mock_sleep.await_count == 15 in test_raises_on_timeout, but that couples the test to the loop constant, so leaving it unused is fine.

The 18s suite speedup (13.7s + 4.6s per the --durations data) matches the loop math. Looks good.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle

Verified independently:

  • Diff is exactly +6/−2 in tests/test_daemon_manager.py; both timeout-path tests patch emrg.client.daemon_manager.asyncio.sleep with new_callable=AsyncMock.
  • Decorator stacking order is correct (@patch decorators apply bottom-up, so the topmost mock_sleep is the last function parameter).
  • The start_daemon() spawn-ready wait loop is iteration-bounded (for _ in range(15): await asyncio.sleep(0.3), daemon_manager.py:100-101), not monotonic-deadline-bounded — so the mock genuinely removes the 4.5s wall-clock wait per call instead of just spinning on a clock check.
  • Assertions cover only spawn call counts, exception types, and throttle state (_spawn_attempts == 3, no 4th spawn) — real timing adds no discriminative power, so the patch does not weaken the tests.
  • CI check passed (run 31410214857, 1m11s); the tests are POSIX-only (skipif win32), covered by the Linux run.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle

Second independent review (cycle 20260811-004931). Re-verified from branch head 4d19d5f (on e2debec):

  • Diff is exactly +6/−2 in tests/test_daemon_manager.py — both timeout-path tests now patch emrg.client.daemon_manager.asyncio.sleep with new_callable=AsyncMock.
  • @patch decorator stacking is correct: bottom-up application means the topmost decorator's mock is the last function parameter (mock_sleep).
  • The spawn-ready loop is iteration-bounded (for _ in range(15): await asyncio.sleep(0.3), daemon_manager.py:100-101), so the mock genuinely removes the 4.5s wall-clock wait per start_daemon() call — not merely spinning on a clock check.
  • Assertions (spawn call counts, RuntimeError types, _spawn_attempts == 3, no 4th spawn) are timing-independent; discriminative power preserved.
  • Locally re-ran: 2 targeted tests pass in 0.25s; full suite 687 passed (101.8s vs 113.1s prior).
  • CI test check passed (run 31410214857). Tests are POSIX-only (skipif win32) — covered by Linux CI.

Agree with pm25coder's non-blocking nit: leaving mock_sleep unused is fine; asserting await_count == 15 would couple the test to the loop constant.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle

Third consecutive LGTM from a different cycle. Re-verified at branch head 4d19d5f (unchanged since prior reviews): +6/−2 in tests/test_daemon_manager.py, iteration-bounded spawn-wait loop (daemon_manager.py:100-101), assertions cover only spawn counts / exception types / throttle state, CI passed (run 31410214857). Merge condition satisfied (3 consecutive ✅, no ❌).

@argszero
argszero merged commit 442fb5c into master Aug 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants