Files
orca/src/main/daemon/node-pty-fd-leak.test.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

93 lines
2.8 KiB
TypeScript

import { execFileSync } from 'node:child_process'
import { existsSync, renameSync } from 'node:fs'
import { setTimeout as delay } from 'node:timers/promises'
import * as pty from 'node-pty'
import { describe, expect, it } from 'vitest'
import { getNodePtySpawnHelperCandidates } from '../providers/local-pty-utils'
function currentRevokedFdCount(): number {
return execFileSync('lsof', ['-p', String(process.pid)], { encoding: 'utf8' })
.split('\n')
.filter((line) => line.includes('(revoked)')).length
}
function currentOpenFdCount(): number {
return execFileSync('lsof', ['-p', String(process.pid)], { encoding: 'utf8' })
.split('\n')
.filter((line) => line.trim().length > 0).length
}
function getExistingSpawnHelper(): string {
const helperPath = getNodePtySpawnHelperCandidates().find((candidate) => existsSync(candidate))
expect(helperPath).toBeTruthy()
return helperPath as string
}
async function spawnExitingPty(index: number): Promise<void> {
const proc = pty.spawn('/bin/sh', ['-c', 'exit 0'], {
name: 'xterm-256color',
cols: 80,
rows: 24,
cwd: process.cwd(),
env: { ...process.env, ORCA_FD_LEAK_TEST_INDEX: String(index) }
})
await new Promise<void>((resolve) => {
proc.onExit(() => resolve())
})
;(proc as unknown as { destroy?: () => void }).destroy?.()
}
const describeOnDarwin = process.platform === 'darwin' ? describe : describe.skip
describeOnDarwin('node-pty macOS spawn fd handling', () => {
it('does not leak revoked slave tty fds across exited pty spawns', async () => {
const before = currentRevokedFdCount()
for (let i = 0; i < 50; i++) {
await spawnExitingPty(i)
}
await delay(500)
const after = currentRevokedFdCount()
expect(after - before).toBe(0)
}, 15000)
it('does not leak fds when native posix_spawn setup fails', async () => {
const helperPath = getExistingSpawnHelper()
const hiddenHelperPath = `${helperPath}.orca-test-hidden`
expect(existsSync(hiddenHelperPath)).toBe(false)
const before = currentOpenFdCount()
renameSync(helperPath, hiddenHelperPath)
const restoreHelper = (): void => {
if (existsSync(hiddenHelperPath) && !existsSync(helperPath)) {
renameSync(hiddenHelperPath, helperPath)
}
}
process.on('exit', restoreHelper)
try {
for (let i = 0; i < 20; i++) {
expect(() =>
pty.spawn('/bin/sh', ['-c', 'exit 0'], {
name: 'xterm-256color',
cols: 80,
rows: 24,
cwd: process.cwd(),
env: { ...process.env, ORCA_FD_LEAK_TEST_INDEX: String(i) }
})
).toThrow(/node-pty: posix_spawn failed: ENOENT/)
}
} finally {
restoreHelper()
process.off('exit', restoreHelper)
}
await delay(500)
const after = currentOpenFdCount()
expect(after - before).toBe(0)
}, 15000)
})