From f720393489f2c991c9dfc9a3b067e15ed8f1f396 Mon Sep 17 00:00:00 2001 From: ehmicky Date: Thu, 22 Aug 2024 05:37:02 +0100 Subject: [PATCH] Add `result.signalName` (#3) --- index.js | 20 ++++++++++++++++---- test.js | 48 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 6171a92..aaea50e 100644 --- a/index.js +++ b/index.js @@ -27,10 +27,19 @@ const getResult = async subprocess => { bufferOutput(subprocess, result, 'stderr'); try { - const [exitCode] = await once(subprocess, 'close'); - return {...getOutput(result), exitCode}; + await once(subprocess, 'close'); + return getOutput(subprocess, result); } catch (error) { - throw Object.assign(error, getOutput(result)); + // The `error` event on subprocess is emitted either: + // - Before `spawn`, e.g. for a non-existing executable file. + // Then, `subprocess.pid` is `undefined` and `close` is never emitted. + // - After `spawn`, e.g. for the `signal` option. + // Then, `subprocess.pid` is set and `close` is always emitted. + if (subprocess.pid !== undefined) { + await Promise.allSettled([once(subprocess, 'close')]); + } + + throw Object.assign(error, getOutput(subprocess, result)); } }; @@ -41,7 +50,10 @@ const bufferOutput = (subprocess, result, streamName) => { }); }; -const getOutput = ({stdout, stderr}) => ({ +const getOutput = ({exitCode, signalCode}, {stdout, stderr}) => ({ + // `exitCode` can be a negative number (`errno`) when the `error` event is emitted on the subprocess + ...(exitCode === null || exitCode < 0 ? {} : {exitCode}), + ...(signalCode === null ? {} : {signalName: signalCode}), stdout: stripNewline(stdout), stderr: stripNewline(stderr), }); diff --git a/test.js b/test.js index cbbc5b1..46a76a8 100644 --- a/test.js +++ b/test.js @@ -12,13 +12,45 @@ const arrayFromAsync = async asyncIterable => { }; test('can pass options object without any arguments', async t => { - const {exitCode} = await nanoSpawn('node', {timeout: 1}); - t.is(exitCode, null); + const {exitCode, signalName} = await nanoSpawn('node', {timeout: 1}); + t.is(exitCode, undefined); + t.is(signalName, 'SIGTERM'); }); -test('result.exitCode is set', async t => { - const {exitCode} = await nanoSpawn('node', ['--version']); +test('result.exitCode|signalName on success', async t => { + const {exitCode, signalName} = await nanoSpawn('node', ['--version']); t.is(exitCode, 0); + t.is(signalName, undefined); +}); + +test('result.exitCode|signalName on non-0 exit code', async t => { + const {exitCode, signalName} = await nanoSpawn('node', ['-e', 'process.exit(2)']); + t.is(exitCode, 2); + t.is(signalName, undefined); +}); + +test('result.exitCode|signalName on signal termination', async t => { + const {exitCode, signalName} = await nanoSpawn('node', {timeout: 1}); + t.is(exitCode, undefined); + t.is(signalName, 'SIGTERM'); +}); + +test('result.exitCode|signalName on invalid child_process options', t => { + const {exitCode, signalName} = t.throws(() => nanoSpawn('node', ['--version'], {nativeOptions: {detached: 'true'}})); + t.is(exitCode, undefined); + t.is(signalName, undefined); +}); + +test('result.exitCode|signalName on "error" event before spawn', async t => { + const {exitCode, signalName} = await t.throwsAsync(nanoSpawn('non-existent-command')); + t.is(exitCode, undefined); + t.is(signalName, undefined); +}); + +test('result.exitCode|signalName on "error" event after spawn', async t => { + const {exitCode, signalName} = await t.throwsAsync(nanoSpawn('node', {signal: AbortSignal.abort()})); + t.is(exitCode, undefined); + t.is(signalName, 'SIGTERM'); }); test('result.stdout is set', async t => { @@ -104,12 +136,6 @@ test('stdout handles Windows newline at the end', async t => { t.deepEqual(lines, ['Hello', 'World']); }); -test('rejects on error', async t => { - await t.throwsAsync( - nanoSpawn('non-existent-command'), - ); -}); - test('returns a promise', async t => { const result = nanoSpawn('node', ['--version']); t.false(Object.prototype.propertyIsEnumerable.call(result, 'then')); @@ -123,5 +149,5 @@ test('promise.subprocess is set', async t => { promise.subprocess.kill(); const {exitCode} = await promise; - t.is(exitCode, null); + t.is(exitCode, undefined); });