From 8be800fdc31e0747d2b1717d6f57a90a0f569540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iiro=20J=C3=A4ppinen?= Date: Mon, 18 Apr 2022 12:21:50 +0300 Subject: [PATCH] test: add test for kill child processes on error --- lib/resolveTaskFn.js | 6 +++-- test/resolveTaskFn.spec.js | 53 +++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/resolveTaskFn.js b/lib/resolveTaskFn.js index 6cbf9b831..77a4ab777 100644 --- a/lib/resolveTaskFn.js +++ b/lib/resolveTaskFn.js @@ -62,8 +62,10 @@ const interruptExecutionOnError = (ctx, execaChildProcess) => { if (ctx.errors.size > 0) { clearInterval(loopIntervalId) - const ids = await pidTree(execaChildProcess.pid) - ids.forEach((id) => process.kill(id)) + const childPids = await pidTree(execaChildProcess.pid) + for (const pid of childPids) { + process.kill(pid) + } // The execa process is killed separately in order // to get the `KILLED` status. diff --git a/test/resolveTaskFn.spec.js b/test/resolveTaskFn.spec.js index 0f3cc29c0..f46d57998 100644 --- a/test/resolveTaskFn.spec.js +++ b/test/resolveTaskFn.spec.js @@ -1,4 +1,5 @@ import execa from 'execa' +import pidTree from 'pidtree' import { resolveTaskFn } from '../lib/resolveTaskFn' import { getInitialState } from '../lib/state' @@ -6,9 +7,13 @@ import { TaskError } from '../lib/symbols' import { createExecaReturnValue } from './utils/createExecaReturnValue' +jest.useFakeTimers() + +jest.mock('pidtree', () => jest.fn(async () => [])) + const defaultOpts = { files: ['test.js'] } -function mockExecaImplementationOnce(value) { +const mockExecaImplementationOnce = (value) => { execa.mockImplementationOnce(() => createExecaReturnValue(value)) } @@ -350,6 +355,52 @@ describe('resolveTaskFn', () => { context.errors.add({}) + jest.runAllTimers() + await expect(taskPromise).rejects.toThrowErrorMatchingInlineSnapshot(`"node [KILLED]"`) }) + + it('should also kill child processes of killed execa processes', async () => { + expect.assertions(3) + + execa.mockImplementationOnce(() => + createExecaReturnValue( + { + stdout: 'a-ok', + stderr: '', + code: 0, + cmd: 'mock cmd', + failed: false, + killed: false, + signal: null, + }, + 1000 + ) + ) + + const realKill = process.kill + const mockKill = jest.fn() + Object.defineProperty(process, 'kill', { + value: mockKill, + }) + + pidTree.mockImplementationOnce(() => ['1234']) + + const taskFn = resolveTaskFn({ command: 'node' }) + + const context = getInitialState() + const taskPromise = taskFn(context) + + context.errors.add({}) + jest.runAllTimers() + + await expect(taskPromise).rejects.toThrowErrorMatchingInlineSnapshot(`"node [KILLED]"`) + + expect(mockKill).toHaveBeenCalledTimes(1) + expect(mockKill).toHaveBeenCalledWith('1234') + + Object.defineProperty(process, 'kill', { + value: realKill, + }) + }) })