From 15bdfdf07a2e47e66d1eb7e8b23b39cdb10974e9 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Wed, 16 Sep 2026 15:21:18 +0200 Subject: [PATCH] A gate that stands in front of the suite delays the news it carries, so it runs beside it (#94) The workflow ran everything in one job: check.sh, then build, then the e2e suite and the smokes. The runner has two job slots and four cores, so the second slot lay idle for the whole run, and a lint error was reported only after the full suite had passed in front of it - six minutes instead of one. Now `gate` and `suite` are two jobs without `needs:`. They start together, both have to pass. The price is contention: while the gate runs, vitest and chromium share four cores, and the suite measures text widths and frame counts, which a loaded machine reports differently. The gate therefore caps vitest at two workers via GM_TEST_WORKERS, so two cores stay free for the browser over the whole overlap. Locally the variable is unset and nothing changes. Measured before the push: the cap costs vitest 4.4s (16.6s -> 21.0s), check.sh exits 0 with it, build ok. Whether the split is worth it is a question for the runner - the expected net gain is about 50s, and the acceptance is a measurement over at least two runs. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/ci.yml | 26 +++++++++++++++++++++++--- vite.config.ts | 4 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 01a48ae8..dc73c364 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -3,6 +3,15 @@ # `runs-on: game-maker-ci` picks the image built from tools/ci/Dockerfile. The render device and # /dev/shm come from the runner's own container.options (/_stacks/git/runner/config.yaml), not # from here – without them chromium falls back to software WebGL. +# +# Two jobs without `needs:`, so they start together and use both slots of the runner +# (`capacity: 2`, four cores). The gate no longer stands in front of the suite: a lint error used +# to cost the full e2e run before it was reported, and the run reported it after six minutes +# instead of one. Both still have to pass for the push to be green. +# +# The price is contention: while the gate runs, vitest and chromium share the four cores, and the +# suite measures text widths and frame counts. That is why the gate caps vitest at two workers +# (GM_TEST_WORKERS) – two cores stay free for the browser for the whole overlap. name: game-maker on: @@ -10,6 +19,20 @@ on: workflow_dispatch: jobs: + gate: + runs-on: game-maker-ci + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Install + run: npm ci --no-audit --no-fund + + - name: Gate + run: ./check.sh + env: + GM_TEST_WORKERS: 2 + suite: runs-on: game-maker-ci timeout-minutes: 45 @@ -25,9 +48,6 @@ jobs: - name: Install run: npm ci --no-audit --no-fund - - name: Gate - run: ./check.sh - - name: Build run: npm run build diff --git a/vite.config.ts b/vite.config.ts index 1075b37a..ce9c1e1d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -46,5 +46,9 @@ export default defineConfig({ root: '.', include: ['tests/**/*.test.ts'], environment: 'node', + // Unset locally, so vitest keeps its own guess (one worker per core). CI sets it while the + // gate runs next to the editor suite: the two jobs share four cores, and the suite measures + // text widths and frame counts, which a machine under full load reports differently. + maxWorkers: Number(process.env.GM_TEST_WORKERS) || undefined, }, });