Files
orca/src/shared/ripgrep-process-availability.test.ts
T

113 lines
3.8 KiB
TypeScript

import { spawn, ChildProcess } from 'node:child_process'
import { join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import {
absorbPendingRipgrepSpawnError,
isRipgrepUnavailableExit,
isRipgrepSpawnCwdUsable,
isTransientRipgrepSpawnError,
killSpawnedRipgrepProcess
} from './ripgrep-process-availability'
function createChild(pid: number | undefined): ChildProcess {
const child = new ChildProcess()
Object.defineProperty(child, 'pid', { value: pid })
return child
}
describe('ripgrep process availability', () => {
it('recognizes native pre-spawn failures before either event wins the race', () => {
expect(isRipgrepUnavailableExit(createChild(undefined), null, null)).toBe(true)
expect(isRipgrepUnavailableExit(createChild(undefined), -2, null)).toBe(true)
})
it('keeps all post-spawn and signal exits on their existing paths', () => {
for (const code of [0, 1, 2, 127]) {
expect(isRipgrepUnavailableExit(createChild(1), code, null)).toBe(false)
}
expect(isRipgrepUnavailableExit(createChild(1), null, 'SIGTERM')).toBe(false)
})
it('tags only unsupported native launcher exits after spawn', () => {
for (const code of [0, 1, 2]) {
expect(
isRipgrepUnavailableExit(createChild(1), code, null, {
classifyNativeLauncherExit: true
})
).toBe(false)
}
for (const code of [3, 126, 127, 9009]) {
expect(
isRipgrepUnavailableExit(createChild(1), code, null, {
classifyNativeLauncherExit: true
})
).toBe(true)
}
})
it('does not signal a real failed-spawn handle', async () => {
const child = spawn('orca-definitely-missing-rg-admission-test', [])
const error = new Promise<void>((resolve) => child.once('error', () => resolve()))
const close = new Promise<void>((resolve) => child.once('close', () => resolve()))
const kill = vi.spyOn(child, 'kill')
try {
expect(killSpawnedRipgrepProcess(child)).toBe(false)
expect(kill).not.toHaveBeenCalled()
} finally {
await Promise.all([error, close])
kill.mockRestore()
}
})
it('distinguishes a usable root from a missing spawn cwd', async () => {
await expect(isRipgrepSpawnCwdUsable(process.cwd())).resolves.toBe(true)
await expect(
isRipgrepSpawnCwdUsable(join(process.cwd(), 'orca-definitely-missing-rg-cwd'))
).resolves.toBe(false)
})
it('absorbs a queued spawn error when cleanup wins the race', () => {
const child = createChild(undefined)
absorbPendingRipgrepSpawnError(child, {
errorObserved: false,
unavailableExitObserved: false
})
expect(() => child.emit('error', new Error('spawn rg ENOENT'))).not.toThrow()
expect(child.listenerCount('error')).toBe(0)
})
it('does not retain a sink for started or already-observed processes', () => {
const started = createChild(1)
absorbPendingRipgrepSpawnError(started, {
errorObserved: false,
unavailableExitObserved: false
})
const observed = createChild(undefined)
absorbPendingRipgrepSpawnError(observed, {
errorObserved: true,
unavailableExitObserved: true
})
expect(started.listenerCount('error')).toBe(0)
expect(observed.listenerCount('error')).toBe(0)
})
it('separates transient fork/exec pressure from a missing ripgrep binary', () => {
for (const code of ['EAGAIN', 'EMFILE', 'ENFILE', 'ENOMEM', 'ETXTBSY']) {
expect(isTransientRipgrepSpawnError(Object.assign(new Error('spawn rg'), { code }))).toBe(
true
)
}
for (const error of [
Object.assign(new Error('spawn rg ENOENT'), { code: 'ENOENT' }),
Object.assign(new Error('spawn rg EACCES'), { code: 'EACCES' }),
new Error('spawn rg'),
null
]) {
expect(isTransientRipgrepSpawnError(error)).toBe(false)
}
})
})