Skip to content

Commit 52d5dd8

Browse files
joyeecheungtargos
authored andcommitted
test: add expectSyncExitWithoutError() and expectSyncExit() utils
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>
1 parent caa7ecc commit 52d5dd8

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

test/common/README.md

+38
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This directory contains modules used to test the Node.js implementation.
66

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

39+
## Child Process Module
40+
41+
The `child_process` module is used by tests that launch child processes.
42+
43+
### `expectSyncExit(child, options)`
44+
45+
Checks if a _synchronous_ child process runs in the way expected. If it does
46+
not, print the stdout and stderr output from the child process and additional
47+
information about it to the stderr of the current process before throwing
48+
and error. This helps gathering more information about test failures
49+
coming from child processes.
50+
51+
* `child` [\<ChildProcess>][<ChildProcess>]: a `ChildProcess` instance
52+
returned by `child_process.spawnSync()`.
53+
* `options` [\<Object>][<Object>]
54+
* `status` [\<number>][<number>] Expected `child.status`
55+
* `signal` [\<string>][<string>] | `null` Expected `child.signal`
56+
* `stderr` [\<string>][<string>] | [\<RegExp>][<RegExp>] |
57+
[\<Function>][<Function>] Optional. If it's a string, check that the output
58+
to the stderr of the child process is exactly the same as the string. If
59+
it's a regular expression, check that the stderr matches it. If it's a
60+
function, invoke it with the stderr output as a string and check
61+
that it returns true. The function can just throw errors (e.g. assertion
62+
errors) to provide more information if the check fails.
63+
* `stdout` [\<string>][<string>] | [\<RegExp>][<RegExp>] |
64+
[\<Function>][<Function>] Optional. Similar to `stderr` but for the stdout.
65+
* `trim` [\<boolean>][<boolean>] Optional. Whether this method should trim
66+
out the whitespace characters when checking `stderr` and `stdout` outputs.
67+
Defaults to `false`.
68+
69+
### `expectSyncExitWithoutError(child[, options])`
70+
71+
Similar to `expectSyncExit()` with the `status` expected to be 0 and
72+
`signal` expected to be `null`. Any other optional options are passed
73+
into `expectSyncExit()`.
74+
3875
## Common Module API
3976

4077
The `common` module is used by tests for consistency across repeated
@@ -1086,6 +1123,7 @@ See [the WPT tests README][] for details.
10861123
[<ArrayBufferView>]: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
10871124
[<Buffer>]: https://nodejs.org/api/buffer.html#buffer_class_buffer
10881125
[<BufferSource>]: https://developer.mozilla.org/en-US/docs/Web/API/BufferSource
1126+
[<ChildProcess>]: ../../doc/api/child_process.md#class-childprocess
10891127
[<Error>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
10901128
[<Function>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
10911129
[<Object>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object

test/common/child_process.js

+80
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const assert = require('assert');
44
const common = require('./');
5+
const util = require('util');
56

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

45+
function checkOutput(str, check) {
46+
if ((check instanceof RegExp && !check.test(str)) ||
47+
(typeof check === 'string' && check !== str)) {
48+
return { passed: false, reason: `did not match ${util.inspect(check)}` };
49+
}
50+
if (typeof check === 'function') {
51+
try {
52+
check(str);
53+
} catch (error) {
54+
return {
55+
passed: false,
56+
reason: `did not match expectation, checker throws:\n${util.inspect(error)}`,
57+
};
58+
}
59+
}
60+
return { passed: true };
61+
}
62+
63+
function expectSyncExit(child, {
64+
status,
65+
signal,
66+
stderr: stderrCheck,
67+
stdout: stdoutCheck,
68+
trim = false,
69+
}) {
70+
const failures = [];
71+
let stderrStr, stdoutStr;
72+
if (status !== undefined && child.status !== status) {
73+
failures.push(`- process terminated with status ${child.status}, expected ${status}`);
74+
}
75+
if (signal !== undefined && child.signal !== signal) {
76+
failures.push(`- process terminated with signal ${child.signal}, expected ${signal}`);
77+
}
78+
79+
function logAndThrow() {
80+
const tag = `[process ${child.pid}]:`;
81+
console.error(`${tag} --- stderr ---`);
82+
console.error(stderrStr === undefined ? child.stderr.toString() : stderrStr);
83+
console.error(`${tag} --- stdout ---`);
84+
console.error(stdoutStr === undefined ? child.stdout.toString() : stdoutStr);
85+
console.error(`${tag} status = ${child.status}, signal = ${child.signal}`);
86+
throw new Error(`${failures.join('\n')}`);
87+
}
88+
89+
// If status and signal are not matching expectations, fail early.
90+
if (failures.length !== 0) {
91+
logAndThrow();
92+
}
93+
94+
if (stderrCheck !== undefined) {
95+
stderrStr = child.stderr.toString();
96+
const { passed, reason } = checkOutput(trim ? stderrStr.trim() : stderrStr, stderrCheck);
97+
if (!passed) {
98+
failures.push(`- stderr ${reason}`);
99+
}
100+
}
101+
if (stdoutCheck !== undefined) {
102+
stdoutStr = child.stdout.toString();
103+
const { passed, reason } = checkOutput(trim ? stdoutStr.trim() : stdoutStr, stdoutCheck);
104+
if (!passed) {
105+
failures.push(`- stdout ${reason}`);
106+
}
107+
}
108+
if (failures.length !== 0) {
109+
logAndThrow();
110+
}
111+
return { child, stderr: stderrStr, stdout: stdoutStr };
112+
}
113+
114+
function expectSyncExitWithoutError(child, options) {
115+
return expectSyncExit(child, {
116+
status: 0,
117+
signal: null,
118+
...options,
119+
});
120+
}
121+
44122
module.exports = {
45123
cleanupStaleProcess,
46124
logAfterTime,
47125
kExpiringChildRunTime,
48126
kExpiringParentTimer,
127+
expectSyncExit,
128+
expectSyncExitWithoutError,
49129
};

0 commit comments

Comments
 (0)