diff options
author | Nitzan Uziely <nitzan@testim.io> | 2021-02-11 21:59:00 +0300 |
---|---|---|
committer | Michaƫl Zasso <targos@protonmail.com> | 2021-02-28 16:46:24 +0300 |
commit | ddae11213302254792758cea21f6e73ae0848552 (patch) | |
tree | fd5cf9eaedf522ee6b1ac781b42d881b2d004afc /test | |
parent | 3b7cb75554dcbdc595a73dcdde1cc2f06840eba6 (diff) |
child_process: fix spawn and fork abort behavior
Fix AbortSignal in Spawn which doesn't actually abort
the process, and fork can emit an AbortError even if
the process was already exited. Add documentation
For killSignal.
Fixes: https://github.com/nodejs/node/issues/37273
PR-URL: https://github.com/nodejs/node/pull/37325
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Diffstat (limited to 'test')
3 files changed, 134 insertions, 28 deletions
diff --git a/test/parallel/test-child-process-exec-abortcontroller-promisified.js b/test/parallel/test-child-process-exec-abortcontroller-promisified.js index fa00f2d2177..b818a00a292 100644 --- a/test/parallel/test-child-process-exec-abortcontroller-promisified.js +++ b/test/parallel/test-child-process-exec-abortcontroller-promisified.js @@ -4,48 +4,47 @@ const assert = require('assert'); const exec = require('child_process').exec; const { promisify } = require('util'); -let pwdcommand, dir; const execPromisifed = promisify(exec); const invalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }; - +let waitCommand = ''; if (common.isWindows) { - pwdcommand = 'echo %cd%'; - dir = 'c:\\windows'; + waitCommand = 'TIMEOUT 120'; } else { - pwdcommand = 'pwd'; - dir = '/dev'; + waitCommand = 'sleep 2m'; } - { const ac = new AbortController(); const signal = ac.signal; - const promise = execPromisifed(pwdcommand, { cwd: dir, signal }); - assert.rejects(promise, /AbortError/).then(common.mustCall()); + const promise = execPromisifed(waitCommand, { signal }); + assert.rejects(promise, /AbortError/, 'post aborted sync signal failed') + .then(common.mustCall()); ac.abort(); } { assert.throws(() => { - execPromisifed(pwdcommand, { cwd: dir, signal: {} }); + execPromisifed(waitCommand, { signal: {} }); }, invalidArgTypeError); } { function signal() {} assert.throws(() => { - execPromisifed(pwdcommand, { cwd: dir, signal }); + execPromisifed(waitCommand, { signal }); }, invalidArgTypeError); } { const ac = new AbortController(); - const signal = (ac.abort(), ac.signal); - const promise = execPromisifed(pwdcommand, { cwd: dir, signal }); + const { signal } = ac; + ac.abort(); + const promise = execPromisifed(waitCommand, { signal }); - assert.rejects(promise, /AbortError/).then(common.mustCall()); + assert.rejects(promise, /AbortError/, 'pre aborted signal failed') + .then(common.mustCall()); } diff --git a/test/parallel/test-child-process-fork-abort-signal.js b/test/parallel/test-child-process-fork-abort-signal.js index 63367479234..93a96836627 100644 --- a/test/parallel/test-child-process-fork-abort-signal.js +++ b/test/parallel/test-child-process-fork-abort-signal.js @@ -1,6 +1,6 @@ 'use strict'; -const { mustCall } = require('../common'); +const { mustCall, mustNotCall } = require('../common'); const { strictEqual } = require('assert'); const fixtures = require('../common/fixtures'); const { fork } = require('child_process'); @@ -12,7 +12,10 @@ const { fork } = require('child_process'); const cp = fork(fixtures.path('child-process-stay-alive-forever.js'), { signal }); - cp.on('exit', mustCall()); + cp.on('exit', mustCall((code, killSignal) => { + strictEqual(code, null); + strictEqual(killSignal, 'SIGTERM'); + })); cp.on('error', mustCall((err) => { strictEqual(err.name, 'AbortError'); })); @@ -26,8 +29,44 @@ const { fork } = require('child_process'); const cp = fork(fixtures.path('child-process-stay-alive-forever.js'), { signal }); - cp.on('exit', mustCall()); + cp.on('exit', mustCall((code, killSignal) => { + strictEqual(code, null); + strictEqual(killSignal, 'SIGTERM'); + })); + cp.on('error', mustCall((err) => { + strictEqual(err.name, 'AbortError'); + })); +} + +{ + // Test passing a different kill signal + const ac = new AbortController(); + const { signal } = ac; + ac.abort(); + const cp = fork(fixtures.path('child-process-stay-alive-forever.js'), { + signal, + killSignal: 'SIGKILL', + }); + cp.on('exit', mustCall((code, killSignal) => { + strictEqual(code, null); + strictEqual(killSignal, 'SIGKILL'); + })); cp.on('error', mustCall((err) => { strictEqual(err.name, 'AbortError'); })); } + +{ + // Test aborting a cp before close but after exit + const ac = new AbortController(); + const { signal } = ac; + const cp = fork(fixtures.path('child-process-stay-alive-forever.js'), { + signal + }); + cp.on('exit', mustCall(() => { + ac.abort(); + })); + cp.on('error', mustNotCall()); + + setTimeout(() => cp.kill(), 1); +} diff --git a/test/parallel/test-child-process-spawn-controller.js b/test/parallel/test-child-process-spawn-controller.js index c25307907c1..36c4112cbe0 100644 --- a/test/parallel/test-child-process-spawn-controller.js +++ b/test/parallel/test-child-process-spawn-controller.js @@ -2,20 +2,25 @@ const common = require('../common'); const assert = require('assert'); -const cp = require('child_process'); +const { spawn } = require('child_process'); +const fixtures = require('../common/fixtures'); +const aliveScript = fixtures.path('child-process-stay-alive-forever.js'); { // Verify that passing an AbortSignal works const controller = new AbortController(); const { signal } = controller; - const echo = cp.spawn('echo', ['fun'], { - encoding: 'utf8', - shell: true, - signal + const cp = spawn(process.execPath, [aliveScript], { + signal, }); - echo.on('error', common.mustCall((e) => { + cp.on('exit', common.mustCall((code, killSignal) => { + assert.strictEqual(code, null); + assert.strictEqual(killSignal, 'SIGTERM'); + })); + + cp.on('error', common.mustCall((e) => { assert.strictEqual(e.name, 'AbortError'); })); @@ -29,13 +34,76 @@ const cp = require('child_process'); controller.abort(); - const echo = cp.spawn('echo', ['fun'], { - encoding: 'utf8', - shell: true, - signal + const cp = spawn(process.execPath, [aliveScript], { + signal, + }); + cp.on('exit', common.mustCall((code, killSignal) => { + assert.strictEqual(code, null); + assert.strictEqual(killSignal, 'SIGTERM'); + })); + + cp.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + })); +} + +{ + // Verify that waiting a bit and closing works + const controller = new AbortController(); + const { signal } = controller; + + const cp = spawn(process.execPath, [aliveScript], { + signal, + }); + + cp.on('exit', common.mustCall((code, killSignal) => { + assert.strictEqual(code, null); + assert.strictEqual(killSignal, 'SIGTERM'); + })); + + cp.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + })); + + setTimeout(() => controller.abort(), 1); +} + +{ + // Test passing a different killSignal + const controller = new AbortController(); + const { signal } = controller; + + const cp = spawn(process.execPath, [aliveScript], { + signal, + killSignal: 'SIGKILL', }); - echo.on('error', common.mustCall((e) => { + cp.on('exit', common.mustCall((code, killSignal) => { + assert.strictEqual(code, null); + assert.strictEqual(killSignal, 'SIGKILL'); + })); + + cp.on('error', common.mustCall((e) => { assert.strictEqual(e.name, 'AbortError'); })); + + setTimeout(() => controller.abort(), 1); +} + +{ + // Test aborting a cp before close but after exit + const controller = new AbortController(); + const { signal } = controller; + + const cp = spawn(process.execPath, [aliveScript], { + signal, + }); + + cp.on('exit', common.mustCall(() => { + controller.abort(); + })); + + cp.on('error', common.mustNotCall()); + + setTimeout(() => cp.kill(), 1); } |