diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index fddd0c974..d006b8b48 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -119,7 +119,9 @@ Each Vitest worker clones it on its first file (`CREATE DATABASE … TEMPLATE`, see `tests/integration/worker-database.ts`), so files run in parallel without seeing each other's rows, advisory locks or queue schema. The worker count follows the host (half the cores, at most eight) unless -`HEALTHLOG_INTEGRATION_WORKERS` pins it; CI uses three. +`HEALTHLOG_INTEGRATION_WORKERS` pins it; CI uses three. To reproduce a CI runner's +contention on a larger machine, cap the database container with +`HEALTHLOG_INTEGRATION_PG_CPUS=2`. `truncateAllTables` empties every table the migrations created and puts back the rows they seed (the mood-tag and symptom catalogues), so a test starts diff --git a/tests/integration/dashboard-summary-reads.test.ts b/tests/integration/dashboard-summary-reads.test.ts index 979c6746f..5df8ac0bd 100644 --- a/tests/integration/dashboard-summary-reads.test.ts +++ b/tests/integration/dashboard-summary-reads.test.ts @@ -335,5 +335,12 @@ describe("dashboard all-time counts — parity with the live count", () => { expect(legacy.get("PULSE")).toBeGreaterThan(500_000); expect(legacy.get("WEIGHT")).toBe(5); expect(legacy.get("BLOOD_GLUCOSE")).toBe(2); - }); + // Over half a million rows written and folded into DAY buckets: about + // 35 s on a CI runner when the file ran alone. With three files sharing + // the runner's Postgres it took 1.7 times as long (measured with the + // database capped at two CPUs, HEALTHLOG_INTEGRATION_PG_CPUS=2: 14.6 s + // alone, 25.3 s beside the sharing matrix and the restore memory test), + // past the suite's 60 s default. The work is the point of the case, so + // the budget is sized to it: twice the contended estimate. + }, 150_000); }); diff --git a/tests/integration/global-setup.ts b/tests/integration/global-setup.ts index 31fdc912b..da31f7124 100644 --- a/tests/integration/global-setup.ts +++ b/tests/integration/global-setup.ts @@ -44,7 +44,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const PROJECT_ROOT = resolve(__dirname, "..", ".."); -let container: StartedPostgreSqlContainer | null = null; +let started: StartedPostgreSqlContainer | null = null; declare module "vitest" { export interface ProvidedContext { @@ -56,7 +56,7 @@ declare module "vitest" { export default async function setup( project: TestProject, ): Promise<() => Promise> { - container = await new PostgreSqlContainer("postgres:16-alpine") + let container = new PostgreSqlContainer("postgres:16-alpine") .withDatabase(TEMPLATE_DATABASE) .withUsername("healthlog") .withPassword("healthlog") @@ -74,10 +74,18 @@ export default async function setup( "full_page_writes=off", "-c", "max_connections=400", - ]) - .start(); + ]); + // A CPU quota for the database container, to reproduce a CI runner's + // contention on a larger machine: HEALTHLOG_INTEGRATION_PG_CPUS=2. + const cpus = Number.parseFloat( + process.env.HEALTHLOG_INTEGRATION_PG_CPUS ?? "", + ); + if (Number.isFinite(cpus) && cpus > 0) { + container = container.withResourcesQuota({ cpu: cpus }); + } + started = await container.start(); - const url = container.getConnectionUri() + "?schema=public&pgbouncer=false"; + const url = started.getConnectionUri() + "?schema=public&pgbouncer=false"; process.env.DATABASE_URL = url; // The explicit child environment keeps Prisma CLI on the same container. @@ -91,9 +99,9 @@ export default async function setup( project.provide("integrationDatabasePlan", await readTemplate(url)); return async () => { - if (container) { - await container.stop({ remove: true }); - container = null; + if (started) { + await started.stop({ remove: true }); + started = null; } }; }