Skip to content

Commit 58614a2

Browse files
taylorfsteeleclaude
andcommitted
test(local-process): destroy the sandbox in a finally block
A failed assertion or a failed stdout wait skipped the teardown, so the `sleep 30` child and its sandbox survived the test. Measured on a failing run: two host processes remained. `kill-tree.test.ts` already states this rule for the same construct. `destroy()` kills the process tree unconditionally, so the explicit `proc.kill()` is redundant and goes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent bebb7dc commit 58614a2

1 file changed

Lines changed: 32 additions & 28 deletions

File tree

‎packages/ai-sandbox-local-process/tests/local-process.test.ts‎

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -233,38 +233,42 @@ describe('local-process stdin errors', () => {
233233
'a write to a child that closed its stdin rejects without an uncaught error (skipped on Windows: named pipes do not report EPIPE the same way)',
234234
async () => {
235235
const sbx = await fresh()
236-
// `exec 0<&-` makes the child close its own stdin while it keeps running:
237-
// the write end stays open, and no process holds the read end.
238-
const proc = await sbx.process.spawn('exec 0<&- ; echo closed ; sleep 30')
239-
// The shell echoes after it closes fd 0. An earlier write only fills the
240-
// pipe buffer and resolves, which would prove nothing.
241-
let closed = false
242-
for await (const chunk of proc.stdout) {
243-
if (chunk.includes('closed')) {
244-
closed = true
245-
break
236+
// `finally`: the `sleep 30` below outlives a failed assertion otherwise.
237+
try {
238+
// `exec 0<&-` makes the child close its own stdin while it keeps
239+
// running: the write end stays open, and no process holds the read end.
240+
const proc = await sbx.process.spawn(
241+
'exec 0<&- ; echo closed ; sleep 30',
242+
)
243+
// The shell echoes after it closes fd 0. An earlier write only fills the
244+
// pipe buffer and resolves, which would prove nothing.
245+
let closed = false
246+
for await (const chunk of proc.stdout) {
247+
if (chunk.includes('closed')) {
248+
closed = true
249+
break
250+
}
246251
}
247-
}
248-
expect(closed).toBe(true)
252+
expect(closed).toBe(true)
249253

250-
const uncaught: Array<Error> = []
251-
const onUncaught = (error: Error): void => {
252-
uncaught.push(error)
253-
}
254-
process.on('uncaughtException', onUncaught)
255-
try {
256-
await expect(proc.stdin.write('probe\n')).rejects.toThrow(/EPIPE/)
257-
// Node emits the socket's `error` event on a `nextTick` after the
258-
// callback, and that queue always drains before a `setImmediate`.
259-
await new Promise((resolve) => setImmediate(resolve))
254+
const uncaught: Array<Error> = []
255+
const onUncaught = (error: Error): void => {
256+
uncaught.push(error)
257+
}
258+
process.on('uncaughtException', onUncaught)
259+
try {
260+
await expect(proc.stdin.write('probe\n')).rejects.toThrow(/EPIPE/)
261+
// Node emits the socket's `error` event on a `nextTick` after the
262+
// callback, and that queue always drains before a `setImmediate`.
263+
await new Promise((resolve) => setImmediate(resolve))
264+
} finally {
265+
process.off('uncaughtException', onUncaught)
266+
}
267+
268+
expect(uncaught).toEqual([])
260269
} finally {
261-
process.off('uncaughtException', onUncaught)
270+
await sbx.destroy()
262271
}
263-
264-
expect(uncaught).toEqual([])
265-
266-
await proc.kill()
267-
await sbx.destroy()
268272
},
269273
30_000,
270274
)

0 commit comments

Comments
 (0)