Skip to content

Commit

Permalink
test: add expectSyncExitWithoutError() and expectSyncExit() utils
Browse files Browse the repository at this point in the history
These can be used to check the state and the output of a child
process launched with `spawnSync()`. They log additional information
about the child process when the check fails to facilitate debugging
test failures.

PR-URL: #49020
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
  • Loading branch information
joyeecheung authored and UlisesGascon committed Sep 10, 2023
1 parent 2a35383 commit c441f5a
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 0 deletions.
38 changes: 38 additions & 0 deletions test/common/README.md
Expand Up @@ -6,6 +6,7 @@ This directory contains modules used to test the Node.js implementation.

* [ArrayStream module](#arraystream-module)
* [Benchmark module](#benchmark-module)
* [Child process module](#child-process-module)
* [Common module API](#common-module-api)
* [Countdown module](#countdown-module)
* [CPU Profiler module](#cpu-profiler-module)
Expand Down Expand Up @@ -35,6 +36,42 @@ The `benchmark` module is used by tests to run benchmarks.
* `env` [\<Object>][<Object>] Environment variables to be applied during the
run.

## Child Process Module

The `child_process` module is used by tests that launch child processes.

### `expectSyncExit(child, options)`

Checks if a _synchronous_ child process runs in the way expected. If it does
not, print the stdout and stderr output from the child process and additional
information about it to the stderr of the current process before throwing
and error. This helps gathering more information about test failures
coming from child processes.

* `child` [\<ChildProcess>][<ChildProcess>]: a `ChildProcess` instance
returned by `child_process.spawnSync()`.
* `options` [\<Object>][<Object>]
* `status` [\<number>][<number>] Expected `child.status`
* `signal` [\<string>][<string>] | `null` Expected `child.signal`
* `stderr` [\<string>][<string>] | [\<RegExp>][<RegExp>] |
[\<Function>][<Function>] Optional. If it's a string, check that the output
to the stderr of the child process is exactly the same as the string. If
it's a regular expression, check that the stderr matches it. If it's a
function, invoke it with the stderr output as a string and check
that it returns true. The function can just throw errors (e.g. assertion
errors) to provide more information if the check fails.
* `stdout` [\<string>][<string>] | [\<RegExp>][<RegExp>] |
[\<Function>][<Function>] Optional. Similar to `stderr` but for the stdout.
* `trim` [\<boolean>][<boolean>] Optional. Whether this method should trim
out the whitespace characters when checking `stderr` and `stdout` outputs.
Defaults to `false`.

### `expectSyncExitWithoutError(child[, options])`

Similar to `expectSyncExit()` with the `status` expected to be 0 and
`signal` expected to be `null`. Any other optional options are passed
into `expectSyncExit()`.

## Common Module API

The `common` module is used by tests for consistency across repeated
Expand Down Expand Up @@ -1111,6 +1148,7 @@ See [the WPT tests README][] for details.
[<ArrayBufferView>]: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
[<Buffer>]: https://nodejs.org/api/buffer.html#buffer_class_buffer
[<BufferSource>]: https://developer.mozilla.org/en-US/docs/Web/API/BufferSource
[<ChildProcess>]: ../../doc/api/child_process.md#class-childprocess
[<Error>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
[<Function>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
[<Object>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
Expand Down
80 changes: 80 additions & 0 deletions test/common/child_process.js
Expand Up @@ -2,6 +2,7 @@

const assert = require('assert');
const common = require('./');
const util = require('util');

// Workaround for Windows Server 2008R2
// When CMD is used to launch a process and CMD is killed too quickly, the
Expand Down Expand Up @@ -41,9 +42,88 @@ function logAfterTime(time) {
}, time);
}

function checkOutput(str, check) {
if ((check instanceof RegExp && !check.test(str)) ||
(typeof check === 'string' && check !== str)) {
return { passed: false, reason: `did not match ${util.inspect(check)}` };
}
if (typeof check === 'function') {
try {
check(str);
} catch (error) {
return {
passed: false,
reason: `did not match expectation, checker throws:\n${util.inspect(error)}`,
};
}
}
return { passed: true };
}

function expectSyncExit(child, {
status,
signal,
stderr: stderrCheck,
stdout: stdoutCheck,
trim = false,
}) {
const failures = [];
let stderrStr, stdoutStr;
if (status !== undefined && child.status !== status) {
failures.push(`- process terminated with status ${child.status}, expected ${status}`);
}
if (signal !== undefined && child.signal !== signal) {
failures.push(`- process terminated with signal ${child.signal}, expected ${signal}`);
}

function logAndThrow() {
const tag = `[process ${child.pid}]:`;
console.error(`${tag} --- stderr ---`);
console.error(stderrStr === undefined ? child.stderr.toString() : stderrStr);
console.error(`${tag} --- stdout ---`);
console.error(stdoutStr === undefined ? child.stdout.toString() : stdoutStr);
console.error(`${tag} status = ${child.status}, signal = ${child.signal}`);
throw new Error(`${failures.join('\n')}`);
}

// If status and signal are not matching expectations, fail early.
if (failures.length !== 0) {
logAndThrow();
}

if (stderrCheck !== undefined) {
stderrStr = child.stderr.toString();
const { passed, reason } = checkOutput(trim ? stderrStr.trim() : stderrStr, stderrCheck);
if (!passed) {
failures.push(`- stderr ${reason}`);
}
}
if (stdoutCheck !== undefined) {
stdoutStr = child.stdout.toString();
const { passed, reason } = checkOutput(trim ? stdoutStr.trim() : stdoutStr, stdoutCheck);
if (!passed) {
failures.push(`- stdout ${reason}`);
}
}
if (failures.length !== 0) {
logAndThrow();
}
return { child, stderr: stderrStr, stdout: stdoutStr };
}

function expectSyncExitWithoutError(child, options) {
return expectSyncExit(child, {
status: 0,
signal: null,
...options,
});
}

module.exports = {
cleanupStaleProcess,
logAfterTime,
kExpiringChildRunTime,
kExpiringParentTimer,
expectSyncExit,
expectSyncExitWithoutError,
};

0 comments on commit c441f5a

Please sign in to comment.