Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve cleanup option (code quality, tests, documentation) #225

Merged
merged 7 commits into from May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion fixtures/sub-process
Expand Up @@ -3,5 +3,6 @@
const execa = require('..');

const cleanup = process.argv[2] === 'true';
const subprocess = execa('node', ['./fixtures/forever'], {cleanup});
const detached = process.argv[3] === 'true';
const subprocess = execa('node', ['./fixtures/forever'], {cleanup, detached});
process.send(subprocess.pid);
7 changes: 7 additions & 0 deletions fixtures/sub-process-exit
@@ -0,0 +1,7 @@
#!/usr/bin/env node
'use strict';
const execa = require('..');

const cleanup = process.argv[2] === 'true';
const detached = process.argv[3] === 'true';
execa('node', ['./fixtures/noop'], {cleanup, detached});
4 changes: 3 additions & 1 deletion index.d.ts
Expand Up @@ -101,7 +101,9 @@ declare namespace execa {
readonly reject?: boolean;

/**
Keep track of the spawned process and `kill` it when the parent process exits.
Kill the spawned process when the parent process exits unless either:
- the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached)
- the parent process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit

@default true
*/
Expand Down
8 changes: 2 additions & 6 deletions index.js
Expand Up @@ -93,11 +93,6 @@ function handleArgs(command, args, options = {}) {

options.stdio = stdio(options);

if (options.detached) {
// #115
options.cleanup = false;
}

if (process.platform === 'win32' && path.basename(command, '.exe') === 'cmd') {
// #116
args.unshift('/q');
Expand Down Expand Up @@ -264,8 +259,9 @@ const execa = (command, args, options) => {
return Promise.reject(error);
}

// #115
let removeExitHandler;
if (parsed.options.cleanup) {
if (parsed.options.cleanup && !parsed.options.detached) {
removeExitHandler = onExit(() => {
spawned.kill();
});
Expand Down
4 changes: 3 additions & 1 deletion readme.md
Expand Up @@ -277,7 +277,9 @@ Setting this to `false` resolves the promise with the error instead of rejecting
Type: `boolean`<br>
Default: `true`

Keep track of the spawned process and `kill` it when the parent process exits.
Kill the spawned process when the parent process exits unless either:
- the spawned process is [`detached`](https://nodejs.org/api/child_process.html#child_process_options_detached)
- the parent process is terminated abruptly, for example, with `SIGKILL` as opposed to `SIGTERM` or a normal exit

#### encoding

Expand Down
31 changes: 25 additions & 6 deletions test.js
Expand Up @@ -485,8 +485,19 @@ test(command, ' foo bar', 'foo', 'bar');
test(command, ' baz quz', 'baz', 'quz');
test(command, '');

async function spawnAndKill(t, signal, cleanup, isKilled) {
const subprocess = execa('sub-process', [cleanup], {stdio: ['ignore', 'ignore', 'ignore', 'ipc']});
// When child process exits before parent process
async function spawnAndExit(t, cleanup, detached) {
await t.notThrowsAsync(execa('sub-process-exit', [cleanup, detached]));
}

test('spawnAndExit', spawnAndExit, false, false);
test('spawnAndExit cleanup', spawnAndExit, true, false);
test('spawnAndExit detached', spawnAndExit, false, true);
test('spawnAndExit cleanup detached', spawnAndExit, true, true);

// When parent process exits before child process
async function spawnAndKill(t, signal, cleanup, detached, isKilled) {
const subprocess = execa('sub-process', [cleanup, detached], {stdio: ['ignore', 'ignore', 'ignore', 'ipc']});

const pid = await pEvent(subprocess, 'message');
t.true(Number.isInteger(pid));
Expand All @@ -498,6 +509,10 @@ async function spawnAndKill(t, signal, cleanup, isKilled) {

t.false(isRunning(subprocess.pid));
t.is(isRunning(pid), !isKilled);

if (!isKilled) {
process.kill(pid, 'SIGKILL');
}
}

// Without `options.cleanup`:
Expand All @@ -507,10 +522,14 @@ async function spawnAndKill(t, signal, cleanup, isKilled) {
// With `options.cleanup`, subprocesses are always killed
// - `options.cleanup` with SIGKILL is a noop, since it cannot be handled
const exitIfWindows = process.platform === 'win32';
test('cleanup true - SIGTERM', spawnAndKill, 'SIGTERM', 'true', true);
test('cleanup false - SIGTERM', spawnAndKill, 'SIGTERM', 'false', exitIfWindows);
test('cleanup true - SIGKILL', spawnAndKill, 'SIGKILL', 'true', exitIfWindows);
test('cleanup false - SIGKILL', spawnAndKill, 'SIGKILL', 'false', exitIfWindows);
test('spawnAndKill SIGTERM', spawnAndKill, 'SIGTERM', false, false, exitIfWindows);
test('spawnAndKill SIGKILL', spawnAndKill, 'SIGKILL', false, false, exitIfWindows);
test('spawnAndKill cleanup SIGTERM', spawnAndKill, 'SIGTERM', true, false, true);
test('spawnAndKill cleanup SIGKILL', spawnAndKill, 'SIGKILL', true, false, exitIfWindows);
test('spawnAndKill detached SIGTERM', spawnAndKill, 'SIGTERM', false, true, false);
test('spawnAndKill detached SIGKILL', spawnAndKill, 'SIGKILL', false, true, false);
test('spawnAndKill cleanup detached SIGTERM', spawnAndKill, 'SIGTERM', true, true, false);
test('spawnAndKill cleanup detached SIGKILL', spawnAndKill, 'SIGKILL', true, true, false);

test('execa.shell() supports the `shell` option', async t => {
const {stdout} = await execa.shell('node fixtures/noop foo', {
Expand Down