Skip to content

Commit c627969

Browse files
atlowChemiMoLow
authored andcommitted
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: #47648 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent 0e5b82c commit c627969

File tree

4 files changed

+63
-7
lines changed

4 files changed

+63
-7
lines changed

doc/api/test.md

+10
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,10 @@ unless a destination is explicitly provided.
726726
added:
727727
- v18.9.0
728728
- v16.19.0
729+
changes:
730+
- version: REPLACEME
731+
pr-url: https://github.com/nodejs/node/pull/47628
732+
description: Add a testNamePatterns option.
729733
-->
730734

731735
* `options` {Object} Configuration options for running tests. The following
@@ -751,6 +755,12 @@ added:
751755
number. If a nullish value is provided, each process gets its own port,
752756
incremented from the primary's `process.debugPort`.
753757
**Default:** `undefined`.
758+
* `testNamePatterns` {string|RegExp|Array} A String, RegExp or a RegExp Array,
759+
that can be used to only run tests whose name matches the provided pattern.
760+
Test name patterns are interpreted as JavaScript regular expressions.
761+
For each test that is executed, any corresponding test hooks, such as
762+
`beforeEach()`, are also run.
763+
**Default:** `undefined`.
754764
* Returns: {TestsStream}
755765

756766
```mjs

lib/internal/test_runner/runner.js

+32-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
'use strict';
22
const {
33
ArrayFrom,
4+
ArrayIsArray,
45
ArrayPrototypeFilter,
56
ArrayPrototypeForEach,
67
ArrayPrototypeIncludes,
78
ArrayPrototypeIndexOf,
9+
ArrayPrototypeMap,
810
ArrayPrototypePush,
911
ArrayPrototypeSlice,
1012
ArrayPrototypeSome,
@@ -33,11 +35,13 @@ const { FilesWatcher } = require('internal/watch_mode/files_watcher');
3335
const console = require('internal/console/global');
3436
const {
3537
codes: {
38+
ERR_INVALID_ARG_TYPE,
3639
ERR_TEST_FAILURE,
3740
},
3841
} = require('internal/errors');
3942
const { validateArray, validateBoolean, validateFunction } = require('internal/validators');
4043
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
44+
const { isRegExp } = require('internal/util/types');
4145
const { kEmptyObject } = require('internal/util');
4246
const { createTestTree } = require('internal/test_runner/harness');
4347
const {
@@ -53,6 +57,7 @@ const { YAMLToJs } = require('internal/test_runner/yaml_to_js');
5357
const { TokenKind } = require('internal/test_runner/tap_lexer');
5458

5559
const {
60+
convertStringToRegExp,
5661
countCompletedTest,
5762
doesPathMatchFilter,
5863
isSupportedFileType,
@@ -138,11 +143,14 @@ function filterExecArgv(arg, i, arr) {
138143
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
139144
}
140145

141-
function getRunArgs({ path, inspectPort }) {
146+
function getRunArgs({ path, inspectPort, testNamePatterns }) {
142147
const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
143148
if (isUsingInspector()) {
144149
ArrayPrototypePush(argv, `--inspect-port=${getInspectPort(inspectPort)}`);
145150
}
151+
if (testNamePatterns) {
152+
ArrayPrototypeForEach(testNamePatterns, (pattern) => ArrayPrototypePush(argv, `--test-name-pattern=${pattern}`));
153+
}
146154
ArrayPrototypePush(argv, path);
147155

148156
return argv;
@@ -256,9 +264,9 @@ class FileTest extends Test {
256264
const runningProcesses = new SafeMap();
257265
const runningSubtests = new SafeMap();
258266

259-
function runTestFile(path, root, inspectPort, filesWatcher) {
267+
function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
260268
const subtest = root.createSubtest(FileTest, path, async (t) => {
261-
const args = getRunArgs({ path, inspectPort });
269+
const args = getRunArgs({ path, inspectPort, testNamePatterns });
262270
const stdio = ['pipe', 'pipe', 'pipe'];
263271
const env = { ...process.env, NODE_TEST_CONTEXT: 'child' };
264272
if (filesWatcher) {
@@ -340,7 +348,7 @@ function runTestFile(path, root, inspectPort, filesWatcher) {
340348
return promise;
341349
}
342350

343-
function watchFiles(testFiles, root, inspectPort) {
351+
function watchFiles(testFiles, root, inspectPort, testNamePatterns) {
344352
const filesWatcher = new FilesWatcher({ throttle: 500, mode: 'filter' });
345353
filesWatcher.on('changed', ({ owners }) => {
346354
filesWatcher.unfilterFilesOwnedBy(owners);
@@ -354,7 +362,7 @@ function watchFiles(testFiles, root, inspectPort) {
354362
await once(runningProcess, 'exit');
355363
}
356364
await runningSubtests.get(file);
357-
runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher));
365+
runningSubtests.set(file, runTestFile(file, root, inspectPort, filesWatcher, testNamePatterns));
358366
}, undefined, (error) => {
359367
triggerUncaughtException(error, true /* fromPromise */);
360368
}));
@@ -366,6 +374,7 @@ function run(options) {
366374
if (options === null || typeof options !== 'object') {
367375
options = kEmptyObject;
368376
}
377+
let { testNamePatterns } = options;
369378
const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options;
370379

371380
if (files != null) {
@@ -377,20 +386,36 @@ function run(options) {
377386
if (setup != null) {
378387
validateFunction(setup, 'options.setup');
379388
}
389+
if (testNamePatterns != null) {
390+
if (!ArrayIsArray(testNamePatterns)) {
391+
testNamePatterns = [testNamePatterns];
392+
}
393+
validateArray(testNamePatterns, 'options.testNamePatterns');
394+
testNamePatterns = ArrayPrototypeMap(testNamePatterns, (value, i) => {
395+
if (isRegExp(value)) {
396+
return value;
397+
}
398+
const name = `options.testNamePatterns[${i}]`;
399+
if (typeof value === 'string') {
400+
return convertStringToRegExp(value, name);
401+
}
402+
throw new ERR_INVALID_ARG_TYPE(name, ['string', 'RegExp'], value);
403+
});
404+
}
380405

381406
const root = createTestTree({ concurrency, timeout, signal });
382407
const testFiles = files ?? createTestFileList();
383408

384409
let postRun = () => root.postRun();
385410
let filesWatcher;
386411
if (watch) {
387-
filesWatcher = watchFiles(testFiles, root, inspectPort);
412+
filesWatcher = watchFiles(testFiles, root, inspectPort, testNamePatterns);
388413
postRun = undefined;
389414
}
390415
const runFiles = () => {
391416
root.harness.bootstrapComplete = true;
392417
return SafePromiseAllSettledReturnVoid(testFiles, (path) => {
393-
const subtest = runTestFile(path, root, inspectPort, filesWatcher);
418+
const subtest = runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns);
394419
runningSubtests.set(path, subtest);
395420
return subtest;
396421
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
const test = require('node:test');
3+
4+
test('this should be skipped');
5+
test('this should be executed');

test/parallel/test-runner-run.mjs

+16
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,20 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
101101
assert.strictEqual(result[11], '# todo 0\n');
102102
assert.match(result[12], /# duration_ms \d+\.?\d*/);
103103
});
104+
105+
it('should skip tests not matching testNamePatterns - RegExp', async () => {
106+
const result = await run({ files: [join(testFixtures, 'test/skip_by_name.cjs')], testNamePatterns: [/executed/] })
107+
.compose(tap)
108+
.toArray();
109+
assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP test name does not match pattern\n');
110+
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
111+
});
112+
113+
it('should skip tests not matching testNamePatterns - string', async () => {
114+
const result = await run({ files: [join(testFixtures, 'test/skip_by_name.cjs')], testNamePatterns: ['executed'] })
115+
.compose(tap)
116+
.toArray();
117+
assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP test name does not match pattern\n');
118+
assert.strictEqual(result[5], 'ok 2 - this should be executed\n');
119+
});
104120
});

0 commit comments

Comments
 (0)