Skip to content

Commit 8be800f

Browse files
committed
test: add test for kill child processes on error
1 parent 877ab4c commit 8be800f

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

lib/resolveTaskFn.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ const interruptExecutionOnError = (ctx, execaChildProcess) => {
6262
if (ctx.errors.size > 0) {
6363
clearInterval(loopIntervalId)
6464

65-
const ids = await pidTree(execaChildProcess.pid)
66-
ids.forEach((id) => process.kill(id))
65+
const childPids = await pidTree(execaChildProcess.pid)
66+
for (const pid of childPids) {
67+
process.kill(pid)
68+
}
6769

6870
// The execa process is killed separately in order
6971
// to get the `KILLED` status.

test/resolveTaskFn.spec.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import execa from 'execa'
2+
import pidTree from 'pidtree'
23

34
import { resolveTaskFn } from '../lib/resolveTaskFn'
45
import { getInitialState } from '../lib/state'
56
import { TaskError } from '../lib/symbols'
67

78
import { createExecaReturnValue } from './utils/createExecaReturnValue'
89

10+
jest.useFakeTimers()
11+
12+
jest.mock('pidtree', () => jest.fn(async () => []))
13+
914
const defaultOpts = { files: ['test.js'] }
1015

11-
function mockExecaImplementationOnce(value) {
16+
const mockExecaImplementationOnce = (value) => {
1217
execa.mockImplementationOnce(() => createExecaReturnValue(value))
1318
}
1419

@@ -350,6 +355,52 @@ describe('resolveTaskFn', () => {
350355

351356
context.errors.add({})
352357

358+
jest.runAllTimers()
359+
353360
await expect(taskPromise).rejects.toThrowErrorMatchingInlineSnapshot(`"node [KILLED]"`)
354361
})
362+
363+
it('should also kill child processes of killed execa processes', async () => {
364+
expect.assertions(3)
365+
366+
execa.mockImplementationOnce(() =>
367+
createExecaReturnValue(
368+
{
369+
stdout: 'a-ok',
370+
stderr: '',
371+
code: 0,
372+
cmd: 'mock cmd',
373+
failed: false,
374+
killed: false,
375+
signal: null,
376+
},
377+
1000
378+
)
379+
)
380+
381+
const realKill = process.kill
382+
const mockKill = jest.fn()
383+
Object.defineProperty(process, 'kill', {
384+
value: mockKill,
385+
})
386+
387+
pidTree.mockImplementationOnce(() => ['1234'])
388+
389+
const taskFn = resolveTaskFn({ command: 'node' })
390+
391+
const context = getInitialState()
392+
const taskPromise = taskFn(context)
393+
394+
context.errors.add({})
395+
jest.runAllTimers()
396+
397+
await expect(taskPromise).rejects.toThrowErrorMatchingInlineSnapshot(`"node [KILLED]"`)
398+
399+
expect(mockKill).toHaveBeenCalledTimes(1)
400+
expect(mockKill).toHaveBeenCalledWith('1234')
401+
402+
Object.defineProperty(process, 'kill', {
403+
value: realKill,
404+
})
405+
})
355406
})

0 commit comments

Comments
 (0)