Files
orca/config/scripts/vitest-node-runtime-pool.test.ts
T
Neil 3ec38b8c6d Run Vitest on Bun with Node runtime contracts (#25840)
* Run Vitest on Bun while preserving Node runtime contracts

* Preserve runtime timing provenance and keep the Bun pin in config

* Scope builtin compatibility mocks to test-only lint exceptions

* Give capture retention fixtures distinct filesystem timestamps

* Await the copy button success state in the React fixture

* Bound Node test worker shutdown and tighten migration fixtures
2026-10-06 03:18:06 -07:00

102 lines
3.4 KiB
TypeScript

import { once } from 'node:events'
import { resolve } from 'node:path'
import { afterAll, afterEach, beforeAll, expect, it, vi } from 'vitest'
import { createVitest, type PoolOptions, type Vitest } from 'vitest/node'
import * as processes from '../../src/shared/child-process/run-process'
import { nodeRuntimePool } from './vitest-node-runtime-pool'
let context: Vitest
const children: processes.ChildProcessHandle[] = []
const spawn = processes.spawnProcess
beforeAll(async () => {
context = await createVitest('test', { config: false, watch: false, include: [] })
})
afterEach(async () => {
vi.useRealTimers()
vi.restoreAllMocks()
for (const child of children.splice(0)) {
if (child.pid && child.exitCode === null && child.signalCode === null) {
const exited = once(child, 'exit')
child.kill('SIGKILL')
await exited
}
}
})
afterAll(async () => {
await context.close()
})
function options(executable = process.execPath): PoolOptions {
return {
distPath: '',
project: context.getRootProject(),
method: 'run',
environment: { name: 'node', options: null },
execArgv: [],
env: { ...process.env, ORCA_TEST_NODE_EXECUTABLE: executable }
}
}
function captureChild(): void {
vi.spyOn(processes, 'spawnProcess').mockImplementation((spec) => {
const child = spawn(spec)
children.push(child)
return child
})
}
it('rejects an unavailable Node executable without an uncaught error or leaked pipes', async () => {
const output = context.logger.outputStream
const before = output.getMaxListeners()
const worker = nodeRuntimePool.createPoolWorker(options(resolve('missing-node-executable')))
await expect(worker.start()).rejects.toMatchObject({ code: 'ENOENT' })
await worker.stop()
expect(output.getMaxListeners()).toBe(before)
})
it('clears shutdown timers and listeners when the kill syscall throws', async () => {
captureChild()
const worker = nodeRuntimePool.createPoolWorker(options())
await worker.start()
const child = children[0]
vi.spyOn(child, 'kill').mockImplementation(() => {
throw new Error('kill denied')
})
vi.useFakeTimers()
await expect(worker.stop()).rejects.toThrow('kill denied')
expect(vi.getTimerCount()).toBe(0)
expect(child.listenerCount('exit')).toBe(0)
})
it('waits for a running Node worker to exit and restores its output listener budget', async () => {
captureChild()
const output = context.logger.outputStream
const before = output.getMaxListeners()
const worker = nodeRuntimePool.createPoolWorker(options())
await worker.start()
await worker.stop()
expect(children[0].signalCode).toBe('SIGTERM')
expect(output.getMaxListeners()).toBe(before)
})
it('reports a failed kill within its deadline rather than hanging teardown', async () => {
captureChild()
const worker = nodeRuntimePool.createPoolWorker(options())
const output = context.logger.outputStream
const before = output.getMaxListeners()
await worker.start()
const child = children[0]
vi.spyOn(child, 'kill').mockReturnValue(false)
vi.useFakeTimers()
const stopped = expect(worker.stop()).rejects.toThrow('did not exit after SIGKILL')
await vi.advanceTimersByTimeAsync(2_000)
await stopped
expect(child.kill).toHaveBeenCalledWith('SIGTERM')
expect(child.kill).toHaveBeenCalledWith('SIGKILL')
expect(child.listenerCount('exit')).toBe(0)
expect(output.getMaxListeners()).toBe(before)
})