Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7cd4e70

Browse filesBrowse files
MoLownodejs-github-bot
authored andcommitted
test_runner: support passing globs
PR-URL: #47653 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 1948dce commit 7cd4e70
Copy full SHA for 7cd4e70

File tree

Expand file treeCollapse file tree

6 files changed

+53
-171
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+53
-171
lines changed
Open diff view settings
Collapse file

‎doc/api/test.md‎

Copy file name to clipboardExpand all lines: doc/api/test.md
+16-38Lines changed: 16 additions & 38 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -327,52 +327,29 @@ The Node.js test runner can be invoked from the command line by passing the
327327
node --test
328328
```
329329

330-
By default, Node.js will recursively search the current directory for
331-
JavaScript source files matching a specific naming convention. Matching files
332-
are executed as test files. More information on the expected test file naming
333-
convention and behavior can be found in the [test runner execution model][]
334-
section.
330+
By default Node.js will run all files matching these patterns:
335331

336-
Alternatively, one or more paths can be provided as the final argument(s) to
337-
the Node.js command, as shown below.
332+
* `**/*.test.?(c|m)js`
333+
* `**/*-test.?(c|m)js`
334+
* `**/*_test.?(c|m)js`
335+
* `**/test-*.?(c|m)js`
336+
* `**/test.?(c|m)js`
337+
* `**/test/**/*.?(c|m)js`
338+
339+
Alternatively, one or more glob patterns can be provided as the
340+
final argument(s) to the Node.js command, as shown below.
341+
Glob patterns follow the behavior of [`glob(7)`][].
338342

339343
```bash
340-
node --test test1.js test2.mjs custom_test_dir/
344+
node --test **/*.test.js **/*.spec.js
341345
```
342346

343-
In this example, the test runner will execute the files `test1.js` and
344-
`test2.mjs`. The test runner will also recursively search the
345-
`custom_test_dir/` directory for test files to execute.
347+
Matching files are executed as test files.
348+
More information on the test file execution can be found
349+
in the [test runner execution model][] section.
346350

347351
### Test runner execution model
348352

349-
When searching for test files to execute, the test runner behaves as follows:
350-
351-
* Any files explicitly provided by the user are executed.
352-
* If the user did not explicitly specify any paths, the current working
353-
directory is recursively searched for files as specified in the following
354-
steps.
355-
* `node_modules` directories are skipped unless explicitly provided by the
356-
user.
357-
* If a directory named `test` is encountered, the test runner will search it
358-
recursively for all all `.js`, `.cjs`, and `.mjs` files. All of these files
359-
are treated as test files, and do not need to match the specific naming
360-
convention detailed below. This is to accommodate projects that place all of
361-
their tests in a single `test` directory.
362-
* In all other directories, `.js`, `.cjs`, and `.mjs` files matching the
363-
following patterns are treated as test files:
364-
* `^test$` - Files whose basename is the string `'test'`. Examples:
365-
`test.js`, `test.cjs`, `test.mjs`.
366-
* `^test-.+` - Files whose basename starts with the string `'test-'`
367-
followed by one or more characters. Examples: `test-example.js`,
368-
`test-another-example.mjs`.
369-
* `.+[\.\-\_]test$` - Files whose basename ends with `.test`, `-test`, or
370-
`_test`, preceded by one or more characters. Examples: `example.test.js`,
371-
`example-test.cjs`, `example_test.mjs`.
372-
* Other file types understood by Node.js such as `.node` and `.json` are not
373-
automatically executed by the test runner, but are supported if explicitly
374-
provided on the command line.
375-
376353
Each matching test file is executed in a separate child process. If the child
377354
process finishes with an exit code of 0, the test is considered passing.
378355
Otherwise, the test is considered to be a failure. Test files must be
@@ -2459,6 +2436,7 @@ added:
24592436
[`context.skip`]: #contextskipmessage
24602437
[`context.todo`]: #contexttodomessage
24612438
[`describe()`]: #describename-options-fn
2439+
[`glob(7)`]: https://man7.org/linux/man-pages/man7/glob.7.html
24622440
[`run()`]: #runoptions
24632441
[`test()`]: #testname-options-fn
24642442
[describe options]: #describename-options-fn
Collapse file

‎lib/internal/test_runner/runner.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/runner.js
+16-60Lines changed: 16 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
'use strict';
22
const {
3-
ArrayFrom,
43
ArrayIsArray,
4+
ArrayPrototypeEvery,
55
ArrayPrototypeFilter,
66
ArrayPrototypeForEach,
77
ArrayPrototypeIncludes,
88
ArrayPrototypeMap,
9+
ArrayPrototypeJoin,
910
ArrayPrototypePush,
1011
ArrayPrototypeShift,
1112
ArrayPrototypeSlice,
@@ -27,7 +28,6 @@ const {
2728
} = primordials;
2829

2930
const { spawn } = require('child_process');
30-
const { readdirSync, statSync } = require('fs');
3131
const { finished } = require('internal/streams/end-of-stream');
3232
const { DefaultDeserializer, DefaultSerializer } = require('v8');
3333
// TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern.
@@ -60,10 +60,9 @@ const {
6060
const {
6161
convertStringToRegExp,
6262
countCompletedTest,
63-
doesPathMatchFilter,
64-
isSupportedFileType,
63+
kDefaultPattern,
6564
} = require('internal/test_runner/utils');
66-
const { basename, join, resolve } = require('path');
65+
const { Glob } = require('internal/fs/glob');
6766
const { once } = require('events');
6867
const {
6968
triggerUncaughtException,
@@ -79,66 +78,23 @@ const kCanceledTests = new SafeSet()
7978

8079
let kResistStopPropagation;
8180

82-
// TODO(cjihrig): Replace this with recursive readdir once it lands.
83-
function processPath(path, testFiles, options) {
84-
const stats = statSync(path);
85-
86-
if (stats.isFile()) {
87-
if (options.userSupplied ||
88-
(options.underTestDir && isSupportedFileType(path)) ||
89-
doesPathMatchFilter(path)) {
90-
testFiles.add(path);
91-
}
92-
} else if (stats.isDirectory()) {
93-
const name = basename(path);
94-
95-
if (!options.userSupplied && name === 'node_modules') {
96-
return;
97-
}
98-
99-
// 'test' directories get special treatment. Recursively add all .js,
100-
// .cjs, and .mjs files in the 'test' directory.
101-
const isTestDir = name === 'test';
102-
const { underTestDir } = options;
103-
const entries = readdirSync(path);
104-
105-
if (isTestDir) {
106-
options.underTestDir = true;
107-
}
108-
109-
options.userSupplied = false;
110-
111-
for (let i = 0; i < entries.length; i++) {
112-
processPath(join(path, entries[i]), testFiles, options);
113-
}
114-
115-
options.underTestDir = underTestDir;
116-
}
117-
}
118-
11981
function createTestFileList() {
12082
const cwd = process.cwd();
121-
const hasUserSuppliedPaths = process.argv.length > 1;
122-
const testPaths = hasUserSuppliedPaths ?
123-
ArrayPrototypeSlice(process.argv, 1) : [cwd];
124-
const testFiles = new SafeSet();
125-
126-
try {
127-
for (let i = 0; i < testPaths.length; i++) {
128-
const absolutePath = resolve(testPaths[i]);
129-
130-
processPath(absolutePath, testFiles, { userSupplied: true });
131-
}
132-
} catch (err) {
133-
if (err?.code === 'ENOENT') {
134-
console.error(`Could not find '${err.path}'`);
135-
process.exit(kGenericUserError);
136-
}
83+
const hasUserSuppliedPattern = process.argv.length > 1;
84+
const patterns = hasUserSuppliedPattern ? ArrayPrototypeSlice(process.argv, 1) : [kDefaultPattern];
85+
const glob = new Glob(patterns, {
86+
__proto__: null,
87+
cwd,
88+
exclude: (name) => name === 'node_modules',
89+
});
90+
const results = glob.globSync();
13791

138-
throw err;
92+
if (hasUserSuppliedPattern && results.length === 0 && ArrayPrototypeEvery(glob.matchers, (m) => !m.hasMagic())) {
93+
console.error(`Could not find '${ArrayPrototypeJoin(patterns, ', ')}'`);
94+
process.exit(kGenericUserError);
13995
}
14096

141-
return ArrayPrototypeSort(ArrayFrom(testFiles));
97+
return ArrayPrototypeSort(results);
14298
}
14399

144100
function filterExecArgv(arg, i, arr) {
Collapse file

‎lib/internal/test_runner/utils.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/utils.js
+4-11Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const {
1919
StringPrototypeSlice,
2020
} = primordials;
2121

22-
const { basename, relative } = require('path');
22+
const { relative } = require('path');
2323
const { createWriteStream } = require('fs');
2424
const { pathToFileURL } = require('internal/url');
2525
const { createDeferredPromise } = require('internal/util');
@@ -44,16 +44,10 @@ const coverageColors = {
4444

4545
const kMultipleCallbackInvocations = 'multipleCallbackInvocations';
4646
const kRegExpPattern = /^\/(.*)\/([a-z]*)$/;
47-
const kSupportedFileExtensions = /\.[cm]?js$/;
48-
const kTestFilePattern = /((^test(-.+)?)|(.+[.\-_]test))\.[cm]?js$/;
4947

50-
function doesPathMatchFilter(p) {
51-
return RegExpPrototypeExec(kTestFilePattern, basename(p)) !== null;
52-
}
48+
const kPatterns = ['test', 'test/**/*', 'test-*', '*[.-_]test'];
49+
const kDefaultPattern = `**/{${ArrayPrototypeJoin(kPatterns, ',')}}.?(c|m)js`;
5350

54-
function isSupportedFileType(p) {
55-
return RegExpPrototypeExec(kSupportedFileExtensions, p) !== null;
56-
}
5751

5852
function createDeferredCallback() {
5953
let calledCount = 0;
@@ -414,9 +408,8 @@ module.exports = {
414408
convertStringToRegExp,
415409
countCompletedTest,
416410
createDeferredCallback,
417-
doesPathMatchFilter,
418-
isSupportedFileType,
419411
isTestFailureError,
412+
kDefaultPattern,
420413
parseCommandLine,
421414
setupTestReporters,
422415
getCoverageReport,
Collapse file

‎test/parallel/test-runner-cli.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-runner-cli.js
+10-14Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,28 @@ const testFixtures = fixtures.path('test-runner');
2121
{
2222
// Default behavior. node_modules is ignored. Files that don't match the
2323
// pattern are ignored except in test/ directories.
24-
const args = ['--test', testFixtures];
25-
const child = spawnSync(process.execPath, args);
24+
const args = ['--test'];
25+
const child = spawnSync(process.execPath, args, { cwd: testFixtures });
2626

2727
assert.strictEqual(child.status, 1);
2828
assert.strictEqual(child.signal, null);
2929
assert.strictEqual(child.stderr.toString(), '');
3030
const stdout = child.stdout.toString();
3131
assert.match(stdout, /ok 1 - this should pass/);
3232
assert.match(stdout, /not ok 2 - this should fail/);
33-
assert.match(stdout, /ok 3 - .+subdir.+subdir_test\.js/);
33+
assert.match(stdout, /ok 3 - subdir.+subdir_test\.js/);
3434
assert.match(stdout, /ok 4 - this should pass/);
3535
}
3636

3737
{
3838
// Same but with a prototype mutation in require scripts.
39-
const args = ['--require', join(testFixtures, 'protoMutation.js'), '--test', testFixtures];
40-
const child = spawnSync(process.execPath, args);
39+
const args = ['--require', join(testFixtures, 'protoMutation.js'), '--test'];
40+
const child = spawnSync(process.execPath, args, { cwd: testFixtures });
4141

4242
const stdout = child.stdout.toString();
4343
assert.match(stdout, /ok 1 - this should pass/);
4444
assert.match(stdout, /not ok 2 - this should fail/);
45-
assert.match(stdout, /ok 3 - .+subdir.+subdir_test\.js/);
45+
assert.match(stdout, /ok 3 - subdir.+subdir_test\.js/);
4646
assert.match(stdout, /ok 4 - this should pass/);
4747
assert.strictEqual(child.status, 1);
4848
assert.strictEqual(child.signal, null);
@@ -51,23 +51,19 @@ const testFixtures = fixtures.path('test-runner');
5151

5252
{
5353
// User specified files that don't match the pattern are still run.
54-
const args = ['--test', testFixtures, join(testFixtures, 'index.js')];
55-
const child = spawnSync(process.execPath, args);
54+
const args = ['--test', join(testFixtures, 'index.js')];
55+
const child = spawnSync(process.execPath, args, { cwd: testFixtures });
5656

5757
assert.strictEqual(child.status, 1);
5858
assert.strictEqual(child.signal, null);
5959
assert.strictEqual(child.stderr.toString(), '');
6060
const stdout = child.stdout.toString();
6161
assert.match(stdout, /not ok 1 - .+index\.js/);
62-
assert.match(stdout, /ok 2 - this should pass/);
63-
assert.match(stdout, /not ok 3 - this should fail/);
64-
assert.match(stdout, /ok 4 - .+subdir.+subdir_test\.js/);
65-
assert.match(stdout, /ok 5 - this should pass/);
6662
}
6763

6864
{
6965
// Searches node_modules if specified.
70-
const args = ['--test', join(testFixtures, 'node_modules')];
66+
const args = ['--test', join(testFixtures, 'node_modules/*.js')];
7167
const child = spawnSync(process.execPath, args);
7268

7369
assert.strictEqual(child.status, 1);
@@ -89,7 +85,7 @@ const testFixtures = fixtures.path('test-runner');
8985
const stdout = child.stdout.toString();
9086
assert.match(stdout, /ok 1 - this should pass/);
9187
assert.match(stdout, /not ok 2 - this should fail/);
92-
assert.match(stdout, /ok 3 - .+subdir.+subdir_test\.js/);
88+
assert.match(stdout, /ok 3 - subdir.+subdir_test\.js/);
9389
assert.match(stdout, /ok 4 - this should pass/);
9490
}
9591

Collapse file

‎test/parallel/test-runner-coverage.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-runner-coverage.js
+7-6Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ test('coverage is combined for multiple processes', skipIfNoInspector, () => {
153153
let report = [
154154
'# start of coverage report',
155155
'# file | line % | branch % | funcs % | uncovered lines',
156-
'# test/fixtures/v8-coverage/combined_coverage/common.js | 89.86 | ' +
156+
'# common.js | 89.86 | ' +
157157
'62.50 | 100.00 | 8, 13, 14, 18, 34, 35, 53',
158-
'# test/fixtures/v8-coverage/combined_coverage/first.test.js | 83.33 | ' +
158+
'# first.test.js | 83.33 | ' +
159159
'100.00 | 50.00 | 5, 6',
160-
'# test/fixtures/v8-coverage/combined_coverage/second.test.js | 100.00 ' +
160+
'# second.test.js | 100.00 ' +
161161
'| 100.00 | 100.00 | ',
162-
'# test/fixtures/v8-coverage/combined_coverage/third.test.js | 100.00 | ' +
162+
'# third.test.js | 100.00 | ' +
163163
'100.00 | 100.00 | ',
164164
'# all files | 92.11 | 72.73 | 88.89 |',
165165
'# end of coverage report',
@@ -171,10 +171,11 @@ test('coverage is combined for multiple processes', skipIfNoInspector, () => {
171171

172172
const fixture = fixtures.path('v8-coverage', 'combined_coverage');
173173
const args = [
174-
'--test', '--experimental-test-coverage', '--test-reporter', 'tap', fixture,
174+
'--test', '--experimental-test-coverage', '--test-reporter', 'tap',
175175
];
176176
const result = spawnSync(process.execPath, args, {
177-
env: { ...process.env, NODE_TEST_TMPDIR: tmpdir.path }
177+
env: { ...process.env, NODE_TEST_TMPDIR: tmpdir.path },
178+
cwd: fixture,
178179
});
179180

180181
assert.strictEqual(result.stderr.toString(), '');
Collapse file

‎test/parallel/test-runner-test-filter.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-runner-test-filter.js
-42Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.