Skip to content

Speed Python up tests with session-scoped db fixtures - #4088

Merged
r4victor merged 11 commits into
masterfrom
pr_speed_up_tests
Aug 4, 2026
Merged

Speed Python up tests with session-scoped db fixtures#4088
r4victor merged 11 commits into
masterfrom
pr_speed_up_tests

Conversation

@r4victor

@r4victor r4victor commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Significant speedup of the dstack Python test suite. On my MacBook Pro M1:

  • Serial execution without --runpostgres: ~110s to ~40s.
  • Parallel -n auto execution without --runpostgres: ~70s to ~25s.
  • Serial execution with --runpostgres: >350s to ~95s.
  • Parallel -n auto execution with --runpostgres: ~150s to 50s.

The speedup brings all the obvious benefits like faster CI times. My initial motivation was discovering that agents spend >50% of time running the dstack test suite when working on large features/refactoring since they have to run the full test suite. So this also improves agents productivity.

Changes:

  • Now all tests and fixtures run in a session-scoped event loop by default. Before that, every test function got its own loop. This trades isolation for speed: now tests re-use the same db with metadata created once and cleared for every test instead of re-creating the metadata. All current tests work fine with this session-scoped loop isolation – the requirement is that the tests do not leave pending event loop tasks.
  • Mock long timeouts in tests.
  • Cache and reuse RSA keys in tests.

r4victor added 11 commits August 4, 2026 10:01
Three fleet delete tests asserted lock contention errors by letting the
retry loop exhaust, costing 5s each, and the gateway timeout test waited
out the 8s replica client timeout shared by the whole module.

Name the fleet retry bounds so tests can drop the wait, and give the
timeout test its own short-timeout fixture; the shared 8s one stays, since
httpbin needs it to serve the other tests. Also skip the sleep after the
final attempt, which delayed the error response by 500ms in production.
test_db built a new engine and ran create_all per test, which cost ~20ms each,
and a fresh engine also discarded SQLAlchemy's compiled statement cache and
re-ran the connect PRAGMAs. Create the schema once per session and clear the
rows between tests instead, mirroring what the Postgres path already does.

Sharing a session-scoped engine needs a session-scoped event loop, so tests
must no longer leave pending tasks behind; none do today.

Cuts the serial suite from ~112s to ~57s.
The Postgres path built a new Database per test and cleaned up with TRUNCATE.
TRUNCATE takes an exclusive lock and rewrites files, costing ~90ms for these
tables against ~2ms to delete the rows, and a new engine per test also discarded
the compiled statement cache. Share the session-wide database and clear rows the
same way SQLite does, batching the deletes into one statement since a round trip
per table dominates over a socket. Also drop fsync in the container: a test
database never has to survive a crash.

Two plan tests asserted the order of instances returned by a query that does not
order them; TRUNCATE hid this by resetting the heap every test.

Cuts the suite with --runpostgres from ~339s to ~147s.
Tests generated 104 key pairs costing 7.7s: the server generates one per user,
project, gateway, and job, and a 2048-bit key takes ~80ms. Reuse a single pair
for the whole session.

Route the two remaining call sites through the `crypto` module so all four
resolve the function at call time and one patch covers them.
Clearing 32 tables with a statement each cost ~3.4ms per test against ~0.8ms
sent together. SQLite has no multi-statement execute, so use `executescript`
on the driver connection; Postgres already batched via a DO block.
Fleet spec validation parsed the same test keys 20 times at ~190ms each, 3.8s in
total, because paramiko validates an RSA key on load. Cache the parses for the
session, and route the call site through the `ssh` module so one patch covers it.
The SSH key fixture sat in the server conftest to avoid importing paramiko for
non-server runs, but the root conftest already pulls it in transitively, so there
was nothing to avoid. Placing both together also covers the CLI fleet
configurator and drops the order-dependent patching.
@r4victor
r4victor merged commit ded4a88 into master Aug 4, 2026
27 checks passed
@r4victor
r4victor deleted the pr_speed_up_tests branch August 4, 2026 11:18
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