perf(test): clone a database per test instead of a container - #178
Merged
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 9 |
| Duplication | -4 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The fourth item from the CI/test-speed research, and the one the measurements pointed at: 171 tests consumed 86% of all test time, almost entirely in container startup.
What was happening
start_pgbooted a freshpostgres:17on every call — container start, a hardcoded 1500ms settle,CREATE ROLE, then the full migration set. Per test. 170 call sites did this, and the arithmetic lines up exactly with the 171 slow tests.Why not just share it in a
OnceLockBecause it does not work, which I confirmed rather than assumed: nextest runs each test in its own process. Two tests in one binary printed two distinct PIDs. Any process-local sharing is dead on arrival.
So the server has to outlive the test process and be discovered through the environment.
The change
start_pggains a fast path. WithODAL_TEST_PG_ADMIN_URLset, it clones a per-test database from a migrated template on that server:ensure_templatecreates theodal_approle and a migrated template database, once per server. Guarded by a Postgres advisory lock, not a process-localOnce— the racing parties are separate processes. The first to take the lock builds the template; the rest wait and find it there.clone_from_templateissuesCREATE DATABASE <unique> TEMPLATE <template>, a file copy inside Postgres. Measured at ~190ms, against 12–16s to boot and migrate a container.CREATE DATABASE ... TEMPLATErefuses while anything is connected to the template, so leaving it open would break every clone that followed.Unset, nothing changes. A bare
cargo nextest runstill starts a container per test — slowly, but working.TestPgnow holdsOption<ContainerAsync<..>>,Noneon the shared path.start_pg_beforedeliberately keeps starting its own container: a test of a migration needs a server the migration has not been applied to.The ninth copy
Chasing the remaining time turned up
dpp-vault/tests/helpers/mod.rsstarting its ownpostgres:17— 109 call sites across 27 files, the actual bulk of the slow cohort.just harness-checkmissed it because the function was calledstart_postgres, and I had written the rule as^async fn start_pg. A gate written to catch exactly the copy already found. The rule now matchesGenericImage::new("postgres"— starting a Postgres container at all, whatever the function is named — and was verified to fail on the old shape before being trusted.start_postgresandPgContainerkeep their names and delegate to the shared harness, so none of the 109 call sites changed.Measured
pg_integrationsuite (20 tests)dpp-vaultintegration (307 tests)Full integration tier — dal, vault, plugin-host, node — 1042 tests, all passing against the shared server, including the migration tests that still start their own.
Wiring
just test-integrationnow runs the tiers behindscripts/shared-test-pg.sh, which starts one Postgres, pollspg_isreadyrather than sleeping a fixed guess, and removes it on any exit.just test-integration-isolatedkeeps the container-per-test arrangement. Worth having: it is the only thing that proves the fallback still works, and a suspected cross-test interaction deserves re-running under full isolation before it is believed.services:block with a health check, which is the idiomatic form and needs no script.Worth a reviewer's attention
The isolation model changed. Tests previously had a private server; they now have a private database on a shared one. That is the same isolation for anything schema-scoped — which is everything these suites touch — but cluster-scoped state is now shared: roles, advisory locks,
pg_statviews. Nothing in the suite asserts on those today, and all 1042 pass, but it is the assumption a future test could quietly break.The
odal_approle is created once per cluster rather than once per test, andCREATE ROLEfailing as a duplicate is ignored for that reason.Note on the required-status-check change
Merging #177 needed the
mainruleset updated: dropping thecheck-featuresjob orphaned a required check named "Feature-gated code compiles", which could then never report. That was my miss — I removed a job without reading the ruleset first. The check was removed from the required list with the operator's approval, and the ruleset was backed up before the edit. Clippy now runs with those exact features, so coverage went up, not down.Verification
just checkgreen: 842 tests, all gates includingharness-check,cargo audit. Full integration tier run end to end against a shared server: 1042 passing. Fallback path re-verified by runningpg_integrationwith the env var unset.