Skip to content

Commit 4f5885e

Browse files
committed
test_runner: exclude test files from coverage by default
1 parent b17a1fb commit 4f5885e

File tree

9 files changed

+157
-35
lines changed

9 files changed

+157
-35
lines changed

doc/api/cli.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,6 +2291,9 @@ This option may be specified multiple times to exclude multiple glob patterns.
22912291
If both `--test-coverage-exclude` and `--test-coverage-include` are provided,
22922292
files must meet **both** criteria to be included in the coverage report.
22932293

2294+
By default all the matching test files are excluded from the coverage report.
2295+
Specifying this option will override the default behavior.
2296+
22942297
### `--test-coverage-functions=threshold`
22952298

22962299
<!-- YAML

doc/api/test.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,10 @@ all tests have completed. If the [`NODE_V8_COVERAGE`][] environment variable is
476476
used to specify a code coverage directory, the generated V8 coverage files are
477477
written to that directory. Node.js core modules and files within
478478
`node_modules/` directories are, by default, not included in the coverage report.
479-
However, they can be explicitly included via the [`--test-coverage-include`][] flag. If
480-
coverage is enabled, the coverage report is sent to any [test reporters][] via
479+
However, they can be explicitly included via the [`--test-coverage-include`][] flag.
480+
By default all the matching test files are excluded from the coverage report.
481+
Exclusions can be overridden by using the [`--test-coverage-exclude`][] flag.
482+
If coverage is enabled, the coverage report is sent to any [test reporters][] via
481483
the `'test:coverage'` event.
482484

483485
Coverage can be disabled on a series of lines using the following
@@ -3594,6 +3596,7 @@ Can be used to abort test subtasks when the test has been aborted.
35943596
[`--experimental-test-module-mocks`]: cli.md#--experimental-test-module-mocks
35953597
[`--import`]: cli.md#--importmodule
35963598
[`--test-concurrency`]: cli.md#--test-concurrency
3599+
[`--test-coverage-exclude`]: cli.md#--test-coverage-exclude
35973600
[`--test-coverage-include`]: cli.md#--test-coverage-include
35983601
[`--test-name-pattern`]: cli.md#--test-name-pattern
35993602
[`--test-only`]: cli.md#--test-only

