Skip to content

Commit d097b95

Browse files
avivkelleraduh95
authored andcommitted
test_runner: add support for --test-coverage-include-all
Signed-off-by: avivkeller <[email protected]> PR-URL: #64830 Fixes: #58887 Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Chemi Atlow <[email protected]>
1 parent feeeed1 commit d097b95

15 files changed

Lines changed: 201 additions & 1 deletion

File tree

β€Ždoc/api/cli.mdβ€Ž

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2725,6 +2725,21 @@ This option may be specified multiple times to include multiple glob patterns.
27252725
If both `--test-coverage-exclude` and `--test-coverage-include` are provided,
27262726
files must meet **both** criteria to be included in the coverage report.
27272727

2728+
### `--test-coverage-include-all`
2729+
2730+
<!-- YAML
2731+
added: REPLACEME
2732+
-->
2733+
2734+
> Stability: 1 - Experimental
2735+
2736+
Includes source files that were never loaded by the test run in the coverage
2737+
report, where they are reported as having zero coverage.
2738+
2739+
Candidate files are searched for in the current working directory, and are
2740+
subject to the same `--test-coverage-include` and `--test-coverage-exclude`
2741+
filtering as the rest of the report.
2742+
27282743
### `--test-coverage-lines=threshold`
27292744

27302745
<!-- YAML
@@ -3758,6 +3773,7 @@ one is included in the list below.
37583773
* `--test-coverage-branches`
37593774
* `--test-coverage-exclude`
37603775
* `--test-coverage-functions`
3776+
* `--test-coverage-include-all`
37613777
* `--test-coverage-include`
37623778
* `--test-coverage-lines`
37633779
* `--test-global-setup`

β€Ždoc/api/test.mdβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,6 +1777,13 @@ changes:
17771777
If both `coverageExcludeGlobs` and `coverageIncludeGlobs` are provided,
17781778
files must meet **both** criteria to be included in the coverage report.
17791779
**Default:** `undefined`.
1780+
* `coverageIncludeAll` {boolean} Includes source files that were never loaded by
1781+
the test run in the coverage report, where they are reported as having zero
1782+
coverage. Candidate files are searched for in `cwd`, and are subject to the
1783+
same `coverageIncludeGlobs` and `coverageExcludeGlobs` filtering as the rest
1784+
of the report. This property is only applicable when `coverage` was set to
1785+
`true`.
1786+
**Default:** `false`.
17801787
* `lineCoverage` {number} Require a minimum percent of covered lines. If code
17811788
coverage does not reach the threshold specified, the process will exit with code `1`.
17821789
**Default:** `0`.

β€Ždoc/node.1β€Ž

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,13 @@ Require a minimum threshold for function coverage (0 - 100).
607607
.It Fl -test-coverage-include
608608
A glob pattern that only includes matching files in the coverage report
609609
.
610+
.It Fl -test-coverage-include-all
611+
Includes source files that were never loaded by the test run in the coverage
612+
report, where they are reported as having zero coverage.
613+
Candidate files are searched for in the current working directory, and are
614+
subject to the same \fB--test-coverage-include\fR and \fB--test-coverage-exclude\fR
615+
filtering as the rest of the report.
616+
.
610617
.It Fl -test-coverage-lines Ns = Ns Ar threshold
611618
Require a minimum threshold for line coverage (0 - 100).
612619
.
@@ -938,6 +945,13 @@ Node.js will exit with an error if an option that is not allowed in the environm
938945
.Fl -print
939946
or a script file.
940947
.
948+
.It Fl -test-coverage-include-all
949+
Includes source files that were never loaded by the test run in the coverage
950+
report, where they are reported as having zero coverage.
951+
Candidate files are searched for in the current working directory, and are
952+
subject to the same \fB--test-coverage-include\fR and \fB--test-coverage-exclude\fR
953+
filtering as the rest of the report.
954+
.
941955
.It Ev NODE_PATH Ar directories...
942956
A colon-separated list of
943957
.Ar directories

