Files
orca/src/main/git/runner-command-exec.test.ts
T
Jinjing e0104372da Squashed commits (#2485)
- Fix workspace space cache and git status cleanup checks

- Treat browser tabs as workspace delete blockers

- Move deletion readiness into shared presentation logic so tests cover it
- Preserve explicit empty git status entries for clean worktrees

- Add package manager cache cleanup to space manager

- Detect npm, pnpm, Yarn, and Bun caches from lockfiles during scans
- Add explicit cleanup IPC for safe and aggressive cache actions
- Support local, SSH, and Windows command execution paths safely
- Tighten workspace deletion decisions with git, editor, agent, and terminal state
2026-05-20 22:09:35 -07:00

143 lines
4.5 KiB
TypeScript

import { EventEmitter } from 'node:events'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { execFileMock, execFileSyncMock, spawnMock } = vi.hoisted(() => ({
execFileMock: vi.fn(),
execFileSyncMock: vi.fn(),
spawnMock: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFile: execFileMock,
execFileSync: execFileSyncMock,
spawn: spawnMock
}))
import { commandExecFileAsync } from './runner'
type MockChildProcess = EventEmitter & {
stdout: EventEmitter
stderr: EventEmitter
pid: number
kill: ReturnType<typeof vi.fn>
unref?: ReturnType<typeof vi.fn>
}
function createMockChildProcess(pid: number): MockChildProcess {
const child = new EventEmitter() as MockChildProcess
child.stdout = new EventEmitter()
child.stderr = new EventEmitter()
child.pid = pid
child.kill = vi.fn()
return child
}
function createMockTaskkillProcess(): MockChildProcess {
const child = createMockChildProcess(9000)
child.unref = vi.fn()
return child
}
async function withPlatform<T>(platform: NodeJS.Platform, fn: () => Promise<T>): Promise<T> {
const original = process.platform
Object.defineProperty(process, 'platform', { configurable: true, value: platform })
try {
return await fn()
} finally {
Object.defineProperty(process, 'platform', { configurable: true, value: original })
}
}
describe('commandExecFileAsync Windows command shims', () => {
const originalComSpec = process.env.ComSpec
beforeEach(() => {
process.env.ComSpec = 'C:\\Windows\\System32\\cmd.exe'
execFileMock.mockReset()
execFileSyncMock.mockReset()
spawnMock.mockReset()
})
afterEach(() => {
vi.useRealTimers()
if (originalComSpec === undefined) {
delete process.env.ComSpec
} else {
process.env.ComSpec = originalComSpec
}
})
it('kills aborted Windows .cmd shim executions as a process tree', async () => {
await withPlatform('win32', async () => {
const command = createMockChildProcess(1234)
const taskkill = createMockTaskkillProcess()
spawnMock.mockImplementation((cmd: string) => (cmd === 'taskkill' ? taskkill : command))
const controller = new AbortController()
const promise = commandExecFileAsync('C:\\tools\\pnpm.cmd', ['--version'], {
cwd: 'C:\\repo',
signal: controller.signal
})
const rejection = expect(promise).rejects.toMatchObject({ name: 'AbortError' })
controller.abort()
await rejection
expect(spawnMock).toHaveBeenCalledWith(
'taskkill',
['/pid', '1234', '/t', '/f'],
expect.objectContaining({ stdio: 'ignore', windowsHide: true })
)
expect(command.kill).not.toHaveBeenCalled()
})
})
it('kills timed-out Windows .cmd shim executions as a process tree', async () => {
vi.useFakeTimers()
await withPlatform('win32', async () => {
const command = createMockChildProcess(1234)
const taskkill = createMockTaskkillProcess()
spawnMock.mockImplementation((cmd: string) => (cmd === 'taskkill' ? taskkill : command))
const promise = commandExecFileAsync('C:\\tools\\pnpm.cmd', ['store', 'prune'], {
cwd: 'C:\\repo',
timeout: 1000
})
const rejection = expect(promise).rejects.toThrow('C:\\tools\\pnpm.cmd timed out.')
await vi.advanceTimersByTimeAsync(1000)
await rejection
expect(spawnMock).toHaveBeenCalledWith(
'taskkill',
['/pid', '1234', '/t', '/f'],
expect.objectContaining({ stdio: 'ignore', windowsHide: true })
)
expect(command.kill).not.toHaveBeenCalled()
})
})
it('kills over-buffer Windows .cmd shim executions as a process tree', async () => {
await withPlatform('win32', async () => {
const command = createMockChildProcess(1234)
const taskkill = createMockTaskkillProcess()
spawnMock.mockImplementation((cmd: string) => (cmd === 'taskkill' ? taskkill : command))
const promise = commandExecFileAsync('C:\\tools\\pnpm.cmd', ['store', 'prune'], {
cwd: 'C:\\repo',
maxBuffer: 2
})
const rejection = expect(promise).rejects.toThrow(
'C:\\tools\\pnpm.cmd stdout exceeded maxBuffer.'
)
command.stdout.emit('data', Buffer.from('too much output'))
await rejection
expect(spawnMock).toHaveBeenCalledWith(
'taskkill',
['/pid', '1234', '/t', '/f'],
expect.objectContaining({ stdio: 'ignore', windowsHide: true })
)
expect(command.kill).not.toHaveBeenCalled()
})
})
})