|
1 | | -# Test container |
| 1 | +# Test containers |
2 | 2 |
|
3 | | -This is package exposes some useful vitest utilities for writing tests with Postgres, Prisma, and Redis. |
| 3 | +Vitest utilities for writing tests against real Postgres, Prisma, Redis and ClickHouse - we don't mock |
| 4 | +(see the root `CLAUDE.md`), we boot containers. Also exposes a duration-weighted shard sequencer for |
| 5 | +splitting slow suites across CI shards. |
| 6 | + |
| 7 | +## Choosing a fixture |
| 8 | + |
| 9 | +Most tests share one set of containers per vitest worker (booted once, reset between tests) - this is |
| 10 | +much faster than a container per test. Reach for an isolated variant only when a test needs it. |
| 11 | + |
| 12 | +| Fixture | Postgres | Redis | ClickHouse | Use for | |
| 13 | +| --- | --- | --- | --- | --- | |
| 14 | +| `redisTest` | - | shared | - | redis-only tests | |
| 15 | +| `postgresTest` | shared (clone) | - | - | db-only tests | |
| 16 | +| `containerTest` | shared (clone) | shared | shared | the default - needs all three | |
| 17 | +| `isolatedRedisTest` | - | per-test | - | background redis work (see below) | |
| 18 | +| `containerTestWithIsolatedRedis` | shared (clone) | per-test | shared | background redis work + db/clickhouse | |
| 19 | +| `replicationContainerTest` | per-test | per-test | shared | Postgres→ClickHouse logical replication | |
| 20 | + |
| 21 | +"shared (clone)" = one Postgres per worker with a template database; each test gets a fast `CREATE |
| 22 | +DATABASE ... TEMPLATE` clone, so schema isn't re-pushed per test. |
| 23 | + |
| 24 | +### The background-work gotcha |
| 25 | + |
| 26 | +If a test spawns work that **outlives the test body** - a `RunEngine`, a `redis-worker` Worker, a |
| 27 | +`BatchQueue` - and that work isn't fully drained before the test ends, you **must** use an isolated |
| 28 | +redis fixture (`isolatedRedisTest` / `containerTestWithIsolatedRedis`). |
| 29 | + |
| 30 | +On the shared fixture, the leaked background loop keeps polling the one worker-scoped redis after the |
| 31 | +test's clients close, bleeding into the next test. The symptom is an intermittent `"Connection is |
| 32 | +closed"` error or a test that hangs until its timeout. `FLUSHALL` between tests does **not** fix this - |
| 33 | +it clears data, not live connections/loops, so per-test key prefixes won't help either. A plain |
| 34 | +db/redis test with no lingering background work is fine on the shared fixtures. |
| 35 | + |
| 36 | +## Sharding (`./sequencer`) |
| 37 | + |
| 38 | +CI splits the slow suites with `vitest --shard=i/N`. `DurationShardingSequencer` replaces vitest's |
| 39 | +default file-count split with a duration-weighted one: it reads `test-timings.json` at the repo root |
| 40 | +(`{ "<repo-relative path>": <ms> }`) and greedily bin-packs files so each shard does roughly equal |
| 41 | +*work*, not an equal *number of files*. The packing is deterministic, so every shard computes the same |
| 42 | +bins and runs each file exactly once. |
| 43 | + |
| 44 | +Configs opt in via: |
| 45 | + |
| 46 | +```ts |
| 47 | +import { DurationShardingSequencer } from "@internal/testcontainers/sequencer"; |
| 48 | +// in defineConfig: |
| 49 | +test: { sequence: { sequencer: DurationShardingSequencer } } |
| 50 | +``` |
| 51 | + |
| 52 | +### Adding tests - nothing to do |
| 53 | + |
| 54 | +New test files are discovered by vitest's glob and sharded automatically. A file with no entry in |
| 55 | +`test-timings.json` is given the **median** duration as a fallback, so it's still placed on exactly one |
| 56 | +shard - correctness never depends on the timings being present or current. |
| 57 | + |
| 58 | +What the timings affect is **balance**. A new heavy test estimated at the median can be under-weighted |
| 59 | +and land on an already-full shard, making that shard slower. There's headroom between the current |
| 60 | +makespan and the CI budget to absorb this, so it tolerates drift - but if a shard creeps toward the |
| 61 | +budget, refresh the timings. |
| 62 | + |
| 63 | +### Refreshing `test-timings.json` |
| 64 | + |
| 65 | +Measure each shard with the JSON reporter and write per-file `endTime - startTime` (ms), keyed by |
| 66 | +repo-relative path, back into `test-timings.json`. Set `GITHUB_ACTIONS=true` so suites that |
| 67 | +`skipIf(CI)` are excluded, matching what actually runs on CI: |
| 68 | + |
| 69 | +```bash |
| 70 | +GITHUB_ACTIONS=true pnpm exec vitest run --reporter=json --outputFile=/tmp/run.json |
| 71 | +``` |
| 72 | + |
| 73 | +Stale entries for deleted/renamed files are harmless (they're simply ignored). This is a periodic |
| 74 | +chore, not a per-PR one. |
0 commit comments