β€Žlib/internal/test_runner/coverage.jsβ€Ž

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const {
2020
} = primordials;
2121
const {
2222
copyFileSync,
23+
globSync,
2324
mkdirSync,
2425
mkdtempSync,
2526
opendirSync,
@@ -29,7 +30,7 @@ const {
2930
const { setupCoverageHooks } = require('internal/util');
3031
const { tmpdir } = require('os');
3132
const { join, resolve, relative } = require('path');
32-
const { fileURLToPath, URL } = require('internal/url');
33+
const { fileURLToPath, pathToFileURL, URL } = require('internal/url');
3334
const { kMappings, SourceMap } = require('internal/source_map/source_map');
3435
const {
3536
codes: {
@@ -51,6 +52,7 @@ const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;
5152
const kMatchGlobPatternOptions = { __proto__: null, dot: true };
5253
const kTypeOnlyImportRegex = /^\s*import\s+type\b/u;
5354
const kTypeScriptSourceRegex = /\.(?:cts|mts|ts)$/u;
55+
const kSourceFileGlob = '**/*.{cjs,cts,js,mjs,mts,ts}';
5456

5557
let stripTypeScriptTypesForCoverage;
5658

@@ -418,6 +420,10 @@ class TestCoverage {
418420
this.mergeCoverage(result, this.mapCoverageWithSourceMap(coverage));
419421
}
420422

423+
if (this.options.coverageIncludeAll) {
424+
this.#addUntestedFileCoverage(result);
425+
}
426+
421427
return ArrayFrom(result.values());
422428
} finally {
423429
if (dir) {
@@ -426,6 +432,48 @@ class TestCoverage {
426432
}
427433
}
428434

435+
#addUntestedFileCoverage(merged) {
436+
const files = globSync(kSourceFileGlob, {
437+
__proto__: null,
438+
cwd: this.options.cwd,
439+
// Skip node_modules/, since `shouldSkipFileCoverage` would skip it anyway
440+
exclude: (name) => name === 'node_modules',
441+
});
442+
443+
for (let i = 0; i < files.length; ++i) {
444+
const url = pathToFileURL(resolve(this.options.cwd, files[i])).href;
445+
446+
if (merged.has(url) || this.shouldSkipFileCoverage(url)) {
447+
continue;
448+
}
449+
450+
this.markTypeScriptOnlyLines(url);
451+
const lines = this.getLines(url);
452+
453+
if (!lines || lines.length === 0) {
454+
continue;
455+
}
456+
457+
const lastLine = lines[lines.length - 1];
458+
459+
merged.set(url, {
460+
__proto__: null,
461+
url,
462+
functions: [{
463+
__proto__: null,
464+
functionName: '',
465+
isBlockCoverage: false,
466+
ranges: [{
467+
__proto__: null,
468+
startOffset: 0,
469+
endOffset: lastLine.startOffset + lastLine.src.length,
470+
count: 0,
471+
}],
472+
}],
473+
});
474+
}
475+
}
476+
429477

430478
mapCoverageWithSourceMap(coverage) {
431479
const { result } = coverage;

β€Žlib/internal/test_runner/runner.jsβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ function run(options = kEmptyObject) {
731731
only,
732732
globPatterns,
733733
coverage = false,
734+
coverageIncludeAll = false,
734735
lineCoverage = 0,
735736
branchCoverage = 0,
736737
functionCoverage = 0,
@@ -887,6 +888,7 @@ function run(options = kEmptyObject) {
887888

888889
validateOneOf(isolation, 'options.isolation', ['process', 'none']);
889890
validateBoolean(coverage, 'options.coverage');
891+
validateBoolean(coverageIncludeAll, 'options.coverageIncludeAll');
890892
if (coverageExcludeGlobs != null) {
891893
if (!ArrayIsArray(coverageExcludeGlobs)) {
892894
coverageExcludeGlobs = [coverageExcludeGlobs];
@@ -928,6 +930,7 @@ function run(options = kEmptyObject) {
928930
...parseCommandLine(),
929931
setup, // This line can be removed when parseCommandLine() is removed here.
930932
coverage,
933+
coverageIncludeAll,
931934
coverageExcludeGlobs,
932935
coverageIncludeGlobs,
933936
rerunFailuresFilePath,

β€Žlib/internal/test_runner/utils.jsβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ function parseCommandLine() {
245245

246246
const isTestRunner = getOptionValue('--test');
247247
const coverage = getOptionValue('--experimental-test-coverage');
248+
const coverageIncludeAll = getOptionValue('--test-coverage-include-all');
248249
const forceExit = getOptionValue('--test-force-exit');
249250
const sourceMaps = getOptionValue('--enable-source-maps');
250251
const updateSnapshots = getOptionValue('--test-update-snapshots');
@@ -408,6 +409,7 @@ function parseCommandLine() {
408409
isTestRunner,
409410
concurrency,
410411
coverage,
412+
coverageIncludeAll,
411413
coverageExcludeGlobs,
412414
coverageIncludeGlobs,
413415
destinations,

β€Žsrc/node_options.ccβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
10111011
&EnvironmentOptions::coverage_include_pattern,
10121012
kAllowedInEnvvar,
10131013
OptionNamespaces::kTestRunnerNamespace);
1014+
AddOption("--test-coverage-include-all",
1015+
"include source files that were never loaded in the coverage "
1016+
"report",
1017+
&EnvironmentOptions::coverage_include_all,
1018+
kAllowedInEnvvar,
1019+
false,
1020+
OptionNamespaces::kTestRunnerNamespace);
10141021
AddOption("--test-coverage-exclude",
10151022
"exclude files from coverage report that match this glob pattern",
10161023
&EnvironmentOptions::coverage_exclude_pattern,

β€Žsrc/node_options.hβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ class EnvironmentOptions : public Options {
220220
std::vector<std::string> test_skip_pattern;
221221
std::vector<std::string> experimental_test_tag_filter;
222222
std::vector<std::string> coverage_include_pattern;
223+
bool coverage_include_all = false;
223224
std::vector<std::string> coverage_exclude_pattern;
224225
bool throw_deprecation = false;
225226
bool trace_deprecation = false;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
module.exports = function covered() {
4+
return 'covered';
5+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"not": "a source file"
3+
}

0 commit comments

Comments
Β (0)