From 3fb72d135de28d61c0bbbda7bb3a40be363a7cae Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 6 Oct 2026 18:08:50 -0700 Subject: [PATCH] Run Node event-loop measurement after ordinary test suites (#26015) --- config/scripts/ci-shard-assignment.test.mjs | 56 ++++++++++++++++++- config/scripts/ci-unit-sequencer.test.mjs | 22 +++++++- config/vitest.config.ts | 62 ++++++++++++++------- 3 files changed, 116 insertions(+), 24 deletions(-) diff --git a/config/scripts/ci-shard-assignment.test.mjs b/config/scripts/ci-shard-assignment.test.mjs index 323328fe8bc..ac8ce049d24 100644 --- a/config/scripts/ci-shard-assignment.test.mjs +++ b/config/scripts/ci-shard-assignment.test.mjs @@ -7,6 +7,9 @@ import { balanceFiles } from './ci-shard-assignment.mjs' import { discoverE2eFiles, planE2e } from './ci-e2e-shard-plan.mjs' import { parseTimingLog } from './ci-shard-timing-import.mjs' import TimingSequencer from './ci-unit-sequencer.mjs' +import { UNIT_INCLUDE, UNIT_EXCLUDE } from './ci-unit-files.mjs' +import { NODE_RUNTIME_INCLUDE } from './vitest-node-runtime-files.mjs' +import { nodeRuntimePool } from './vitest-node-runtime-pool' const directories = [] afterEach(() => { @@ -60,10 +63,61 @@ describe('timing-weighted shard selection', () => { expect(new Set(selected)).toEqual(new Set(specs)) }) - it('wires a constructor into the opt-in Vitest config', async () => { + it('keeps global sharding and runs the Node measurement after ordinary projects', async () => { vi.stubEnv('ORCA_BALANCE_UNIT_SHARDS', '1') const { default: config } = await import('../vitest.config') expect(config.test.sequence.sequencer).toBe(TimingSequencer) + expect(config.test.maxWorkers).toBe(process.platform === 'win32' ? 4 : undefined) + const measurementFile = + 'src/main/foreign-sqlite-readers/foreign-sqlite-reader-event-loop.test.ts' + const projects = config.test.projects + const measurement = projects.filter((project) => project.test.name === 'node-measurement') + const ordinary = projects.filter((project) => project.test.name !== 'node-measurement') + expect(measurement).toHaveLength(1) + expect(ordinary).toHaveLength(process.versions.bun ? 2 : 1) + expect(measurement[0].test).toMatchObject({ + include: [measurementFile], + exclude: UNIT_EXCLUDE, + maxWorkers: 1, + sequence: { groupOrder: 2 }, + env: { ORCA_VITEST_RUNTIME: process.versions.bun ? 'node-runtime' : 'node' }, + pool: process.versions.bun ? 'node-runtime' : 'forks' + }) + expect(measurement[0].test.poolRunner).toBe(process.versions.bun ? nodeRuntimePool : undefined) + for (const project of ordinary) { + expect(project.test.sequence.groupOrder).toBe(1) + expect(project.test.exclude).toContain(measurementFile) + } + const ordinaryNode = ordinary.find((project) => project.test.name !== 'bun') + expect(ordinaryNode.test.include).toEqual( + process.versions.bun ? NODE_RUNTIME_INCLUDE : UNIT_INCLUDE + ) + const ordinaryBun = ordinary.find((project) => project.test.name === 'bun') + if (process.versions.bun) { + expect(ordinaryBun.test.exclude).toEqual( + expect.arrayContaining([...NODE_RUNTIME_INCLUDE, measurementFile]) + ) + } + for (const project of projects) { + expect(project.extends).toBe(false) + expect(project.define).toEqual(config.define) + expect(project.resolve).toEqual(config.resolve) + expect(project.test.execArgv).toEqual(['--no-experimental-webstorage', '--expose-gc']) + expect(project.test.setupFiles).toEqual([ + resolve('config/scripts/vitest-real-agent-home-write-guard.ts'), + resolve('config/scripts/vitest-bun-node-builtins.ts'), + resolve('config/scripts/happy-dom-offscreen-canvas.ts'), + resolve('config/scripts/happy-dom-mutation-observer-retention.ts'), + resolve('config/scripts/vitest-host-ports-setup.ts'), + resolve('config/scripts/vitest-caller-identity-env-setup.ts') + ]) + expect(project.test).toMatchObject({ + environment: 'node', + server: { deps: { inline: ['zod'] } }, + hookTimeout: 60_000, + testTimeout: 30_000 + }) + } }) it('keeps nested/serial E2E files atomic and fails closed on discovery errors', () => { diff --git a/config/scripts/ci-unit-sequencer.test.mjs b/config/scripts/ci-unit-sequencer.test.mjs index a14ab60b497..b4a56a4c551 100644 --- a/config/scripts/ci-unit-sequencer.test.mjs +++ b/config/scripts/ci-unit-sequencer.test.mjs @@ -1,4 +1,4 @@ -import { mkdtempSync, writeFileSync, rmSync } from 'node:fs' +import { mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, expect, it, vi } from 'vitest' @@ -22,7 +22,12 @@ it.each([ 'empty-selection' ])('preserves complete shard coverage with %s planning evidence', async (kind) => { root = mkdtempSync(join(tmpdir(), 'unit-sequencer-')) - const files = ['src/a.test.ts', 'src/b.test.ts', 'src/c.test.ts', 'src/d.test.ts'] + const files = [ + 'src/main/foreign-sqlite-readers/foreign-sqlite-reader-event-loop.test.ts', + 'src/b.test.ts', + 'src/c.test.ts', + 'src/d.test.ts' + ] const plan = { version: 1, sourceSha: kind === 'stale' ? 'old' : 'current', @@ -44,8 +49,19 @@ it.each([ const assigned = [] for (const index of [1, 2]) { const sequencer = new TimingSequencer({ config: { root, shard: { index, count: 2 } } }) - const specs = files.map((file) => ({ moduleId: join(root, file) })) + const specs = files.map((file, position) => ({ + moduleId: join(root, file), + project: { + name: position === 0 ? 'node-measurement' : position % 2 ? 'bun' : 'node-runtime', + config: { sequence: { groupOrder: position === 0 ? 2 : 1 }, isolate: true } + } + })) assigned.push(...(await sequencer.shard(specs)).map((spec) => spec.moduleId)) + const manifest = JSON.parse(readFileSync(join(root, 'assignment.json'), 'utf8')) + expect(manifest.selectedShard).toBe(index) + expect(manifest.shards.flatMap((shard) => shard.files).sort()).toEqual( + (kind === 'valid' ? files.slice(0, 2) : files).toSorted() + ) } expect(assigned.sort()).toEqual( (kind === 'valid' ? files.slice(0, 2) : files).map((file) => join(root, file)).sort() diff --git a/config/vitest.config.ts b/config/vitest.config.ts index 9883f293c24..c5c057bc64e 100644 --- a/config/vitest.config.ts +++ b/config/vitest.config.ts @@ -7,6 +7,7 @@ import { NODE_RUNTIME_INCLUDE } from './scripts/vitest-node-runtime-files.mjs' import { nodeRuntimePool } from './scripts/vitest-node-runtime-pool' const balancedShards = process.env.ORCA_BALANCE_UNIT_SHARDS === '1' +const measurementFile = 'src/main/foreign-sqlite-readers/foreign-sqlite-reader-event-loop.test.ts' const transforms = { define: { ORCA_FEATURE_WALL_ENABLED: 'true' }, resolve: { @@ -39,28 +40,49 @@ const testOptions = { hookTimeout: 60_000, testTimeout: 30_000 } +const nodeProject = { + extends: false, + ...transforms, + test: { + ...testOptions, + name: process.versions.bun ? 'node-runtime' : 'node', + env: { ORCA_VITEST_RUNTIME: process.versions.bun ? 'node-runtime' : 'node' }, + include: process.versions.bun ? NODE_RUNTIME_INCLUDE : UNIT_INCLUDE, + exclude: [...testOptions.exclude, measurementFile], + sequence: { groupOrder: 1 }, + ...(process.versions.bun + ? { pool: 'node-runtime', poolRunner: nodeRuntimePool } + : { pool: 'forks' }) + } +} const projects = [ + ...(process.versions.bun + ? [ + { + extends: false, + ...transforms, + test: { + ...testOptions, + name: 'bun', + env: { ORCA_VITEST_RUNTIME: 'bun' }, + pool: 'forks', + exclude: [...testOptions.exclude, ...NODE_RUNTIME_INCLUDE, measurementFile], + sequence: { groupOrder: 1 } + } + } + ] + : []), + nodeProject, + // Keep the event-loop measurement free of other suites without weakening its limits. { - extends: false, - ...transforms, + ...nodeProject, test: { - ...testOptions, - name: 'bun', - env: { ORCA_VITEST_RUNTIME: 'bun' }, - pool: 'forks', - exclude: [...testOptions.exclude, ...NODE_RUNTIME_INCLUDE] - } - }, - { - extends: false, - ...transforms, - test: { - ...testOptions, - name: 'node-runtime', - env: { ORCA_VITEST_RUNTIME: 'node-runtime' }, - include: NODE_RUNTIME_INCLUDE, - pool: 'node-runtime', - poolRunner: nodeRuntimePool + ...nodeProject.test, + name: 'node-measurement', + include: [measurementFile], + exclude: testOptions.exclude, + maxWorkers: 1, + sequence: { groupOrder: 2 } } } ] @@ -75,7 +97,7 @@ export default defineConfig({ reporters: ['default', resolve('config/scripts/ci-unit-timing-reporter.mjs')] } : {}), - ...(process.versions.bun ? { projects } : {}), + projects, ...(process.platform === 'win32' ? { maxWorkers: 4 } : {}) } })