Skip to content

tests: speed up unit tests by running them concurrently#5980

Draft
Bobronium wants to merge 6 commits into
mainfrom
arseny/concurrent-tests
Draft

tests: speed up unit tests by running them concurrently#5980
Bobronium wants to merge 6 commits into
mainfrom
arseny/concurrent-tests

Conversation

@Bobronium
Copy link
Copy Markdown
Member

Running pytest --unit with these changes goes from 3 minutes 44 seconds to 44 seconds.
Just test_agent_session.py finishes under 8 seconds instead of 1 minute 45 seconds.

How does it work

New plugin will group all tests marked as concurrent by their module and execute them in a shared event loop. This shrinks wall time to the slowest test + raw compute. This also preserves some level of isolation. contextvars are used to preserve input capture and track leaked tasks.

All unit tests are marked as concurrent by default. Tests from other categories are unaffected, unless explicit --concurrent option is used when launching pytest.

To disable concurrent execution, --no-concurrent option can be used.
To disable per-module or per-test, pytest.mark.no_concurrent can be used.

Changes

  • Add pytest-asyncio-concurrent — gives the speedup
  • Add tests/concurrent.py that extends on pytest-asyncio-concurrent and adds crucial features:
    • Captures stdout/stderr and reports on failure (same as pytest)
    • Allows leaked tasks detection in concurrent mode (so that fail_on_leaked_tasks continues to work)
    • adds --concurrent/--no-concurrent options
    • Adds live progress
  • Mark all unit tests except tests/test_ipc.py and tests/test_audio_decoder.py as concurrent

No tests were changed.

This PR:

pytest --unit
742 passed, 25 deselected, 632 warnings in 44.33s

main:

pytest --unit
742 passed, 25 deselected, 645 warnings in 224.53s (0:03:44)
pytest --unit  -n 4
742 passed, 648 warnings in 116.79s (0:01:56)

Co-authored-by: Claude <noreply@anthropic.com>
@Bobronium Bobronium force-pushed the arseny/concurrent-tests branch from 4cb205f to d27a648 Compare June 5, 2026 17:34
Bobronium and others added 5 commits June 5, 2026 18:17
…ture

The shared event loop couples timing across a concurrent group, so the tight
speed_factor=5.0 pacing in test_user_turn_exceeded flaked. Add an is_concurrent
fixture (a single isinstance on the test node) and use speed = 3.0 if is_concurrent
else 5.0, relaxing pacing only on the concurrent path and leaving sequential runs
unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
The text-input turn is scheduled out-of-band, so on a shared event loop run_session's
teardown can race its reply. Bump drain_delay to 2.0 only when is_concurrent, leaving the
sequential path at 0.5.

Co-authored-by: Claude <noreply@anthropic.com>
Its text-input interruption is coupled to the play-out clock (the interrupted message
only commits once the synchronized transcript is non-empty), so it can't run on the shared
concurrent loop. Mark it no_concurrent and widen drain_delay so the out-of-band reply also
clears the teardown margin when run sequentially. Reverts the earlier is_concurrent attempt.

Co-authored-by: Claude <noreply@anthropic.com>
Bring the deterministic virtual-time harness onto the concurrent-tests branch
and mark every time-dependent module no_concurrent.

- add tests/virtual_time.py + wire event_loop_policy / _virtual_wall_clock /
  marker registration into conftest; add async-solipsism dev dep
- virtual_time modules run no_concurrent: a concurrent group shares one event
  loop, which bypasses the per-test autojump loop and races the process-global
  clock patch + validator swap (test_agent_session fails outright concurrent;
  others just lose the speedup). Measured concurrent vs --no-concurrent per
  module: no_concurrent is faster or equal everywhere, dramatically so for the
  heavy suites.
- revert the concurrency-driven relaxations now that these run sequentially:
  restore test_user_turn_exceeded (speed=5.0, drop is_concurrent param) and
  test_agent_session (drain_delay, drop single-test no_concurrent) to strict
  bodies; remove the now-unused is_concurrent fixture
- mark module-level no_concurrent on all 17 virtual_time modules
- suppress the event_loop_policy override deprecation warning

Co-authored-by: Claude <noreply@anthropic.com>
- remove the unit->concurrent auto-promotion (pytest_collectstart); every
  async unit module now declares concurrent/no_concurrent explicitly. Mark the
  15 async unit modules concurrent (incl. test_interruption/test_interruption_
  failover); leave sync-only modules as plain unit (concurrency is async-only,
  so a concurrent marker there is a no-op).
- fix the virtual clock leaking into non-loop callers: _now/_perf returned a
  frozen 1.7e9 epoch when no loop was running, so any background thread (e.g.
  the OpenTelemetry metrics exporter) reading time during a virtual-time test
  got a 2023 timestamp the gateway rejects. Scope virtual time to the
  async-solipsism loop and return the real clock everywhere else.

Co-authored-by: Claude <noreply@anthropic.com>
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.

1 participant