Skip to content

Commit

Permalink
changed options interface, 100% test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
bingtimren committed Jun 8, 2021
1 parent 723e2b9 commit fcf73c8
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 41 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ If the process exits or encounter an error before the expected output, the retur
The resolved value is the line of output that matches the pattern.

```Typescript
export function promiseOutputPattern(
process: child_process.ChildProcess, // the child process
pattern: string | RegExp,
watchStdout: boolean = true,
watchStderr: boolean = true,
timeoutInMs?: number, // if provided, will wait for timeout and, if not resolved, reject with an error
killProcessIfTimeout: boolean = false // if timed out & this option is true, kill the child process before rejects
): Promise<string>
/**
* Return a Promise that resolves to a string (line of output) when a pattern to be outputed from the process's stdout or stderr
* @param process
* @param pattern - string or RegExp to match against the output
* @param options - (optional) options
* - watchStdout
* - watchStderr
* - timeoutInMs - time in ms after which the Promise will be rejected
* - killProcessIfTimeout - when timeout, whether or not to kill the process before rejection
*/
```

## promiseKilled
Expand All @@ -69,8 +71,8 @@ Parameters:
```Typescript
export async function promiseKilled(
process: ChildProcess,
signal?: string
): Promise<number | string>
signal?: number | NodeJS.Signals
): Promise<number | NodeJS.Signals>
```

