test_runner: add testNamePatterns to run api

Accept a `testNamePatterns` value in the `run` fn, and drill those
patterns to the spawned processes.

PR-URL: https://github.com/nodejs/node/pull/47648
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
atlowChemi
2023-04-20 00:16:57 +02:00
committed by Moshe Atlow
parent 0e5b82c381
commit c6279690be
4 changed files with 63 additions and 7 deletions

View File

@@ -726,6 +726,10 @@ unless a destination is explicitly provided.
added:
- v18.9.0
- v16.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/47628
description: Add a testNamePatterns option.
-->
* `options` {Object} Configuration options for running tests. The following
@@ -751,6 +755,12 @@ added:
number. If a nullish value is provided, each process gets its own port,
incremented from the primary's `process.debugPort`.
**Default:** `undefined`.
* `testNamePatterns` {string|RegExp|Array} A String, RegExp or a RegExp Array,
that can be used to only run tests whose name matches the provided pattern.
Test name patterns are interpreted as JavaScript regular expressions.
For each test that is executed, any corresponding test hooks, such as
`beforeEach()`, are also run.
**Default:** `undefined`.
* Returns: {TestsStream}
```mjs

View File

@@ -1,10 +1,12 @@
'use strict';
const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypeFilter,
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeIndexOf,
ArrayPrototypeMap,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSome,
@@ -33,11 +35,13 @@ const { FilesWatcher } = require('internal/watch_mode/files_watcher');
const console = require('internal/console/global');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_TEST_FAILURE,
},
} = require('internal/errors');
const { validateArray, validateBoolean, validateFunction } = require('internal/validators');
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
const { isRegExp } = require('internal/util/types');
const { kEmptyObject } = require('internal/util');
const { createTestTree } = require('internal/test_runner/harness');
const {
@@ -53,6 +57,7 @@ const { YAMLToJs } = require('internal/test_runner/yaml_to_js');
const { TokenKind } = require('internal/test_runner/tap_lexer');
const {
convertStringToRegExp,
countCompletedTest,
doesPathMatchFilter,
isSupportedFileType,
@@ -138,11 +143,14 @@ function filterExecArgv(arg, i, arr) {
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
}
function getRunArgs({ path, inspectPort }) {
function getRunArgs({ path, inspectPort, testNamePatterns }) {
const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
if (isUsingInspector()) {
ArrayPrototypePush(argv, `--inspect-port=${getInspectPort(inspectPort)}`);
}
if (testNamePatterns) {
ArrayPrototypeForEach(testNamePatterns, (pattern) => ArrayPrototypePush(argv, `--test-name-pattern=${pattern}`));
}
ArrayPrototypePush(argv, path);
return argv;
@@ -256,9 +264,9 @@ class FileTest extends Test {
const runningProcesses = new SafeMap();
const runningSubtests = new SafeMap();
function runTestFile(path, root, inspectPort, filesWatcher) {
function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
const subtest = root.createSubtest(FileTest, path, async (t) => {
const args = getRunArgs({ path, inspectPort });
const args = getRunArgs({ path, inspectPort, testNamePatterns });
const stdio = ['pipe', 'pipe', 'pipe'];
const env = { ...process.env, NODE_TEST_CONTEXT: 'child' };
if (filesWatcher) {
@@ -340,7 +348,7 @@ function runTestFile(path, root, inspectPort, filesWatcher) {
return promise;
}
function watchFiles(testFiles, root, inspectPort) {
function watchFiles(testFiles, root, inspectPort, testNamePatterns) {
const filesWatcher = new FilesWatcher({ throttle: 500, mode: 'filter' });
filesWatcher.on('changed', ({ owners }) => {
filesWatcher.unfilterFilesOwnedBy(owners);
@@ -354,7 +362,7 @@ function watchFiles(testFiles, root, inspectPort) {
await once(runningProcess, 'exit');
}
await runningSubtests.get(file);
runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher));
runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher, testNamePatterns));
}, undefined, (error) => {
triggerUncaughtException(error, true /* fromPromise */);
}));
@@ -366,6 +374,7 @@ function run(options) {
if (options === null || typeof options !== 'object') {
options = kEmptyObject;
}
let { testNamePatterns } = options;
const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options;
if (files != null) {
@@ -377,6 +386,22 @@ function run(options) {
if (setup != null) {
validateFunction(setup, 'options.setup');
}
if (testNamePatterns != null) {
if (!ArrayIsArray(testNamePatterns)) {
testNamePatterns = [testNamePatterns];
}
validateArray(testNamePatterns, 'options.testNamePatterns');
testNamePatterns = ArrayPrototypeMap(testNamePatterns, (value, i) => {
if (isRegExp(value)) {
return value;
}
const name = `options.testNamePatterns[${i}]`;
if (typeof value === 'string') {
return convertStringToRegExp(value, name);
}
throw new ERR_INVALID_ARG_TYPE(name, ['string', 'RegExp'], value);
});
}
const root = createTestTree({ concurrency, timeout, signal });
const testFiles = files ?? createTestFileList();
@@ -384,13 +409,13 @@ function run(options) {
let postRun = () => root.postRun();
let filesWatcher;
if (watch) {
filesWatcher = watchFiles(testFiles, root, inspectPort);
filesWatcher = watchFiles(testFiles, root, inspectPort, testNamePatterns);
postRun = undefined;
}
const runFiles = () => {
root.harness.bootstrapComplete = true;
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
const subtest = runTestFile(path, root, inspectPort, filesWatcher);
const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns);
runningSubtests.set(path, subtest);
return subtest;
});

View File

@@ -0,0 +1,5 @@
'use strict';
const test = require('node:test');
test('this should be skipped');
test('this should be executed');

View File

@@ -101,4 +101,20 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
assert.strictEqual(result[11], '# todo 0\n');
assert.match(result[12], /# duration_ms \d+\.?\d*/);
});
it('should skip tests not matching testNamePatterns - RegExp', async () => {
const result = await run({ files: [join(testFixtures, 'test/skip_by_name.cjs')], testNamePatterns: [/executed/] })
.compose(tap)
.toArray();
assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP test name does not match pattern\n');
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
});
it('should skip tests not matching testNamePatterns - string', async () => {
const result = await run({ files: [join(testFixtures, 'test/skip_by_name.cjs')], testNamePatterns: ['executed'] })
.compose(tap)
.toArray();
assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP test name does not match pattern\n');
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
});
});