| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- 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> = {}): 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<T>(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<T>(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-contracts-ready',
- '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('keeps the public repository link policy in the documentation gate', () => {
- const ids = withPnpmEntrypoint(() => gatesForMode('doc-sync').map(subject => subject.id))
- expect(ids).toContain('public-repository-links')
- })
- it('keeps native Windows coverage blocking while portability inventory remains observational', () => {
- const gates = withPnpmEntrypoint(() => gatesForMode('ci-windows-complete'))
- const byId = new Map(gates.map(subject => [subject.id, subject]))
- expect(byId.get('coverage')?.allowFailure).not.toBe(true)
- expect(byId.get('coverage-exempt-heavy')?.allowFailure).not.toBe(true)
- expect(byId.get('duplication')?.allowFailure).toBe(true)
- })
- 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-contracts-ready')[0]))
- expect(subject).toMatchObject({
- id: 'lint',
- displayCommand: 'pnpm run lint:contracts-ready',
- command: process.execPath,
- args: ['/private/pnpm.cjs', 'run', 'lint:contracts-ready'],
- })
- })
- it('surfaces the configured worker bound on the shared package script', () => {
- const subject = withEnv('DSH_OXLINT_THREADS', '4', () =>
- withPnpmEntrypoint(() => gatesForMode('ci-lint-contracts-ready')[0]))
- expect(subject).toMatchObject({
- id: 'lint',
- displayCommand: 'DSH_OXLINT_THREADS=4 pnpm run lint:contracts-ready',
- command: process.execPath,
- args: ['/private/pnpm.cjs', 'run', 'lint:contracts-ready'],
- })
- })
- })
- describe('TypeRT contract preparation', () => {
- it('prepares primary source consumers once before they run', () => {
- const subject = withEnv('DSH_OXLINT_THREADS', undefined, () =>
- withPnpmEntrypoint(() => gatesForMode('ci-primary')))
- expect(subject.find(item => item.id === 'typert-contracts')).toMatchObject({
- displayCommand: 'pnpm run build:lib:host',
- args: ['/private/pnpm.cjs', 'run', 'build:lib:host'],
- })
- for (const [id, script] of [
- ['typecheck', 'typecheck:contracts-ready'],
- ['lint', 'lint:contracts-ready'],
- ['doc-typecheck', 'doc-typecheck:contracts-ready'],
- ] as const) {
- expect(subject.find(item => item.id === id)).toMatchObject({
- displayCommand: `pnpm run ${script}`,
- args: ['/private/pnpm.cjs', 'run', script],
- needs: ['typert-contracts'],
- })
- }
- expect(subject.find(item => item.id === 'build')?.needs).toEqual([
- 'typecheck',
- 'lint',
- 'doc-typecheck',
- ])
- })
- it('reuses contracts from the validated consumer build', () => {
- const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers'))
- expect(subject.find(item => item.id === 'lint-and-duplication')).toMatchObject({
- displayCommand: 'pnpm run check:ci:lint:contracts-ready',
- args: ['/private/pnpm.cjs', 'run', 'check:ci:lint:contracts-ready'],
- })
- expect(subject.find(item => item.id === 'doc-typecheck')).toMatchObject({
- displayCommand: 'pnpm run doc-typecheck:contracts-ready',
- args: ['/private/pnpm.cjs', 'run', 'doc-typecheck:contracts-ready'],
- })
- })
- it('keeps standalone doc sync responsible for preparation', () => {
- const docTypecheck = withPnpmEntrypoint(() =>
- gatesForMode('doc-sync').find(item => item.id === 'doc-typecheck'))
- expect(docTypecheck?.displayCommand).toBe('pnpm run doc-typecheck')
- })
- })
- 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 === 'built-bin-smoke')?.args).toEqual(
- expect.arrayContaining([
- 'packages/subagent/subagent-codex/tests/loader-composition.e2e.ts',
- 'packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts',
- ]),
- )
- 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')
- })
- })
|