Usage example:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"fix:tslint": "tslint --fix --project .",
"test": "run-s build test:*",
"test:lint": "tslint --project . && prettier \"src/**/*.ts\" --list-different",
"test:unit": "nyc ava",
"test:unit": "nyc ava -c 1",
"watch": "run-s clean build:main && run-p \"build:main -- -w\" \"test:unit -- --watch\"",
"cov": "run-s build test:unit cov:html && opn coverage/index.html",
"cov:html": "nyc report --reporter=html",
Expand Down
3 changes: 3 additions & 0 deletions src/lib/echo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ test('echo child process (expect two lines of output)', async t => {
echoChildProcessOutput(child, {
outPrefix: 'life, universe and everything:'
});
echoChildProcessOutput(child, { echoStderr: false });
echoChildProcessOutput(child, { echoStdout: false });
echoChildProcessOutput(child);
});
});
1 change: 1 addition & 0 deletions src/lib/echo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function echoReadable(
output: Writable,
prefix?: string
): void {
/* istanbul ignore else */
if (input) {
const reader = readline.createInterface({ input });
reader.on('line', line => {
Expand Down
32 changes: 28 additions & 4 deletions src/lib/promise-exit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ test('when error should throw', async t => {
t.fail();
});

test('check timeout without kill', async t => {
test('check timeout without kill, with sleep', async t => {
const child = child_process.spawn('bash', ['-c', 'sleep 5;echo something']);
const normalExitPromise = promiseExit(child);
await t.throwsAsync(
async () => {
await promiseExit(child, 500, false);
await promiseExit(child, {
killProcessIfTimeout: false,
timeoutInMs: 500
});
},
null,
'Wait timeout'
Expand All @@ -49,12 +52,15 @@ test('check timeout without kill', async t => {
t.is(normalExitResult[1], null);
});

test('check timeout and kill', async t => {
test('check timeout and kill, exit before timeout', async t => {
const child = child_process.spawn('bash', ['-c', 'sleep 5;echo something']);
const normalExitPromise = promiseExit(child);
await t.throwsAsync(
async () => {
await promiseExit(child, 500, true);
await promiseExit(child, {
killProcessIfTimeout: true,
timeoutInMs: 500
});
},
null,
'Wait timeout'
Expand All @@ -64,6 +70,24 @@ test('check timeout and kill', async t => {
t.is(typeof normalExitResult[1], 'string');
});

test('check timeout without kill', async t => {
const child = child_process.spawn('bash', ['-c', 'sleep 1;echo something']);
const normalExitPromise = promiseExit(child, { timeoutInMs: 2000 });
await t.throwsAsync(
async () => {
await promiseExit(child, {
killProcessIfTimeout: false,
timeoutInMs: 200
});
},
null,
'Wait timeout'
);
const normalExitResult = await normalExitPromise;
t.is(normalExitResult[0], 0);
t.is(normalExitResult[1], null);
});

// test('check output pattern (with timeout but in time)', async t => {
// const child = child_process.spawn('bash', [
// '-c',
Expand Down
26 changes: 19 additions & 7 deletions src/lib/promise-exit.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import * as child_process from 'child_process';

interface Options {
timeoutInMs: number | undefined;
killProcessIfTimeout: boolean;
}

const defaultOption: Options = {
killProcessIfTimeout: false,
timeoutInMs: undefined
};

/**
* returns a Promise that resolves on the child process's exit event (
* when the process exits or terminated by a signal), or rejects on the
* child process's error event, or rejects upon timeout
*
* @param process - the child process
* @param timeoutInMs - an optional timeout
* @param killProcessIfTimeout - if upon timeout, whether or not to kill the child process
* @param options - (optional) options object
* timeoutInMs - an optional timeout
* killProcessIfTimeout - if upon timeout, whether or not to kill the child process
* @returns the Promise
*/

export function promiseExit(
process: child_process.ChildProcess,
timeoutInMs?: number,
killProcessIfTimeout: boolean = false
options: Partial<Options> = {}
): Promise<[number | null, NodeJS.Signals | null]> {
options = { ...defaultOption, ...options };
let solveOrRejected = false;
return new Promise((resolve, reject) => {
process.on('exit', (code, signal) => {
Expand All @@ -25,16 +37,16 @@ export function promiseExit(
solveOrRejected = true;
reject(error);
});
if (timeoutInMs !== undefined) {
if (options.timeoutInMs !== undefined) {
setTimeout(() => {
if (!solveOrRejected) {
if (killProcessIfTimeout) {
if (options.killProcessIfTimeout) {
process.kill();
}
solveOrRejected = true;
reject(new Error('Wait timeout'));
}
}, timeoutInMs);
}, options.timeoutInMs);
}
});
}
30 changes: 30 additions & 0 deletions src/lib/promise-killed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,36 @@ test('promise killed', async t => {
});
});

test('promise killed, expect exit before kill', async t => {
await t.notThrowsAsync(async () => {
const child = child_process.spawn('bash', ['-c', 'echo THE answer is 42'], {
stdio: 'inherit'
});
await new Promise(resolve => {
setTimeout(resolve, 2000);
});
const code = await promiseKilled(child);
t.is(code, 0);
t.is(child.killed, false);
t.is(child.exitCode, 0);
});
});

test('promise killed, kill before promiseKill', async t => {
await t.notThrowsAsync(async () => {
const child = child_process.spawn('bash', ['-c', 'echo THE answer is 42'], {
stdio: 'inherit'
});
child.kill();
await new Promise(resolve => {
setTimeout(resolve, 500);
});
const signal = await promiseKilled(child);
t.is(signal, 'SIGTERM');
t.is(child.killed, true);
});
});

test('promise-kill when error should throw', async t => {
const child = child_process.spawn('debracadebraonooi'); // error
try {
Expand Down
12 changes: 10 additions & 2 deletions src/lib/promise-killed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ import { ChildProcess } from 'child_process';
export async function promiseKilled(
process: ChildProcess,
signal?: number | NodeJS.Signals
): Promise<number | string> {
): Promise<number | NodeJS.Signals> {
return new Promise((resolve, reject) => {
process.on('exit', (code, killSignal) => {
resolve(code !== null ? code : (killSignal as number | string)); // one of the two will be non-null per Node documentation
/* istanbul ignore next */
resolve(code !== null ? code : (killSignal as NodeJS.Signals)); // one of the two will be non-null per Node documentation
});
process.on('error', err => {
reject(err);
});
if (typeof process.exitCode === 'number' || process.killed) {
resolve(
process.exitCode !== null
? process.exitCode
: (process.signalCode as NodeJS.Signals)
);
}
process.kill(signal);
});
}
59 changes: 55 additions & 4 deletions src/lib/promise-output.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ test('check output pattern (string)', async t => {
});
});

test('check output pattern (dup string)', async t => {
await t.notThrowsAsync(async () => {
const child = child_process.spawn('bash', [
'-c',
'echo answer 42;echo answer 42'
]);
await promiseOutputPattern(child, 'answer 42');
});
});

test('check output pattern (not found till end)', async t => {
const child = child_process.spawn('bash', ['-c', 'echo answer 42']);
try {
Expand Down Expand Up @@ -53,7 +63,11 @@ test('check output pattern (timeout)', async t => {
]);
await t.throwsAsync(
async () => {
await promiseOutputPattern(child, 'DEF', true, true, 10);
await promiseOutputPattern(child, 'DEF', {
timeoutInMs: 10,
watchStderr: true,
watchStdout: true
});
},
null,
'Wait timeout'
Expand All @@ -69,7 +83,10 @@ test('check output pattern (with timeout but in time)', async t => {
await t.notThrowsAsync(async () => {
t.is(
'0123ABC4567',
await promiseOutputPattern(child, 'ABC', true, true, 2000, true)
await promiseOutputPattern(child, 'ABC', {
killProcessIfTimeout: true,
timeoutInMs: 2000
})
);
// sleep 3 seconds where timeout trigger would have been fired
await new Promise<void>(resolve => {
Expand All @@ -90,7 +107,36 @@ test('check output pattern (timeout, only stderr)', async t => {
]);
await t.throwsAsync(
async () => {
await promiseOutputPattern(child, 'ABC', false, true, 2000, true);
await promiseOutputPattern(child, 'ABC', {
killProcessIfTimeout: true,

timeoutInMs: 2000,

watchStderr: true,

watchStdout: false
});
},
null,
'Wait timeout'
);
});

test('check output pattern (timeout, only stdout)', async t => {
const child = child_process.spawn('bash', [
'-c',
'>&2 echo ABC;sleep 10;echo DEF'
]);
await t.throwsAsync(
async () => {
await promiseOutputPattern(child, 'ABC', {
killProcessIfTimeout: true,
timeoutInMs: 2000,

watchStderr: false,

watchStdout: true
});
},
null,
'Wait timeout'
Expand All @@ -104,7 +150,12 @@ test('check output pattern (timeout & kill)', async t => {
]);
await t.throwsAsync(
async () => {
await promiseOutputPattern(child, 'DEF', true, true, 10, true);
await promiseOutputPattern(child, 'DEF', {
killProcessIfTimeout: true,
timeoutInMs: 10,
watchStderr: true,
watchStdout: true
});
},
null,
'Wait timeout'
Expand Down
Loading

0 comments on commit fcf73c8

Please sign in to comment.