From fb8723ebb589044a99835354d186235ce6fa6c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 7 Aug 2026 23:39:12 +0200 Subject: [PATCH] ci: run the integration suite as two shards The suite outgrew its runner. The 25-minute guard was sized for 128 files taking about 13 minutes; the sharing release took the suite to 180 files and past the cap on CI runners, so every run died at the timeout with every test to that point green, reported as cancelled. The guard's own rule applies: split, don't raise. The files run as two vitest shards, each with its own Postgres testcontainer. A gate job carries the exact name branch protection requires and fails when any shard failed or was cancelled, so a half-green matrix cannot satisfy it. vitest is invoked directly because pnpm passes the -- separator through to the script literally: the flag never applied through the script form, and both "shards" ran the full suite. Proven by paired local runs: the script form ran 180 files twice, the direct form splits 90/90 with 927 plus 689 tests, all green. --- .github/workflows/integration.yml | 46 +++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 79b2c50bc..ceb9557f5 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -10,18 +10,21 @@ permissions: contents: read jobs: - integration: + # The suite outgrew a single runner: 128 files took ~13 minutes when the + # 25-minute guard was written, and the v1.37 sharing work took it to 173 + # files and past 25 minutes of wall clock, so every run died at the timeout + # with every test to that point green. The guard's own rule applies — split, + # don't raise — so the files run as two vitest shards. Each shard boots its + # own Postgres testcontainer; the timeout stays a hang detector, sized so a + # healthy half-suite clears it on a slow runner. + shard: runs-on: ubuntu-latest - # This guard exists to kill a hung testcontainer or a deadlocked query, not - # to hold the suite to a performance budget. At 15 it had stopped telling - # those apart: the suite itself takes around 13 minutes of job time (128 - # files against a real Postgres), so two consecutive runs of the SAME - # commit range came in at 12m50s and then over 15m, and the second was - # killed mid-run with every test to that point green. A cap that a healthy - # run clears only on a fast runner reports runner weather, not health. - # 25 leaves real headroom and still catches a hang inside one CI cycle. - # If the suite ever approaches this number, split it rather than raise it. timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + shard: [1, 2] + name: integration shard ${{ matrix.shard }}/2 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -36,7 +39,12 @@ jobs: - run: pnpm db:generate - - run: pnpm test:integration + # vitest is invoked directly: pnpm passes the `--` separator through to + # the script literally, so `pnpm test:integration -- --shard=…` hands + # vitest a stray positional argument and the shard flag never applies — + # both "shards" then run the full suite. Proven by a paired local run + # before this workflow shipped. + - run: pnpm exec vitest run --config vitest.integration.config.mts --shard=${{ matrix.shard }}/2 env: # The integration suite boots a Postgres testcontainer and points # Prisma at it via DATABASE_URL set inside `tests/integration/setup.ts`. @@ -50,3 +58,19 @@ jobs: API_TOKEN_HMAC_KEY: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000" SESSION_SECRET: "integration-tests" + + # Branch protection requires a check named exactly "integration". This job + # carries that name and answers for both shards: it fails when any shard + # failed or was cancelled, so a half-green matrix cannot satisfy the gate. + integration: + runs-on: ubuntu-latest + needs: shard + if: always() + timeout-minutes: 5 + steps: + - name: Every shard finished green + run: | + if [ "${{ needs.shard.result }}" != "success" ]; then + echo "shard result: ${{ needs.shard.result }}" + exit 1 + fi