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

test: add and use expectSyncExitWithoutError() and expectSyncExit() utils #49020

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions test/common/README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
};
18 changes: 8 additions & 10 deletions test/parallel/test-snapshot-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const assert = require('assert');
const { spawnSync } = require('child_process');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const { expectSyncExitWithoutError } = require('../common/child_process');
const fs = require('fs');

const v8 = require('v8');
Expand Down Expand Up @@ -36,11 +37,8 @@ const entry = fixtures.path('snapshot', 'v8-startup-snapshot-api.js');
], {
cwd: tmpdir.path
});
if (child.status !== 0) {
console.log(child.stderr.toString());
console.log(child.stdout.toString());
assert.strictEqual(child.status, 0);
}

expectSyncExitWithoutError(child);
const stats = fs.statSync(tmpdir.resolve('snapshot.blob'));
assert(stats.isFile());
}
Expand All @@ -58,9 +56,9 @@ const entry = fixtures.path('snapshot', 'v8-startup-snapshot-api.js');
}
});

const stdout = child.stdout.toString().trim();
const stderr = child.stderr.toString().trim();
assert.strictEqual(stderr, 'Reading book1.en_US.txt');
assert.strictEqual(stdout, 'This is book1.en_US.txt');
assert.strictEqual(child.status, 0);
expectSyncExitWithoutError(child, {
stderr: 'Reading book1.en_US.txt',
stdout: 'This is book1.en_US.txt',
trim: true
});
}
42 changes: 15 additions & 27 deletions test/parallel/test-snapshot-basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ const assert = require('assert');
const { spawnSync } = require('child_process');
const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const { expectSyncExitWithoutError, expectSyncExit } = require('../common/child_process');
const fs = require('fs');

tmpdir.refresh();

let snapshotScript = 'node:embedded_snapshot_main';
if (!process.config.variables.node_use_node_snapshot) {
// Check that Node.js built without an embedded snapshot
// exits with 1 when node:embedded_snapshot_main is specified
// exits with 9 when node:embedded_snapshot_main is specified
// as snapshot entry point.
const child = spawnSync(process.execPath, [
'--build-snapshot',
Expand All @@ -24,10 +25,11 @@ if (!process.config.variables.node_use_node_snapshot) {
cwd: tmpdir.path
});

assert.match(
child.stderr.toString(),
/Node\.js was built without embedded snapshot/);
assert.strictEqual(child.status, 9);
expectSyncExit(child, {
status: 9,
signal: null,
stderr: /Node\.js was built without embedded snapshot/
});

snapshotScript = fixtures.path('empty.js');
}
Expand All @@ -41,12 +43,7 @@ if (!process.config.variables.node_use_node_snapshot) {
], {
cwd: tmpdir.path
});
if (child.status !== 0) {
console.log(child.stderr.toString());
console.log(child.stdout.toString());
console.log(child.signal);
assert.strictEqual(child.status, 0);
}
expectSyncExitWithoutError(child);
const stats = fs.statSync(tmpdir.resolve('snapshot.blob'));
assert(stats.isFile());
}
Expand All @@ -63,12 +60,7 @@ const blobPath = tmpdir.resolve('my-snapshot.blob');
], {
cwd: tmpdir.path
});
if (child.status !== 0) {
console.log(child.stderr.toString());
console.log(child.stdout.toString());
console.log(child.signal);
assert.strictEqual(child.status, 0);
}
expectSyncExitWithoutError(child);
const stats = fs.statSync(blobPath);
assert(stats.isFile());
}
Expand All @@ -82,13 +74,7 @@ const blobPath = tmpdir.resolve('my-snapshot.blob');
], {
cwd: tmpdir.path
});

if (child.status !== 0) {
console.log(child.stderr.toString());
console.log(child.stdout.toString());
console.log(child.signal);
assert.strictEqual(child.status, 0);
}
expectSyncExitWithoutError(child);

assert(child.stdout.toString().includes('--help'));
}
Expand All @@ -105,7 +91,9 @@ const blobPath = tmpdir.resolve('my-snapshot.blob');
});

// Check that it is a noop.
assert.strictEqual(child.stdout.toString().trim(), '');
assert.strictEqual(child.stderr.toString().trim(), '');
assert.strictEqual(child.status, 0);
expectSyncExitWithoutError(child, {
stderr: '',
stdout: '',
trim: true
});
}
Loading