fix(ci OPS50): matrix estática [1,2] no e2e (Forgejo não passa job results à expansão de matrix — fromJson(needs.*) vira shard vazio) e VITEST_MAX_WORKERS=4 no int (8 CPUs do runner × pool-10 estoura os 100 clients do Postgres do serviço)
Some checks failed
Agent PR Ready + auto-merge / ready-automerge (pull_request) Has been skipped
CI (PR) / docs-guards (pull_request) Successful in 6s
CI (PR) / plans-only-closes (pull_request) Successful in 6s
CI (PR) / scope (pull_request) Successful in 31s
CI (PR) / lint (pull_request) Successful in 40s
CI (PR) / format (pull_request) Successful in 57s
CI (PR) / typecheck (pull_request) Successful in 33s
CI (PR) / knip (pull_request) Successful in 33s
CI (PR) / cycles (pull_request) Successful in 34s
CI (PR) / unit (pull_request) Successful in 45s
CI (PR) / build (pull_request) Has been skipped
CI (PR) / int (pull_request) Successful in 1m58s
CI (PR) / e2e (1) (pull_request) Failing after 3m8s
CI (PR) / e2e (2) (pull_request) Failing after 3m11s
CI (PR) / checks (pull_request) Failing after 1s

This commit is contained in:
Francisco Solla 2026-08-17 04:17:56 -03:00
parent 6b9c04820b
commit b51f745f66
2 changed files with 28 additions and 6 deletions

View file

@ -179,6 +179,12 @@ jobs:
&& (needs.knip.result == 'success' || needs.knip.result == 'skipped') && (needs.knip.result == 'success' || needs.knip.result == 'skipped')
&& (needs.cycles.result == 'success' || needs.cycles.result == 'skipped') && (needs.cycles.result == 'success' || needs.cycles.result == 'skipped')
&& (needs.unit.result == 'success' || needs.unit.result == 'skipped') && (needs.unit.result == 'success' || needs.unit.result == 'skipped')
env:
# Self-hosted runner has 8 CPUs; vitest forks ≈ CPUs and each fork holds
# a Payload pool (max 10) → overflows the service Postgres' 100 clients
# ("too many clients already", CI run 180). 4 forks stay well under.
# GitHub's 2–4 vCPU runners keep the CPU-relative default (no env).
VITEST_MAX_WORKERS: 4
services: services:
postgres: postgres:
image: postgres:17-alpine image: postgres:17-alpine
@ -281,12 +287,16 @@ jobs:
&& (needs.knip.result == 'success' || needs.knip.result == 'skipped') && (needs.knip.result == 'success' || needs.knip.result == 'skipped')
&& (needs.cycles.result == 'success' || needs.cycles.result == 'skipped') && (needs.cycles.result == 'success' || needs.cycles.result == 'skipped')
&& (needs.unit.result == 'success' || needs.unit.result == 'skipped') && (needs.unit.result == 'success' || needs.unit.result == 'skipped')
# OPS34: full mode splits across 2 runners (matrix from the scope job); # OPS34: full mode splits across 2 runners; selected mode stays on shard 1
# selected mode stays on one. fail-fast: false keeps both shards' logs. # (shard 2 skips build+test via step `if`). Static [1, 2] — Forgejo 1.22
# does not pass job results into matrix expansion, so fromJson(needs.*)
# yields an empty shard. Keep the literal in sync with e2eShardConfig in
# scripts/lib/test-affected-core.mjs (ci.yml pins the same [1, 2] / 2).
# fail-fast: false keeps both shards' logs.
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shard: ${{ fromJson(needs.scope.outputs.e2e_matrix) }} shard: [1, 2]
env: env:
# Explicit CI workers; the local default stays 2 (playwright.config.ts). # Explicit CI workers; the local default stays 2 (playwright.config.ts).
PLAYWRIGHT_WORKERS: 2 PLAYWRIGHT_WORKERS: 2
@ -336,6 +346,9 @@ jobs:
- run: pnpm migrate - run: pnpm migrate
- run: pnpm db:seed:minimal - run: pnpm db:seed:minimal
- name: Build for e2e - name: Build for e2e
# Selected mode only needs shard 1 (a second build for a handful of
# specs is waste).
if: needs.scope.outputs.e2e_mode == 'full' || matrix.shard == 1
run: pnpm build run: pnpm build
env: env:
NEXT_DIST_DIR: .next/e2e NEXT_DIST_DIR: .next/e2e
@ -347,7 +360,8 @@ jobs:
run: pnpm test:e2e --shard=${{ matrix.shard }}/${{ needs.scope.outputs.e2e_shards }} run: pnpm test:e2e --shard=${{ matrix.shard }}/${{ needs.scope.outputs.e2e_shards }}
- name: E2E tests (manifest-selected specs) - name: E2E tests (manifest-selected specs)
if: needs.scope.outputs.e2e_mode == 'selected' # Selected specs run once, on shard 1; shard 2 skips (no test step).
if: needs.scope.outputs.e2e_mode == 'selected' && matrix.shard == 1
run: pnpm test:e2e -- ${{ needs.scope.outputs.e2e_specs }} run: pnpm test:e2e -- ${{ needs.scope.outputs.e2e_specs }}
docs-guards: docs-guards:

View file

@ -10,8 +10,16 @@ import { defineConfig } from 'vitest/config'
// 160 clients → "too many clients already"). Do NOT set a literal // 160 clients → "too many clients already"). Do NOT set a literal
// `maxWorkers: 8` on CI: Vitest treats that as "use up to 8" even on 2–4 // `maxWorkers: 8` on CI: Vitest treats that as "use up to 8" even on 2–4
// vCPU runners, which starves shared advisory leases (invite/consent) and // vCPU runners, which starves shared advisory leases (invite/consent) and
// times out at 15s. Leave CI on Vitest's CPU-relative default. // times out at 15s. Leave CI on Vitest's CPU-relative default — except on
const maxWorkers = process.env.CI ? undefined : Math.min(8, os.availableParallelism()) // self-hosted high-core runners (Forgejo homeserver: 8 CPUs → 8 forks ×
// pool-10 already overflows the service Postgres' 100 clients), where the
// workflow pins VITEST_MAX_WORKERS=4 (the "too many clients" failure mode
// landed there in CI run 180).
const maxWorkers = process.env.VITEST_MAX_WORKERS
? Number(process.env.VITEST_MAX_WORKERS)
: process.env.CI
? undefined
: Math.min(8, os.availableParallelism())
export default defineConfig({ export default defineConfig({
resolve: { resolve: {