mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 08:02:31 +00:00
* fix: keep macOS shell ownership proof within recovery budget * fix: parse the shell-proof column set with its own anchored parser The narrower macOS capture (`pid ppid pgid tpgid stat command`) was fed to the shared lenient parser, whose optional tty/start pair has no `tty=` column left to absorb it. It then eats the head of any argv shaped `python 3 app.py` (parsing command as `app.py`, tty as `/usr/bin/python`), and turns a command-less row into a garbage pid/stat pair. Either can flip a shell ownership verdict, which is what gates dead-TUI recovery. Give the column set a named constant and a parser anchored to exactly those six columns, beside its `CHEAP_PS_ARGS` sibling. A capture that yields no rows now raises `empty_capture` rather than reading as a machine with no processes. Update the `confirmShellForegroundProcess` fixtures from the 4-column legacy shape to the 6 columns the darwin reader actually emits; that describe block already forces `platform=darwin`, so the stale fixtures were failing.
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
|
|
const { execFileMock } = vi.hoisted(() => ({ execFileMock: vi.fn() }))
|
|
vi.mock('node:child_process', () => ({ execFile: execFileMock }))
|
|
|
|
import {
|
|
getFreshShellForegroundSnapshot,
|
|
getProcessTableSnapshot,
|
|
resetProcessTableSnapshotForTests
|
|
} from './process-table-snapshot-reader'
|
|
import { parseShellForegroundRows } from './process-table-snapshot'
|
|
|
|
type Callback = (error: Error | null, result: { stdout: string; stderr: string }) => void
|
|
const platform = Object.getOwnPropertyDescriptor(process, 'platform')!
|
|
const shell = '100 99 100 100 Ss+ /bin/zsh -l'
|
|
|
|
beforeEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: 'darwin' })
|
|
execFileMock.mockReset()
|
|
resetProcessTableSnapshotForTests()
|
|
})
|
|
afterEach(() => Object.defineProperty(process, 'platform', platform))
|
|
|
|
it('answers concurrent shell proofs without waiting for a pending full capture', async () => {
|
|
let finishFull!: Callback
|
|
execFileMock.mockImplementation((_program, args: string[], _options, callback: Callback) => {
|
|
if (args[1]?.includes('tty=')) {
|
|
finishFull = callback
|
|
} else {
|
|
expect(args).toEqual(['-axo', 'pid=,ppid=,pgid=,tpgid=,stat=,command='])
|
|
callback(null, { stdout: shell, stderr: '' })
|
|
}
|
|
})
|
|
const full = getProcessTableSnapshot()
|
|
const [first, second] = await Promise.all([
|
|
getFreshShellForegroundSnapshot(),
|
|
getFreshShellForegroundSnapshot()
|
|
])
|
|
expect(first).toEqual([
|
|
{ pid: 100, ppid: 99, pgid: 100, tpgid: 100, stat: 'Ss+', command: '/bin/zsh -l' }
|
|
])
|
|
expect(second).toBe(first)
|
|
expect(execFileMock).toHaveBeenCalledTimes(2)
|
|
finishFull(null, { stdout: shell, stderr: '' })
|
|
await full
|
|
await getFreshShellForegroundSnapshot()
|
|
expect(execFileMock).toHaveBeenCalledTimes(3)
|
|
})
|
|
|
|
it('requires a new capture after an earlier shell proof has started', async () => {
|
|
const callbacks: Callback[] = []
|
|
execFileMock.mockImplementation((_program, _args, _options, callback: Callback) => {
|
|
callbacks.push(callback)
|
|
})
|
|
const first = getFreshShellForegroundSnapshot()
|
|
await vi.waitFor(() => expect(callbacks).toHaveLength(1))
|
|
const second = getFreshShellForegroundSnapshot()
|
|
callbacks[0]!(null, { stdout: shell, stderr: '' })
|
|
await first
|
|
await vi.waitFor(() => expect(callbacks).toHaveLength(2))
|
|
callbacks[1]!(null, { stdout: shell.replace('Ss+', 'Ss'), stderr: '' })
|
|
expect((await second)[0]?.stat).toBe('Ss')
|
|
})
|
|
|
|
// With no `tty=` column to absorb them, the shared parser read `python`/`3` as tty/start.
|
|
it('keeps an argv whose second token is numeric', () => {
|
|
expect(parseShellForegroundRows('101 100 101 101 S+ /usr/bin/python 3 app.py')).toEqual([
|
|
{ pid: 101, ppid: 100, pgid: 101, tpgid: 101, stat: 'S+', command: '/usr/bin/python 3 app.py' }
|
|
])
|
|
})
|
|
|
|
it('rejects an unreadable shell capture', async () => {
|
|
execFileMock.mockImplementation((_program, _args, _options, callback: Callback) => {
|
|
callback(null, { stdout: '', stderr: '' })
|
|
})
|
|
await expect(getFreshShellForegroundSnapshot()).rejects.toThrow('empty_capture')
|
|
})
|