lib/internal/test_runner/coverage.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const {
2525
readFileSync,
2626
rmSync,
2727
} = require('fs');
28-
const { setupCoverageHooks } = require('internal/util');
28+
const { setupCoverageHooks, isWindows, isMacOS } = require('internal/util');
2929
const { tmpdir } = require('os');
30-
const { join, resolve, relative, matchesGlob } = require('path');
30+
const { join, resolve, relative } = require('path');
3131
const { fileURLToPath } = require('internal/url');
3232
const { kMappings, SourceMap } = require('internal/source_map/source_map');
3333
const {
@@ -42,6 +42,25 @@ const kLineEndingRegex = /\r?\n$/u;
4242
const kLineSplitRegex = /(?<=\r?\n)/u;
4343
const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;
4444

45+
let minimatch;
46+
function lazyMinimatch() {
47+
minimatch ??= require('internal/deps/minimatch/index');
48+
return minimatch;
49+
}
50+
51+
function glob(path, pattern, windows) {
52+
return lazyMinimatch().minimatch(path, pattern, {
53+
__proto__: null,
54+
nocase: isMacOS || isWindows,
55+
windowsPathsNoEscape: true,
56+
nonegate: true,
57+
nocomment: true,
58+
optimizationLevel: 2,
59+
platform: windows ? 'win32' : 'posix',
60+
nocaseMagicOnly: true,
61+
});
62+
}
63+
4564
class CoverageLine {
4665
constructor(line, startOffset, src, length = src?.length) {
4766
const newlineLength = src == null ? 0 :
@@ -464,19 +483,24 @@ class TestCoverage {
464483
coverageExcludeGlobs: excludeGlobs,
465484
coverageIncludeGlobs: includeGlobs,
466485
} = this.options;
486+
467487
// This check filters out files that match the exclude globs.
468488
if (excludeGlobs?.length > 0) {
469489
for (let i = 0; i < excludeGlobs.length; ++i) {
470-
if (matchesGlob(relativePath, excludeGlobs[i]) ||
471-
matchesGlob(absolutePath, excludeGlobs[i])) return true;
490+
if (
491+
glob(relativePath, excludeGlobs[i]) ||
492+
glob(absolutePath, excludeGlobs[i])
493+
) return true;
472494
}
473495
}
474496

475497
// This check filters out files that do not match the include globs.
476498
if (includeGlobs?.length > 0) {
477499
for (let i = 0; i < includeGlobs.length; ++i) {
478-
if (matchesGlob(relativePath, includeGlobs[i]) ||
479-
matchesGlob(absolutePath, includeGlobs[i])) return false;
500+
if (
501+
glob(relativePath, includeGlobs[i]) ||
502+
glob(absolutePath, includeGlobs[i])
503+
) return false;
480504
}
481505
return true;
482506
}

lib/internal/test_runner/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,11 @@ function parseCommandLine() {
287287

288288
if (coverage) {
289289
coverageExcludeGlobs = getOptionValue('--test-coverage-exclude');
290+
if (!coverageExcludeGlobs || coverageExcludeGlobs.length === 0) {
291+
// TODO(pmarchini): this default should follow something similar to c8 defaults
292+
// Default exclusions should be also exported to be used by other tools / users
293+
coverageExcludeGlobs = [kDefaultPattern];
294+
}
290295
coverageIncludeGlobs = getOptionValue('--test-coverage-include');
291296

292297
branchCoverage = getOptionValue('--test-coverage-branches');

test/fixtures/test-runner/output/lcov_reporter.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,13 @@ const fixtures = require('../../../common/fixtures');
44
const spawn = require('node:child_process').spawn;
55

66
spawn(process.execPath,
7-
['--no-warnings', '--experimental-test-coverage', '--test-reporter', 'lcov', fixtures.path('test-runner/output/output.js')], { stdio: 'inherit' });
7+
[
8+
'--no-warnings',
9+
'--experimental-test-coverage',
10+
'--test-coverage-exclude=!test/**',
11+
'--test-reporter',
12+
'lcov',
13+
fixtures.path('test-runner/output/output.js')
14+
],
15+
{ stdio: 'inherit' }
16+
);

test/parallel/test-runner-coverage-source-map.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ function generateReport(report) {
2020

2121
const flags = [
2222
'--enable-source-maps',
23-
'--test', '--experimental-test-coverage', '--test-reporter', 'tap',
23+
'--test',
24+
'--experimental-test-coverage',
25+
'--test-coverage-exclude=!test/**',
26+
'--test-reporter',
27+
'tap',
2428
];
2529

2630
describe('Coverage with source maps', async () => {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ for (const coverage of coverages) {
6161
const result = spawnSync(process.execPath, [
6262
'--test',
6363
'--experimental-test-coverage',
64+
'--test-coverage-exclude=!test/**',
6465
`${coverage.flag}=25`,
6566
'--test-reporter', 'tap',
6667
fixture,
@@ -77,6 +78,7 @@ for (const coverage of coverages) {
7778
const result = spawnSync(process.execPath, [
7879
'--test',
7980
'--experimental-test-coverage',
81+
'--test-coverage-exclude=!test/**',
8082
`${coverage.flag}=25`,
8183
'--test-reporter', reporter,
8284
fixture,
@@ -92,6 +94,7 @@ for (const coverage of coverages) {
9294
const result = spawnSync(process.execPath, [
9395
'--test',
9496
'--experimental-test-coverage',
97+
'--test-coverage-exclude=!test/**',
9598
`${coverage.flag}=99`,
9699
'--test-reporter', 'tap',
97100
fixture,
@@ -108,6 +111,7 @@ for (const coverage of coverages) {
108111
const result = spawnSync(process.execPath, [
109112
'--test',
110113
'--experimental-test-coverage',
114+
'--test-coverage-exclude=!test/**',
111115
`${coverage.flag}=99`,
112116
'--test-reporter', reporter,
113117
fixture,
@@ -123,6 +127,7 @@ for (const coverage of coverages) {
123127
const result = spawnSync(process.execPath, [
124128
'--test',
125129
'--experimental-test-coverage',
130+
'--test-coverage-exclude=!test/**',
126131
`${coverage.flag}=101`,
127132
fixture,
128133
]);
@@ -136,6 +141,7 @@ for (const coverage of coverages) {
136141
const result = spawnSync(process.execPath, [
137142
'--test',
138143
'--experimental-test-coverage',
144+
'--test-coverage-exclude=!test/**',
139145
`${coverage.flag}=-1`,
140146
fixture,
141147
]);

test/parallel/test-runner-coverage.js

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ test('test coverage report', async (t) => {
8484
}
8585

8686
const fixture = fixtures.path('test-runner', 'coverage.js');
87-
const args = ['--experimental-test-coverage', fixture];
87+
const args = [
88+
'--experimental-test-coverage',
89+
'--test-coverage-exclude=!test/**',
90+
fixture,
91+
];
8892
const result = spawnSync(process.execPath, args);
8993

9094
assert(!result.stdout.toString().includes('# start of coverage report'));
@@ -97,7 +101,13 @@ test('test coverage report', async (t) => {
97101
test('test tap coverage reporter', skipIfNoInspector, async (t) => {
98102
await t.test('coverage is reported and dumped to NODE_V8_COVERAGE if present', (t) => {
99103
const fixture = fixtures.path('test-runner', 'coverage.js');
100-
const args = ['--experimental-test-coverage', '--test-reporter', 'tap', fixture];
104+
const args = [
105+
'--experimental-test-coverage',
106+
'--test-coverage-exclude=!test/**',
107+
'--test-reporter',
108+
'tap',
109+
fixture,
110+
];
101111
const options = { env: { ...process.env, NODE_V8_COVERAGE: tmpdir.path } };
102112
const result = spawnSync(process.execPath, args, options);
103113
const report = getTapCoverageFixtureReport();
@@ -109,7 +119,13 @@ test('test tap coverage reporter', skipIfNoInspector, async (t) => {
109119

110120
await t.test('coverage is reported without NODE_V8_COVERAGE present', (t) => {
111121
const fixture = fixtures.path('test-runner', 'coverage.js');
112-
const args = ['--experimental-test-coverage', '--test-reporter', 'tap', fixture];
122+
const args = [
123+
'--experimental-test-coverage',
124+
'--test-coverage-exclude=!test/**',
125+
'--test-reporter',
126+
'tap',
127+
fixture,
128+
];
113129
const result = spawnSync(process.execPath, args);
114130
const report = getTapCoverageFixtureReport();
115131

@@ -123,7 +139,12 @@ test('test tap coverage reporter', skipIfNoInspector, async (t) => {
123139
test('test spec coverage reporter', skipIfNoInspector, async (t) => {
124140
await t.test('coverage is reported and dumped to NODE_V8_COVERAGE if present', (t) => {
125141
const fixture = fixtures.path('test-runner', 'coverage.js');
126-
const args = ['--experimental-test-coverage', '--test-reporter', 'spec', fixture];
142+
const args = [
143+
'--experimental-test-coverage',
144+
'--test-coverage-exclude=!test/**',
145+
'--test-reporter',
146+
'spec',
147+
fixture];
127148
const options = { env: { ...process.env, NODE_V8_COVERAGE: tmpdir.path } };
128149
const result = spawnSync(process.execPath, args, options);
129150
const report = getSpecCoverageFixtureReport();
@@ -136,7 +157,12 @@ test('test spec coverage reporter', skipIfNoInspector, async (t) => {
136157

137158
await t.test('coverage is reported without NODE_V8_COVERAGE present', (t) => {
138159
const fixture = fixtures.path('test-runner', 'coverage.js');
139-
const args = ['--experimental-test-coverage', '--test-reporter', 'spec', fixture];
160+
const args = [
161+
'--experimental-test-coverage',
162+
'--test-coverage-exclude=!test/**',
163+
'--test-reporter',
164+
'spec',
165+
fixture];
140166
const result = spawnSync(process.execPath, args);
141167
const report = getSpecCoverageFixtureReport();
142168

@@ -150,7 +176,12 @@ test('test spec coverage reporter', skipIfNoInspector, async (t) => {
150176
test('single process coverage is the same with --test', skipIfNoInspector, () => {
151177
const fixture = fixtures.path('test-runner', 'coverage.js');
152178
const args = [
153-
'--test', '--experimental-test-coverage', '--test-reporter', 'tap', fixture,
179+
'--test',
180+
'--experimental-test-coverage',
181+
'--test-coverage-exclude=!test/**',
182+
'--test-reporter',
183+
'tap',
184+
fixture,
154185
];
155186
const result = spawnSync(process.execPath, args);
156187
const report = getTapCoverageFixtureReport();
@@ -183,7 +214,11 @@ test('coverage is combined for multiple processes', skipIfNoInspector, () => {
183214

184215
const fixture = fixtures.path('v8-coverage', 'combined_coverage');
185216
const args = [
186-
'--test', '--experimental-test-coverage', '--test-reporter', 'tap',
217+
'--test',
218+
'--experimental-test-coverage',
219+
'--test-coverage-exclude=!test/**',
220+
'--test-reporter',
221+
'tap',
187222
];
188223
const result = spawnSync(process.execPath, args, {
189224
env: { ...process.env, NODE_TEST_TMPDIR: tmpdir.path },
@@ -221,7 +256,11 @@ test.skip('coverage works with isolation=none', skipIfNoInspector, () => {
221256

222257
const fixture = fixtures.path('v8-coverage', 'combined_coverage');
223258
const args = [
224-
'--test', '--experimental-test-coverage', '--test-reporter', 'tap', '--experimental-test-isolation=none',
259+
'--test',
260+
'--experimental-test-coverage',
261+
'--test-reporter',
262+
'tap',
263+
'--experimental-test-isolation=none',
225264
];
226265
const result = spawnSync(process.execPath, args, {
227266
env: { ...process.env, NODE_TEST_TMPDIR: tmpdir.path },
@@ -236,9 +275,14 @@ test.skip('coverage works with isolation=none', skipIfNoInspector, () => {
236275
test('coverage reports on lines, functions, and branches', skipIfNoInspector, async (t) => {
237276
const fixture = fixtures.path('test-runner', 'coverage.js');
238277
const child = spawnSync(process.execPath,
239-
['--test', '--experimental-test-coverage', '--test-reporter',
240-
fixtures.fileURL('test-runner/custom_reporters/coverage.mjs'),
241-
fixture]);
278+
[
279+
'--test',
280+
'--experimental-test-coverage',
281+
'--test-coverage-exclude=!test/**',
282+
'--test-reporter',
283+
fixtures.fileURL('test-runner/custom_reporters/coverage.mjs'),
284+
fixture,
285+
]);
242286
assert.strictEqual(child.stderr.toString(), '');
243287
const stdout = child.stdout.toString();
244288
const coverage = JSON.parse(stdout);
@@ -310,7 +354,14 @@ test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
310354

311355
const fixture = fixtures.path('test-runner', 'coverage-loader');
312356
const args = [
313-
'--import', './register-hooks.js', '--test', '--experimental-test-coverage', '--test-reporter', 'tap', 'virtual.js',
357+
'--import',
358+
'./register-hooks.js',
359+
'--test',
360+
'--experimental-test-coverage',
361+
'--test-coverage-exclude=!test/**',
362+
'--test-reporter',
363+
'tap',
364+
'virtual.js',
314365
];
315366
const result = spawnSync(process.execPath, args, { cwd: fixture });
316367

@@ -341,7 +392,10 @@ test('coverage with ESM hook - source transpiled', skipIfNoInspector, () => {
341392

342393
const fixture = fixtures.path('test-runner', 'coverage-loader');
343394
const args = [
344-
'--import', './register-hooks.js', '--test', '--experimental-test-coverage',
395+
'--import', './register-hooks.js',
396+
'--test',
397+
'--experimental-test-coverage',
398+
'--test-coverage-exclude=!test/**',
345399
'--test-reporter', 'tap', 'sum.test.ts',
346400
];
347401
const result = spawnSync(process.execPath, args, { cwd: fixture });
@@ -356,6 +410,7 @@ test('coverage with excluded files', skipIfNoInspector, () => {
356410
const args = [
357411
'--experimental-test-coverage', '--test-reporter', 'tap',
358412
'--test-coverage-exclude=test/*/test-runner/invalid-tap.js',
413+
'--test-coverage-exclude=!test/**',
359414
fixture];
360415
const result = spawnSync(process.execPath, args);
361416
const report = [
@@ -391,6 +446,7 @@ test('coverage with included files', skipIfNoInspector, () => {
391446
'--experimental-test-coverage', '--test-reporter', 'tap',
392447
'--test-coverage-include=test/fixtures/test-runner/coverage.js',
393448
'--test-coverage-include=test/fixtures/v8-coverage/throw.js',
449+
'--test-coverage-exclude=!test/**',
394450
fixture,
395451
];
396452
const result = spawnSync(process.execPath, args);
@@ -478,7 +534,12 @@ test('correctly prints the coverage report of files contained in parent director
478534
}
479535
const fixture = fixtures.path('test-runner', 'coverage.js');
480536
const args = [
481-
'--test', '--experimental-test-coverage', '--test-reporter', 'tap', fixture,
537+
'--test',
538+
'--experimental-test-coverage',
539+
'--test-coverage-exclude=!test/**',
540+
'--test-reporter',
541+
'tap',
542+
fixture,
482543
];
483544
const result = spawnSync(process.execPath, args, {
484545
env: { ...process.env, NODE_TEST_TMPDIR: tmpdir.path },

0 commit comments

Comments
 (0)