Skip to content

Commit

Permalink
Add result.signalName
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Aug 18, 2024
1 parent b354022 commit 35580a1
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 15 deletions.
20 changes: 16 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
};

Expand All @@ -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),
});
Expand Down
48 changes: 37 additions & 11 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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'));
Expand All @@ -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);
});

0 comments on commit 35580a1

Please sign in to comment.