mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
* fix(relay): sweep detached tools on immediate terminal close * test(relay): reject failed process cleanup queries
151 lines
5.3 KiB
JavaScript
151 lines
5.3 KiB
JavaScript
import { it, expect } from 'vitest'
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { tmpdir } from 'node:os'
|
|
import {
|
|
createMockDispatcher,
|
|
createTestPtyHandler
|
|
} from '../../src/relay/pty-handler-test-harness.ts'
|
|
import {
|
|
captureDescendantSnapshot,
|
|
readProcessTable
|
|
} from '../../src/main/pty-descendant-termination.ts'
|
|
import { runProcess } from '../../src/shared/child-process/run-process.ts'
|
|
|
|
const binary = process.env.ORCA_OMP_PROBE_BINARY
|
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`
|
|
|
|
it.skipIf(!binary || process.platform === 'win32')(
|
|
'closes actual OMP detached tools through the relay host',
|
|
async () => {
|
|
const fixtures = join(process.cwd(), '.bench-fixtures')
|
|
mkdirSync(fixtures, { recursive: true })
|
|
const output = mkdtempSync(join(fixtures, 'omp-relay-close-'))
|
|
const home = mkdtempSync(join(tmpdir(), 'orca-omp-relay-close-home-'))
|
|
const agentHome = join(home, 'agent')
|
|
mkdirSync(agentHome)
|
|
const config = join(home, 'probe.yml')
|
|
writeFileSync(
|
|
config,
|
|
'startup:\n setupWizard: false\n showSplash: false\n checkUpdate: false\n'
|
|
)
|
|
const dispatcher = createMockDispatcher()
|
|
let transcript = ''
|
|
dispatcher.notify = (method, params) => {
|
|
if (method === 'pty.data' && typeof params?.data === 'string') {
|
|
transcript = (transcript + params.data).slice(-131072)
|
|
}
|
|
}
|
|
const handler = createTestPtyHandler(dispatcher)
|
|
let snapshot
|
|
let id
|
|
try {
|
|
const spawned = await dispatcher.callRequest('pty.spawn', {
|
|
cwd: home,
|
|
cols: 120,
|
|
rows: 35,
|
|
env: {
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
ZDOTDIR: home,
|
|
ORCA_ORIG_ZDOTDIR: home,
|
|
SHELL:
|
|
process.env.ORCA_OMP_PROBE_SHELL ??
|
|
(process.platform === 'darwin' ? '/bin/zsh' : '/bin/bash'),
|
|
XDG_CONFIG_HOME: join(home, 'config'),
|
|
XDG_DATA_HOME: join(home, 'data'),
|
|
XDG_CACHE_HOME: join(home, 'cache'),
|
|
XDG_STATE_HOME: join(home, 'state'),
|
|
OMP_CODING_AGENT_DIR: agentHome,
|
|
PI_CODING_AGENT_DIR: agentHome,
|
|
OMP_PROFILE: '',
|
|
PI_PROFILE: '',
|
|
PI_CONFIG_DIR: '.omp',
|
|
PI_CONFIG_FILES: '',
|
|
ORCA_BACKGROUND_LAUNCH: '1',
|
|
ORCA_PANE_KEY: 'omp-relay-probe:owned-leaf',
|
|
ORCA_TAB_ID: 'omp-relay-probe'
|
|
},
|
|
envToDelete: ['BASH_ENV', 'ENV', 'ORCA_OMP_STATUS_EXTENSION', 'ORCA_PI_STATUS_EXTENSION']
|
|
})
|
|
id = spawned.id
|
|
const [entry] = JSON.parse(await dispatcher.callRequest('pty.serialize', { ids: [id] }))
|
|
snapshot = await captureDescendantSnapshot(entry.pid)
|
|
expect(snapshot?.root?.pid).toBe(entry.pid)
|
|
dispatcher.callNotification('pty.data', {
|
|
id,
|
|
data: `${quote(binary)} --no-session --config ${quote(config)}\r`
|
|
})
|
|
await pause(5000)
|
|
dispatcher.callNotification('pty.data', { id, data: '! /bin/sleep 120\r' })
|
|
for (let attempt = 0; attempt < 25; attempt++) {
|
|
await pause(200)
|
|
snapshot = await captureDescendantSnapshot(entry.pid)
|
|
if (snapshot?.descendants.length > 1) {
|
|
break
|
|
}
|
|
}
|
|
expect(snapshot?.descendants.length).toBeGreaterThan(1)
|
|
const pids = [entry.pid, ...snapshot.descendants.map((row) => row.pid)]
|
|
const rows = async () => {
|
|
const result = await runProcess({
|
|
program: 'ps',
|
|
args: ['-p', pids.join(','), '-o', 'pid=,ppid=,pgid=,stat=,comm='],
|
|
maxOutputBytes: 16000
|
|
})
|
|
expect(result.timedOut).toBe(false)
|
|
expect(result.signal).toBeNull()
|
|
expect(result.stderr.trim()).toBe('')
|
|
expect([0, 1]).toContain(result.code)
|
|
if (result.code === 1) {
|
|
expect(result.stdout.trim()).toBe('')
|
|
}
|
|
return result.stdout.trim()
|
|
}
|
|
const before = await rows()
|
|
expect(before).toContain('omp')
|
|
expect(before).toContain('sleep')
|
|
await dispatcher.callRequest('pty.shutdown', {
|
|
id,
|
|
immediate: true,
|
|
expectedIncarnationId: spawned.incarnationId
|
|
})
|
|
await pause(6000)
|
|
const after = await rows()
|
|
writeFileSync(
|
|
join(output, 'report.json'),
|
|
JSON.stringify({ backend: 'relay-host', before, after, pid: entry.pid, id, home }, null, 2)
|
|
)
|
|
writeFileSync(join(output, 'transcript.txt'), transcript)
|
|
console.log(output)
|
|
expect(after).toBe('')
|
|
} finally {
|
|
if (snapshot) {
|
|
const current = await readProcessTable()
|
|
const owned = [
|
|
...snapshot.descendants,
|
|
...(snapshot.root ? [{ ...snapshot.root, pgid: snapshot.rootPgid }] : [])
|
|
]
|
|
for (const row of current.rows) {
|
|
if (
|
|
owned.some(
|
|
(known) =>
|
|
known.pid === row.pid &&
|
|
known.startedAt === row.startedAt &&
|
|
known.pgid === row.pgid
|
|
)
|
|
) {
|
|
try {
|
|
process.kill(row.pid, 'SIGKILL')
|
|
} catch {}
|
|
}
|
|
}
|
|
}
|
|
await handler.dispose({ waitForPhysicalExit: false })
|
|
rmSync(home, { recursive: true, force: true })
|
|
}
|
|
},
|
|
45000
|
|
)
|