Files
orca/src/shared/libproc-process-table.test.ts
T
OrcaWinandOrcaWin e280eb0734 test: remove 125 low-value tests (#39513)
* test: remove 124 low-value tests

* test: remove 125 low-value tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 06:59:05 -08:00

656 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const { runProcess } = vi.hoisted(() => ({ runProcess: vi.fn() }))
vi.mock('@orca/process-host', () => ({ runProcess }))
vi.mock('./darwin-terminal-names', () => ({
nameDarwinTerminalDevices: async (devices: ReadonlySet<string>) =>
new Map([...devices].filter((device) => device === '16/4').map((device) => [device, 'ttys004']))
}))
import {
setLibprocAddonForTests,
type LibprocAddon,
type LibprocProcessRow,
type LibprocQuery
} from './libproc-addon'
import { nativeReadWaitersForTests, resetNativeReadsForTests } from './libproc-native-reads'
import { runPsInProcess } from './libproc-process-table'
import { resetPsFallbackForTests } from './libproc-ps-fallback'
import { createProcessTableSnapshotReader } from './process-table-snapshot-reader'
const TTYS004 = (16 << 24) | 4
// 2026-09-03T16:02:01Z, a single-digit day so lstart pads it.
const START = Date.UTC(2026, 8, 3, 16, 2, 1) / 1000
// Pin the locale so lstart formatting doesn't depend on the runner's LANG.
const BUDGET = { timeoutMs: 1000, env: { LC_ALL: 'C' } }
function row(overrides: Partial<LibprocProcessRow>): LibprocProcessRow {
return {
pid: 1,
ppid: 0,
pgid: 1,
tpgid: 0,
tdev: -1,
status: 2,
flags: 0,
sessionLeader: false,
startSec: START,
comm: 'launchd',
command: '/sbin/launchd',
commandIncomplete: true,
...overrides
}
}
const TABLE = [
row({
pid: 4300,
ppid: 4242,
pgid: 4300,
tpgid: 4300,
tdev: TTYS004,
flags: 0x2,
comm: 'node',
command: 'node /usr/local/bin/claude --resume',
commandIncomplete: false
}),
row({
pid: 4242,
ppid: 4200,
pgid: 4242,
tpgid: 4300,
tdev: TTYS004,
flags: 0x2,
sessionLeader: true,
comm: 'zsh',
command: '-zsh',
commandIncomplete: false
}),
// A pane's root login is root-owned and sits under the terminal host.
row({
pid: 4200,
ppid: 1,
pgid: 4200,
tdev: TTYS004,
flags: 0x2,
comm: 'login',
command: '/usr/bin/login'
}),
row({
pid: 4400,
ppid: 4242,
pgid: 4400,
tpgid: 4300,
status: 4,
comm: 'vim',
command: 'vim',
commandIncomplete: false
}),
row({ pid: 4401, ppid: 4242, pgid: 4401, status: 5, comm: 'sh', command: '<defunct>' }),
row({})
]
function tableFor(
query: LibprocQuery,
table: readonly LibprocProcessRow[] = TABLE
): LibprocProcessRow[] | null {
if (query.tty !== undefined && !/^(?:\/dev\/)?ttys004$/.test(query.tty)) {
return null
}
return table
.filter((entry) => !query.pids || query.pids.includes(entry.pid))
.filter((entry) => query.tty === undefined || entry.tdev === TTYS004)
.map((entry) =>
query.command ? entry : { ...entry, command: undefined, commandIncomplete: undefined }
)
}
function installAddon(overrides: Partial<LibprocAddon> = {}): LibprocAddon {
const addon: LibprocAddon = {
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query)),
readProcessCwd: vi.fn(async () => null),
readProcessPath: vi.fn(async () => null),
...overrides
}
setLibprocAddonForTests(addon)
return addon
}
// Orca's euid is pinned, so neither a root runner nor Windows (no geteuid) changes whose argv is "own".
const OWN_UID = 501
const OTHER_UID = 502
const realGeteuid = process.geteuid
beforeEach(() => {
process.geteuid = () => OWN_UID
})
const PS_RESULT = {
code: 0,
signal: null,
stdout: ' 1\n',
stderr: '',
timedOut: false,
outputTruncated: false
}
afterEach(() => {
process.geteuid = realGeteuid
setLibprocAddonForTests(undefined)
resetNativeReadsForTests()
resetPsFallbackForTests()
runProcess.mockReset()
vi.useRealTimers()
})
describe('runPsInProcess', () => {
it('keeps the real ps for invocations it cannot reproduce, and answers a failing addon with /bin/ps', async () => {
installAddon()
expect(await runPsInProcess(['-axo', 'pid=PROCESS_ID'], BUDGET)).toBeNull()
expect(await runPsInProcess(['-axo', 'pid=,uid='], BUDGET)).toBeNull()
expect(await runPsInProcess(['-e', '-o', 'pid='], BUDGET)).toBeNull()
expect(await runPsInProcess(['-p', '1', '-t', 'ttys004', '-o', 'pid='], BUDGET)).toBeNull()
expect(
await runPsInProcess(['-axo', 'lstart='], { env: { TZ: 'America/New_York' }, timeoutMs: 1 })
).toBeNull()
expect(
await runPsInProcess(['-axo', 'lstart='], { env: { LC_ALL: 'de_DE.UTF-8' }, timeoutMs: 1 })
).toBeNull()
installAddon({
readProcessTable: async () => {
throw new Error('sysctl failed')
}
})
runProcess.mockResolvedValue(PS_RESULT)
expect(await runPsInProcess(['-axo', 'pid='], BUDGET)).toEqual(PS_RESULT)
expect(runProcess).toHaveBeenCalledWith(
expect.objectContaining({ program: '/bin/ps', args: ['-axo', 'pid='] })
)
setLibprocAddonForTests(null)
expect(await runPsInProcess(['-axo', 'pid='], BUDGET)).toBeNull()
})
it('times out each caller of a stalled shared read like a killed ps', async () => {
vi.useFakeTimers()
const addon = installAddon({ readProcessTable: vi.fn(() => new Promise<never>(() => {})) })
const short = runPsInProcess(['-p', '4300', '-o', 'pid=,tpgid=,tty='], { timeoutMs: 125 })
const long = runPsInProcess(['-p', '4300', '-o', 'pid=,tpgid=,tty='], { timeoutMs: 1000 })
await vi.advanceTimersByTimeAsync(125)
await expect(short).resolves.toMatchObject({ code: null, timedOut: true, stdout: '' })
await vi.advanceTimersByTimeAsync(875)
await expect(long).resolves.toMatchObject({ timedOut: true })
expect(addon.readProcessTable).toHaveBeenCalledTimes(1)
})
it('asks ps once for argv only it can read, for another user’s process under a pane shell', async () => {
const sudo = row({
pid: 4500,
ppid: 4242,
pgid: 4500,
tpgid: 4300,
tdev: TTYS004,
flags: 0x2,
comm: 'node',
command: '/usr/local/bin/node'
})
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, sudo]))
})
runProcess.mockResolvedValue({
code: 0,
signal: null,
stdout: ' 4500 node /usr/local/lib/claude-code/cli.js\n',
stderr: '',
timedOut: false
})
const args = ['-o', 'pid=,ppid=,pgid=,tpgid=,stat=,command=', '-t', 'ttys004']
const options = { env: { LC_ALL: 'en_US.UTF-8' }, timeoutMs: 1000 }
for (let poll = 0; poll < 2; poll += 1) {
const result = await runPsInProcess(args, options)
expect(result?.stdout).toContain(
'4500 4242 4500 4300 S node /usr/local/lib/claude-code/cli.js'
)
expect(result?.stdout).toContain('4200 1 4200 0 S /usr/bin/login')
}
expect(runProcess).toHaveBeenCalledTimes(1)
expect(runProcess).toHaveBeenCalledWith(
expect.objectContaining({
program: '/bin/ps',
args: ['-ww', '-o', 'pid=,command=', '-p', '4500']
})
)
})
it('never joins a read submitted before the request, so stale rows never reach a caller', async () => {
type Rows = LibprocProcessRow[] | null
let finishStale: (rows: Rows) => void = () => {}
const stale = new Promise<Rows>((resolve) => {
finishStale = resolve
})
const readProcessTable = vi
.fn<(query: LibprocQuery) => Promise<Rows>>()
.mockReturnValueOnce(stale)
.mockImplementation(async (query) => tableFor(query))
installAddon({ readProcessTable })
const args = ['-axo', 'pid=,ppid=']
expect(await runPsInProcess(args, { timeoutMs: 10 })).toMatchObject({ timedOut: true })
const fresh = runPsInProcess(args, BUDGET)
finishStale([row({ pid: 9999, ppid: 4242 })])
expect((await fresh)?.stdout).not.toContain('9999')
expect(readProcessTable).toHaveBeenCalledTimes(2)
})
it('queues past the read cap without retaining timed-out waiters, then hands a stalled pool to budgeted ps', async () => {
vi.useFakeTimers()
const addon = installAddon({ readProcessTable: vi.fn(() => new Promise<never>(() => {})) })
runProcess.mockResolvedValue(PS_RESULT)
const read = (pid: number, timeoutMs = 50) =>
runPsInProcess(['-p', String(pid), '-o', 'pid='], { timeoutMs })
// Eight distinct reads fill the cap and stall; overflow callers wait on one slot notifier.
const capped = Array.from({ length: 8 }, (_, index) => read(5000 + index, 10_000))
const overflow = Array.from({ length: 300 }, (_, index) => read(6000 + index))
expect(nativeReadWaitersForTests()).toBe(300)
await vi.advanceTimersByTimeAsync(50)
expect(nativeReadWaitersForTests()).toBe(0)
// The stalled burst costs the fork budget, not one ps per caller; the rest time out like a killed ps.
await vi.advanceTimersByTimeAsync(50)
const results = await Promise.all(overflow)
expect(results.filter((result) => result?.timedOut === false)).toHaveLength(4)
expect(results.filter((result) => result?.timedOut === true)).toHaveLength(296)
expect(runProcess).toHaveBeenCalledTimes(4)
// The breaker stays open for its cool-down: callers go to ps without queueing on the pool.
await vi.advanceTimersByTimeAsync(1_000)
expect(await read(7000)).toEqual(PS_RESULT)
expect(nativeReadWaitersForTests()).toBe(0)
expect(addon.readProcessTable).toHaveBeenCalledTimes(8)
await vi.advanceTimersByTimeAsync(10_000)
expect(await Promise.all(capped)).toEqual(
Array(8).fill(expect.objectContaining({ timedOut: true }))
)
// Past the cool-down with the cap still held, callers learn it at once instead of after their timeout.
await vi.advanceTimersByTimeAsync(30_000)
let settled: unknown = 'pending'
void read(8000, 5_000).then((result) => {
settled = result
})
await vi.advanceTimersByTimeAsync(0)
expect(settled).toEqual(PS_RESULT)
})
it('recovers argv for a sudo use_pty tree on its own pty, but not for root daemons or GUI helpers', async () => {
const TTYS005 = (16 << 24) | 5
const root = { commandIncomplete: true, flags: 0x2 }
const table = [
...TABLE,
row({
...root,
pid: 4600,
ppid: 4242,
tdev: TTYS004,
comm: 'sudo',
command: '/usr/bin/sudo'
}),
row({
...root,
pid: 4601,
ppid: 4600,
tdev: TTYS005,
comm: 'sudo',
command: '/usr/bin/sudo'
}),
row({ ...root, pid: 4602, ppid: 4601, tdev: TTYS005, comm: 'zsh', command: '/bin/zsh' }),
row({ ...root, pid: 4603, ppid: 4602, tdev: TTYS005, comm: 'node', command: '/opt/node' }),
row({ pid: 300, ppid: 1, comm: 'syslogd', command: '/usr/sbin/syslogd' }),
row({
pid: 700,
ppid: 1,
comm: 'Orca',
command: '/Applications/Orca',
commandIncomplete: false
}),
row({ pid: 701, ppid: 700, comm: 'helper', command: '/usr/libexec/helper' })
]
installAddon({ readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, table)) })
runProcess.mockResolvedValue({ code: 0, signal: null, stdout: '', stderr: '', timedOut: false })
await runPsInProcess(['-axo', 'pid=,command='], { env: { LC_ALL: 'C' }, timeoutMs: 1000 })
const pids = runProcess.mock.calls[0]?.[0].args.at(-1).split(',').map(Number).sort()
expect(pids).toEqual([4600, 4601, 4602, 4603])
})
it('asks ps again once exec changes the program behind a cached pid and start time', async () => {
const shell = row({ pid: 4700, ppid: 4242, tdev: TTYS004, comm: 'zsh', command: '/bin/zsh' })
let current = shell
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, current]))
})
runProcess.mockImplementation(async () => ({
code: 0,
signal: null,
stdout: current === shell ? ' 4700 -zsh\n' : ' 4700 node /opt/claude/cli.js\n',
stderr: '',
timedOut: false
}))
const args = ['-p', '4700', '-o', 'command=']
const options = { env: { LC_ALL: 'C' }, timeoutMs: 1000 }
expect((await runPsInProcess(args, options))?.stdout).toBe('-zsh\n')
expect((await runPsInProcess(args, options))?.stdout).toBe('-zsh\n')
current = { ...shell, comm: 'node', command: '/opt/node' }
expect((await runPsInProcess(args, options))?.stdout).toBe('node /opt/claude/cli.js\n')
expect(runProcess).toHaveBeenCalledTimes(2)
})
it('leaves argv of the user’s own mid-exec processes and of exiting ones to (comm), asking ps only for other users', async () => {
const foreign = { ppid: 4242, tdev: TTYS004, uid: OTHER_UID, flags: 0x2 }
const table = [
...TABLE,
// A pane looping short commands: our own child between fork and exec reads like another user's.
row({ pid: 4800, ppid: 4242, tdev: TTYS004, uid: OWN_UID, comm: 'git', command: '(git)' }),
row({ ...foreign, pid: 4801, flags: 0x2 | 0x2000, comm: 'sudo', command: '(sudo)' }),
row({ ...foreign, pid: 4802, status: 1, comm: 'sudo', command: '(sudo)' }),
row({ ...foreign, pid: 4803, comm: 'node', command: '(node)' })
]
installAddon({ readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, table)) })
runProcess.mockResolvedValue({
code: 0,
signal: null,
stdout: ' 4803 node /opt/claude/cli.js\n',
stderr: '',
timedOut: false
})
const result = await runPsInProcess(['-axo', 'pid=,command='], BUDGET)
expect(result?.stdout).toContain('4800 (git)\n')
expect(result?.stdout).toContain('4803 node /opt/claude/cli.js\n')
expect(runProcess).toHaveBeenCalledTimes(1)
expect(runProcess.mock.calls[0]?.[0].args.at(-1)).toBe('4803')
})
it('shares one argv ps among concurrent readers and waits out a failed recovery before asking again', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'performance'] })
const sudo = row({
pid: 4900,
ppid: 4242,
tdev: TTYS004,
uid: OTHER_UID,
comm: 'node',
command: '(node)'
})
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, sudo]))
})
// ps omits a process that exited, or one it cannot read either.
runProcess.mockResolvedValue({ code: 1, signal: null, stdout: '', stderr: '', timedOut: false })
const read = (): Promise<unknown> =>
runPsInProcess(['-o', 'pid=,command=', '-t', 'ttys004'], BUDGET)
const concurrent = await Promise.all(Array.from({ length: 24 }, read))
expect(concurrent).toEqual(Array(24).fill(expect.objectContaining({ timedOut: false })))
expect(runProcess).toHaveBeenCalledTimes(1)
await read()
expect(runProcess).toHaveBeenCalledTimes(1)
await vi.advanceTimersByTimeAsync(5_000)
await read()
expect(runProcess).toHaveBeenCalledTimes(2)
})
it('reuses recovered argv for at most 1.5s, so a same-comm re-exec is seen', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'performance'] })
const sudo = row({
pid: 5100,
ppid: 4242,
tdev: TTYS004,
uid: OTHER_UID,
comm: 'node',
command: '(node)'
})
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, sudo]))
})
// Same pid, start time and comm: node re-exec'd from Claude into an unrelated server.
let argv = 'node /opt/claude/cli.js'
runProcess.mockImplementation(async () => ({
code: 0,
signal: null,
stdout: ` 5100 ${argv}\n`,
stderr: '',
timedOut: false
}))
const args = ['-p', '5100', '-o', 'command=']
expect((await runPsInProcess(args, BUDGET))?.stdout).toBe(`node /opt/claude/cli.js\n`)
argv = 'node /srv/server.js'
expect((await runPsInProcess(args, BUDGET))?.stdout).toBe(`node /opt/claude/cli.js\n`)
await vi.advanceTimersByTimeAsync(1_500)
expect((await runPsInProcess(args, BUDGET))?.stdout).toBe('node /srv/server.js\n')
expect(runProcess).toHaveBeenCalledTimes(2)
})
it('waits out a failed recovery from when ps gave up, however long it ran', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'performance'] })
const sudo = row({
pid: 5200,
ppid: 4242,
tdev: TTYS004,
uid: OTHER_UID,
comm: 'su',
command: '(su)'
})
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, sudo]))
})
// A loaded host: ps takes 6s, then omits the process.
let finish: () => void = () => {}
runProcess.mockImplementation(
() =>
new Promise((resolve) => {
finish = () => resolve({ code: 1, signal: null, stdout: '', stderr: '', timedOut: false })
})
)
const read = () => runPsInProcess(['-p', '5200', '-o', 'command='], { timeoutMs: 10_000 })
const slow = read()
await vi.advanceTimersByTimeAsync(6_000)
finish()
await slow
await read()
expect(runProcess).toHaveBeenCalledTimes(1)
})
it('recovers a su pane’s argv within the recovery budget however often it is read', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'performance'] })
const su = row({
pid: 5300,
ppid: 4242,
tdev: TTYS004,
uid: OTHER_UID,
comm: 'su',
command: '(su)'
})
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, su]))
})
runProcess.mockResolvedValue({
code: 0,
signal: null,
stdout: ' 5300 su -l\n',
stderr: '',
timedOut: false
})
const args = ['-o', 'pid=,command=', '-t', 'ttys004']
const reads: ReturnType<typeof runPsInProcess>[] = []
for (let poll = 0; poll < 20; poll += 1) {
// readTerminalProcessRows' budget: a read may wait for the next recovery token.
reads.push(runPsInProcess(args, { ...BUDGET, timeoutMs: 3_000 }))
await vi.advanceTimersByTimeAsync(750)
}
for (const read of await Promise.all(reads)) {
expect(read?.stdout).toContain('5300 su -l\n')
}
// The recovery budget: a burst of 2, then one per 2s over the ~16s the reads span.
expect(runProcess.mock.calls.length).toBeLessThanOrEqual(10)
})
it('asks again at once after a recovery ps that timed out, which proved nothing', async () => {
const su = row({
pid: 5350,
ppid: 4242,
tdev: TTYS004,
uid: OTHER_UID,
comm: 'su',
command: '(su)'
})
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, su]))
})
runProcess
.mockResolvedValueOnce({
code: null,
signal: 'SIGTERM',
stdout: '',
stderr: '',
timedOut: true
})
.mockResolvedValue({
code: 0,
signal: null,
stdout: ' 5350 su -l\n',
stderr: '',
timedOut: false
})
const read = () => runPsInProcess(['-p', '5350', '-o', 'command='], BUDGET)
expect((await read())?.stdout).toBe('(su)\n')
expect((await read())?.stdout).toBe('su -l\n')
})
it('recovers the foreground of a terminal where no process of ours is left (exec su)', async () => {
const root = { tdev: TTYS004, uid: OTHER_UID, flags: 0x2, tpgid: 5400 }
const table = [
row({ ...root, pid: 4200, ppid: 1, pgid: 4200, comm: 'login', command: '(login)' }),
row({ ...root, pid: 5400, ppid: 4200, pgid: 5400, comm: 'sh', command: '(sh)' })
]
installAddon({ readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, table)) })
runProcess.mockResolvedValue({
code: 0,
signal: null,
stdout: ' 5400 -sh\n',
stderr: '',
timedOut: false
})
const result = await runPsInProcess(['-o', 'pid=,command=', '-t', 'ttys004'], BUDGET)
expect(result?.stdout).toContain('5400 -sh\n')
// The pane's root login is not in the foreground group, so it never costs a ps.
expect(runProcess.mock.calls.map(([spec]) => spec.args.at(-1))).toEqual(['5400'])
})
describe('recovered argv age in strict evidence', () => {
// Each case its own pid: the argv cache is process-wide.
let pid = 5500
const agent = () =>
row({
pid,
ppid: 4242,
tdev: TTYS004,
uid: OTHER_UID,
comm: 'node',
command: '(node)'
})
const args = ['-o', 'pid=,command=', '-t', 'ttys004']
// The production reader, stamping on the same monotonic clock the fake timers drive.
const strictReader = () =>
createProcessTableSnapshotReader({
runPs: (_fresh, inputAt) =>
runPsInProcess(args, { ...BUDGET, timeoutMs: 10_000, onArgvReadAt: inputAt }).then(
(result) => result?.stdout
),
now: () => performance.now(),
ttlMs: 0
})
const psAnswers = (argv: () => string) =>
runProcess.mockImplementation(async () => ({
code: 0,
signal: null,
stdout: ` ${pid} ${argv()}\n`,
stderr: '',
timedOut: false
}))
beforeEach(() => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'performance'] })
installAddon({
readProcessTable: vi.fn(async (query: LibprocQuery) => tableFor(query, [...TABLE, agent()]))
})
})
it('fresh recipient reads bypass recovered argv cached before the request', async () => {
pid = 5700
let argv = 'node /opt/claude/cli.js'
psAnswers(() => argv)
await runPsInProcess(args, BUDGET)
argv = 'node /srv/server.js'
await vi.advanceTimersByTimeAsync(1)
const observed = await runPsInProcess(args, { ...BUDGET, freshArgv: true })
expect(observed?.stdout).toContain('/srv/server.js')
expect(observed?.stdout).not.toContain('/opt/claude/cli.js')
})
it('ages a capture by the cached argv it reused, so a same-comm re-exec cannot read as fresh', async () => {
pid = 5500
let argv = 'node /opt/claude/cli.js'
psAnswers(() => argv)
const reader = strictReader()
await reader.getSnapshotWithAge()
// node re-execs into a server; same pid, start time and comm.
argv = 'node /srv/server.js'
await vi.advanceTimersByTimeAsync(1_400)
const snapshot = await reader.getSnapshotWithAge()
expect(snapshot.value).toContain('claude')
expect(snapshot.capturedAgeMs).toBeGreaterThanOrEqual(1_400)
})
it('never answers a reader with a recovery begun more than 1.5s before it asked', async () => {
pid = 5600
let finishSlow: (stdout: string) => void = () => {}
runProcess
.mockImplementationOnce(
() =>
new Promise((resolve) => {
finishSlow = (stdout) =>
resolve({ code: 0, signal: null, stdout, stderr: '', timedOut: false })
})
)
.mockResolvedValue({
code: 0,
signal: null,
stdout: ' 5600 node /srv/server.js\n',
stderr: '',
timedOut: false
})
// Two captures (say the daemon's and main's), sharing one recovery that takes 6s.
void strictReader().getSnapshotWithAge()
await vi.advanceTimersByTimeAsync(5_900)
const late = strictReader().getSnapshotWithAge()
await vi.advanceTimersByTimeAsync(100)
finishSlow(' 5600 node /opt/claude/cli.js\n')
const snapshot = await late
expect(snapshot.value).toContain('/srv/server.js')
expect(runProcess).toHaveBeenCalledTimes(2)
})
})
})