import { describe, expect, it, vi } from 'vitest' import { defaultConcurrency, formatGateResultReason, gatesForMode, runGate, runGates, type Gate, type GateResult, } from './run-gates.ts' function gate(id: string, options: Partial = {}): Gate { return { id, label: id, displayCommand: `run ${id}`, command: process.execPath, args: ['-e', ''], ...options, } } function resultFor(subject: Gate, status: GateResult['status'] = 'passed'): GateResult { return { gate: subject, status, durationMs: 10, output: [], exitCode: status === 'passed' ? 0 : 1, signalCode: null, } } function withPnpmEntrypoint(action: () => T): T { const previous = process.env.npm_execpath process.env.npm_execpath = '/private/pnpm.cjs' try { return action() } finally { if (previous === undefined) Reflect.deleteProperty(process.env, 'npm_execpath') else process.env.npm_execpath = previous } } function withEnv(name: string, value: string | undefined, action: () => T): T { const previous = process.env[name] if (value === undefined) Reflect.deleteProperty(process.env, name) else process.env[name] = value try { return action() } finally { if (previous === undefined) Reflect.deleteProperty(process.env, name) else process.env[name] = previous } } describe('gate graph validation', () => { it.each([ 'ci-primary', 'ci-linux-primary', 'ci-static', 'ci-lint', 'ci-coverage', 'ci-snapshot', 'ci-artifacts', 'ci-consumers', 'ci-windows-blocking', 'ci-windows-complete', 'ci-windows-observational', 'node-compat', 'check-all', 'doc-sync', ] as const)('constructs and executes preflight for a valid non-empty %s graph', async (mode) => { const subject = withPnpmEntrypoint(() => gatesForMode(mode)) const execute = vi.fn(async (item: Gate) => resultFor(item)) await expect(runGates(subject, subject.length, execute)).resolves.toHaveLength(subject.length) }) it.each([ ['empty', [], /gate graph has no gates/], ['duplicate ids', [gate('same'), gate('same')], /duplicate gate id "same"/], ['unknown dependencies', [gate('subject', { needs: ['missing'] })], /depends on unknown gate "missing"/], ['cycles', [gate('first', { needs: ['second'] }), gate('second', { needs: ['first'] })], /dependency cycle: first -> second -> first/], ] as const)('rejects %s before starting a child', async (_label, invalid, message) => { const execute = vi.fn(async (subject: Gate) => resultFor(subject)) await expect(runGates([...invalid], 1, execute)).rejects.toThrow(message) expect(execute).not.toHaveBeenCalled() }) it('rejects an invalid worker count before starting a child', async () => { const execute = vi.fn(async (subject: Gate) => resultFor(subject)) await expect(runGates([gate('subject')], 0, execute)).rejects.toThrow('max concurrency must be a positive integer') expect(execute).not.toHaveBeenCalled() }) it('skips dependents after their prerequisite fails', async () => { const dependent = gate('dependent', { needs: ['root'] }) const root = gate('root') const execute = vi.fn(async (subject: Gate) => resultFor(subject, 'failed')) const results = await runGates([dependent, root], 1, execute) expect(execute).toHaveBeenCalledOnce() expect(execute).toHaveBeenCalledWith(root) expect(results[0]).toMatchObject({ gate: dependent, status: 'skipped', error: 'dependency failed or skipped: root' }) }) }) describe('Oxlint gate', () => { it('uses the package script when no worker bound is configured', () => { const subject = withEnv('DSH_OXLINT_THREADS', undefined, () => withPnpmEntrypoint(() => gatesForMode('ci-lint')[0])) expect(subject).toMatchObject({ id: 'lint', displayCommand: 'pnpm run lint', command: process.execPath, args: ['/private/pnpm.cjs', 'run', 'lint'], }) }) it('surfaces the configured worker bound on the shared package script', () => { const subject = withEnv('DSH_OXLINT_THREADS', '4', () => withPnpmEntrypoint(() => gatesForMode('ci-lint')[0])) expect(subject).toMatchObject({ id: 'lint', displayCommand: 'DSH_OXLINT_THREADS=4 pnpm run lint', command: process.execPath, args: ['/private/pnpm.cjs', 'run', 'lint'], }) }) }) describe('Node compatibility graph', () => { it('runs the jsdom environment smoke on every advertised Node line', () => { const subject = withPnpmEntrypoint(() => gatesForMode('node-compat')) expect(subject.find(item => item.id === 'vitest-jsdom-smoke')).toMatchObject({ label: 'Vitest jsdom smoke', args: [ '/private/pnpm.cjs', 'exec', 'vitest', 'run', 'scripts/vitest-environment.compat.spec.ts', ], }) }) }) describe('Node 24 lane ownership', () => { it('keeps the static lane source-only', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-static')) expect(subject.map(item => item.id)).not.toContain('build') expect(subject.map(item => item.id)).not.toContain('doc-typecheck') }) it('owns the build and orders its artifact consumers', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers')) expect(defaultConcurrency('ci-consumers', subject.length, 4)).toEqual({ workers: 10, source: 'ci-consumers gate count', }) expect(subject.map(item => item.id)).toEqual([ 'build', 'node-compat', 'publint', 'built-package-invariants', 'lint-and-duplication', 'snapshot', 'web-snapshot', 'doc-typecheck', 'node-next-types', 'built-bin-smoke', ]) expect(subject.find(item => item.id === 'publint')?.needs).toEqual(['build']) expect(subject.find(item => item.id === 'built-package-invariants')?.needs).toEqual(['publint']) expect(subject.find(item => item.id === 'lint-and-duplication')?.needs).toEqual(['built-package-invariants']) for (const id of ['snapshot', 'web-snapshot', 'doc-typecheck', 'node-next-types', 'built-bin-smoke']) { expect(subject.find(item => item.id === id)?.needs).toEqual(['built-package-invariants']) } expect(subject.find(item => item.id === 'snapshot')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' }) expect(subject.find(item => item.id === 'doc-typecheck')?.env).toEqual({ DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1', }) expect(subject.find(item => item.id === 'web-snapshot')).toMatchObject({ displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built', env: { DSH_SNAPSHOT: 'replay' }, }) }) }) describe('Linux primary graph', () => { it('adds the same compare-only web gate after built client artifacts', () => { const subject = withPnpmEntrypoint(() => gatesForMode('ci-linux-primary')) const web = subject.find(item => item.id === 'web-snapshot') expect(web).toMatchObject({ displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built', env: { DSH_SNAPSHOT: 'replay' }, needs: ['built-package-invariants'], }) }) }) describe('gate process outcomes', () => { it.skipIf(process.platform === 'win32')('reports signal termination independently from exit status', async () => { const result = await runGate(gate('terminated', { args: ['-e', "process.kill(process.pid, 'SIGTERM')"], })) expect(result.status).toBe('failed') expect(result.exitCode).toBeNull() expect(result.signalCode).toBe('SIGTERM') expect(formatGateResultReason(result)).toBe('signal SIGTERM') }) })