diff --git a/docs/agents/testing.md b/docs/agents/testing.md index 3d8d10bc2..bda12b188 100644 --- a/docs/agents/testing.md +++ b/docs/agents/testing.md @@ -180,3 +180,5 @@ There is no unit-test retry layer—fix or remove flakes. - Keep Vitest isolation enabled and the pool on forks. Both alternatives were measured and did not improve the suite; importing the module under test rather than a platform barrel is the useful optimization. +- Raise the two-worker local cap only for a solo run: `AGENT_DEVICE_VITEST_MAX_WORKERS=`, + clamped to host CPUs, ignored in CI. diff --git a/scripts/lib/vitest-concurrency.ts b/scripts/lib/vitest-concurrency.ts index 5c5635159..827370ad7 100644 --- a/scripts/lib/vitest-concurrency.ts +++ b/scripts/lib/vitest-concurrency.ts @@ -1,3 +1,5 @@ +import os from 'node:os'; + /** * Keep one Vitest invocation modest enough to coexist with two other Codex * worktrees on a 12-core development host: 3 agents + (3 suites * 2 workers) @@ -5,6 +7,32 @@ */ export const DEFAULT_VITEST_MAX_WORKERS = 2; +/** + * Opt-in escape hatch for a solo local run that owns the whole machine (see + * docs/agents/testing.md). Ignored in CI, which already derives its own + * worker count from the isolated runner's CPU pool. + */ +export const VITEST_MAX_WORKERS_OVERRIDE_ENV = 'AGENT_DEVICE_VITEST_MAX_WORKERS'; + export function resolveVitestMaxWorkers(env: NodeJS.ProcessEnv = process.env): number | undefined { - return env.CI === 'true' ? undefined : DEFAULT_VITEST_MAX_WORKERS; + if (env.CI === 'true') return undefined; + + const override = parsePositiveInt(env[VITEST_MAX_WORKERS_OVERRIDE_ENV]); + // Clamp rather than trust the override literally: a typo like `999` must not + // oversubscribe the host the way the default cap above exists to prevent. + // availableParallelism(), not cpus().length: Node documents the latter as + // unfit for sizing parallelism because it ignores CPU affinity and cgroup + // limits, which would inflate the ceiling this clamp exists to enforce. + if (override !== undefined) return Math.min(override, os.availableParallelism()); + + return DEFAULT_VITEST_MAX_WORKERS; +} + +// A missing, blank, non-numeric, non-integer, or non-positive value falls +// through to the default cap instead of throwing or coercing to something +// surprising (e.g. `Number('')` is 0, not NaN). +function parsePositiveInt(value: string | undefined): number | undefined { + if (value === undefined || value.trim() === '') return undefined; + const parsed = Number(value); + return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined; } diff --git a/src/__tests__/hermetic-env-setup.test.ts b/src/__tests__/hermetic-env-setup.test.ts index adabb34cc..dd1e04128 100644 --- a/src/__tests__/hermetic-env-setup.test.ts +++ b/src/__tests__/hermetic-env-setup.test.ts @@ -5,6 +5,7 @@ import assert from 'node:assert/strict'; import { DEFAULT_VITEST_MAX_WORKERS, resolveVitestMaxWorkers, + VITEST_MAX_WORKERS_OVERRIDE_ENV, } from '../../scripts/lib/vitest-concurrency.ts'; import vitestConfig from '../../vitest.config.ts'; @@ -23,6 +24,35 @@ test('vitest caps aggregate worker concurrency for parallel worktrees', () => { assert.equal(resolveVitestMaxWorkers({ CI: 'true' }), undefined); }); +// The opt-in solo-run escape hatch (#1962). These live here rather than beside the +// resolver so the mutation lane's `vitest related` graph is not widened by a new +// test file: this one is already in the unit-core suite and already imports it. +test('a solo run may raise the local worker cap, clamped and CI-ignored', () => { + // Clamped to availableParallelism(), never honored literally: cpus().length would + // ignore CPU affinity and cgroup limits and inflate the ceiling this enforces. + assert.equal( + resolveVitestMaxWorkers({ [VITEST_MAX_WORKERS_OVERRIDE_ENV]: '999' }), + os.availableParallelism(), + ); + // 1 is <= availableParallelism() on every host, so this takes the override branch. + assert.equal(resolveVitestMaxWorkers({ [VITEST_MAX_WORKERS_OVERRIDE_ENV]: '1' }), 1); + // CI derives its own count, so the override is inert there even when both are set. + assert.equal( + resolveVitestMaxWorkers({ CI: 'true', [VITEST_MAX_WORKERS_OVERRIDE_ENV]: '8' }), + undefined, + ); +}); + +test('an unusable worker override falls through to the default cap', () => { + for (const value of ['not-a-number', '0', '-4', '2.5', '', ' ']) { + assert.equal( + resolveVitestMaxWorkers({ [VITEST_MAX_WORKERS_OVERRIDE_ENV]: value }), + DEFAULT_VITEST_MAX_WORKERS, + `${JSON.stringify(value)} must degrade to the default cap rather than throw`, + ); + } +}); + // Wiring: the scrub only helps if every project loads it as a setup file. CI runs with the // vars unset, so a dropped wiring is otherwise invisible — assert it structurally instead. test('every vitest project wires the hermetic-env setup', () => {