test_runner, cli: add --test-concurrency flag

This commit adds a new --test-concurrency CLI flag that controls
the parallelism of the test runner CLI.

PR-URL: https://github.com/nodejs/node/pull/49996
Fixes: https://github.com/nodejs/node/issues/49487
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
This commit is contained in:
cjihrig 2023-10-01 11:46:14 -04:00
parent 69fb55e6b9
commit 9f9c58212e
No known key found for this signature in database
GPG Key ID: 7434390BDBE9B9C5
7 changed files with 70 additions and 6 deletions

View File

@ -1654,6 +1654,15 @@ Starts the Node.js command line test runner. This flag cannot be combined with
See the documentation on [running tests from the command line][] See the documentation on [running tests from the command line][]
for more details. for more details.
### `--test-concurrency`
<!-- YAML
added: REPLACEME
-->
The maximum number of test files that the test runner CLI will execute
concurrently. The default value is `os.availableParallelism() - 1`.
### `--test-name-pattern` ### `--test-name-pattern`
<!-- YAML <!-- YAML

View File

@ -350,11 +350,12 @@ in the [test runner execution model][] section.
### Test runner execution model ### Test runner execution model
Each matching test file is executed in a separate child process. If the child Each matching test file is executed in a separate child process. The maximum
process finishes with an exit code of 0, the test is considered passing. number of child processes running at any time is controlled by the
Otherwise, the test is considered to be a failure. Test files must be [`--test-concurrency`][] flag. If the child process finishes with an exit code
executable by Node.js, but are not required to use the `node:test` module of 0, the test is considered passing. Otherwise, the test is considered to be a
internally. failure. Test files must be executable by Node.js, but are not required to use
the `node:test` module internally.
Each test file is executed as if it was a regular script. That is, if the test Each test file is executed as if it was a regular script. That is, if the test
file itself uses `node:test` to define tests, all of those tests will be file itself uses `node:test` to define tests, all of those tests will be
@ -2551,6 +2552,7 @@ added:
[TTY]: tty.md [TTY]: tty.md
[`--experimental-test-coverage`]: cli.md#--experimental-test-coverage [`--experimental-test-coverage`]: cli.md#--experimental-test-coverage
[`--import`]: cli.md#--importmodule [`--import`]: cli.md#--importmodule
[`--test-concurrency`]: cli.md#--test-concurrency
[`--test-name-pattern`]: cli.md#--test-name-pattern [`--test-name-pattern`]: cli.md#--test-name-pattern
[`--test-only`]: cli.md#--test-only [`--test-only`]: cli.md#--test-only
[`--test-reporter-destination`]: cli.md#--test-reporter-destination [`--test-reporter-destination`]: cli.md#--test-reporter-destination

View File

@ -418,6 +418,10 @@ Specify the minimum allocation from the OpenSSL secure heap. The default is 2. T
.It Fl -test .It Fl -test
Starts the Node.js command line test runner. Starts the Node.js command line test runner.
. .
.It Fl -test-concurrency
The maximum number of test files that the test runner CLI will execute
concurrently.
.
.It Fl -test-name-pattern .It Fl -test-name-pattern
A regular expression that configures the test runner to only execute tests A regular expression that configures the test runner to only execute tests
whose name matches the provided pattern. whose name matches the provided pattern.

View File

@ -22,7 +22,7 @@ const {
prepareMainThreadExecution(false); prepareMainThreadExecution(false);
markBootstrapComplete(); markBootstrapComplete();
let concurrency = true; let concurrency = getOptionValue('--test-concurrency') || true;
let inspectPort; let inspectPort;
if (isUsingInspector()) { if (isUsingInspector()) {

View File

@ -595,6 +595,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--test", AddOption("--test",
"launch test runner on startup", "launch test runner on startup",
&EnvironmentOptions::test_runner); &EnvironmentOptions::test_runner);
AddOption("--test-concurrency",
"specify test runner concurrency",
&EnvironmentOptions::test_runner_concurrency);
AddOption("--experimental-test-coverage", AddOption("--experimental-test-coverage",
"enable code coverage in the test runner", "enable code coverage in the test runner",
&EnvironmentOptions::test_runner_coverage); &EnvironmentOptions::test_runner_coverage);

View File

@ -159,6 +159,7 @@ class EnvironmentOptions : public Options {
std::string env_file; std::string env_file;
bool has_env_file_string = false; bool has_env_file_string = false;
bool test_runner = false; bool test_runner = false;
uint64_t test_runner_concurrency = 0;
bool test_runner_coverage = false; bool test_runner_coverage = false;
std::vector<std::string> test_name_pattern; std::vector<std::string> test_name_pattern;
std::vector<std::string> test_reporter; std::vector<std::string> test_reporter;

View File

@ -0,0 +1,45 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const { deepStrictEqual, strictEqual } = require('node:assert');
const { spawnSync } = require('node:child_process');
const { readdirSync, writeFileSync } = require('node:fs');
const { join } = require('node:path');
const { beforeEach, test } = require('node:test');
function createTestFile(name) {
writeFileSync(join(tmpdir.path, name), `
const fs = require('node:fs');
fs.unlinkSync(__filename);
setTimeout(() => {}, 1_000_000_000);
`);
}
beforeEach(() => {
tmpdir.refresh();
createTestFile('test-1.js');
createTestFile('test-2.js');
});
test('concurrency of one', () => {
const cp = spawnSync(process.execPath, ['--test', '--test-concurrency=1'], {
cwd: tmpdir.path,
timeout: common.platformTimeout(1000),
});
strictEqual(cp.stderr.toString(), '');
strictEqual(cp.error.code, 'ETIMEDOUT');
deepStrictEqual(readdirSync(tmpdir.path), ['test-2.js']);
});
test('concurrency of two', () => {
const cp = spawnSync(process.execPath, ['--test', '--test-concurrency=2'], {
cwd: tmpdir.path,
timeout: common.platformTimeout(1000),
});
strictEqual(cp.stderr.toString(), '');
strictEqual(cp.error.code, 'ETIMEDOUT');
deepStrictEqual(readdirSync(tmpdir.path), []);
});