Skip to content

Commit b7d29fe

Browse files
committed
test_runner: add support for --test-coverage-include-all
Signed-off-by: avivkeller <me@aviv.sh> PR-URL: #64830 Fixes: #58887 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
1 parent c543cfb commit b7d29fe

15 files changed

Lines changed: 196 additions & 1 deletion

File tree

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

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

2880+
### `--test-coverage-include-all`
2881+
2882+
<!-- YAML
2883+
added: REPLACEME
2884+
-->
2885+
2886+
> Stability: 1 - Experimental
2887+
2888+
Includes source files that were never loaded by the test run in the coverage
2889+
report, where they are reported as having zero coverage.
2890+
2891+
Candidate files are searched for in the current working directory, and are
2892+
subject to the same `--test-coverage-include` and `--test-coverage-exclude`
2893+
filtering as the rest of the report.
2894+
28802895
### `--test-coverage-lines=threshold`
28812896

28822897
<!-- YAML
@@ -3920,6 +3935,7 @@ one is included in the list below.
39203935
* `--test-coverage-branches`
39213936
* `--test-coverage-exclude`
39223937
* `--test-coverage-functions`
3938+
* `--test-coverage-include-all`
39233939
* `--test-coverage-include`
39243940
* `--test-coverage-lines`
39253941
* `--test-global-setup`

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

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

β€Ždoc/node.1β€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,13 @@ This option may be specified multiple times to include multiple glob patterns.
14111411
If both \fB--test-coverage-exclude\fR and \fB--test-coverage-include\fR are provided,
14121412
files must meet \fBboth\fR criteria to be included in the coverage report.
14131413
.
1414+
.It Fl -test-coverage-include-all
1415+
Includes source files that were never loaded by the test run in the coverage
1416+
report, where they are reported as having zero coverage.
1417+
Candidate files are searched for in the current working directory, and are
1418+
subject to the same \fB--test-coverage-include\fR and \fB--test-coverage-exclude\fR
1419+
filtering as the rest of the report.
1420+
.
14141421
.It Fl -test-coverage-lines Ns = Ns Ar threshold
14151422
Require a minimum percent of covered lines. If code coverage does not reach
14161423
the threshold specified, the process will exit with code \fB1\fR.
@@ -2136,6 +2143,8 @@ one is included in the list below.
21362143
.It
21372144
\fB--test-coverage-functions\fR
21382145
.It
2146+
\fB--test-coverage-include-all\fR
2147+
.It
21392148
\fB--test-coverage-include\fR
21402149
.It
21412150
\fB--test-coverage-lines\fR

β€Ž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: {
@@ -48,6 +49,7 @@ const kLineSplitRegex = /(?<=\r?\n)/u;
4849
const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;
4950
const kTypeOnlyImportRegex = /^\s*import\s+type\b/u;
5051
const kTypeScriptSourceRegex = /\.(?:cts|mts|ts)$/u;
52+
const kSourceFileGlob = '**/*.{cjs,cts,js,mjs,mts,ts}';
5153

5254
let stripTypeScriptTypesForCoverage;
5355

@@ -407,6 +409,10 @@ class TestCoverage {
407409
this.mergeCoverage(result, this.mapCoverageWithSourceMap(coverage));
408410
}
409411

412+
if (this.options.coverageIncludeAll) {
413+
this.#addUntestedFileCoverage(result);
414+
}
415+
410416
return ArrayFrom(result.values());
411417
} finally {
412418
if (dir) {
@@ -415,6 +421,48 @@ class TestCoverage {
415421
}
416422
}
417423

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

419467
mapCoverageWithSourceMap(coverage) {
420468
const { result } = coverage;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ function run(options = kEmptyObject) {
723723
only,
724724
globPatterns,
725725
coverage = false,
726+
coverageIncludeAll = false,
726727
lineCoverage = 0,
727728
branchCoverage = 0,
728729
functionCoverage = 0,
@@ -882,6 +883,7 @@ function run(options = kEmptyObject) {
882883

883884
validateOneOf(isolation, 'options.isolation', ['process', 'none']);
884885
validateBoolean(coverage, 'options.coverage');
886+
validateBoolean(coverageIncludeAll, 'options.coverageIncludeAll');
885887
if (coverageExcludeGlobs != null) {
886888
if (!ArrayIsArray(coverageExcludeGlobs)) {
887889
coverageExcludeGlobs = [coverageExcludeGlobs];
@@ -923,6 +925,7 @@ function run(options = kEmptyObject) {
923925
...parseCommandLine(),
924926
setup, // This line can be removed when parseCommandLine() is removed here.
925927
coverage,
928+
coverageIncludeAll,
926929
coverageExcludeGlobs,
927930
coverageIncludeGlobs,
928931
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');
@@ -415,6 +416,7 @@ function parseCommandLine() {
415416
isTestRunner,
416417
concurrency,
417418
coverage,
419+
coverageIncludeAll,
418420
coverageExcludeGlobs,
419421
coverageIncludeGlobs,
420422
destinations,

β€Žsrc/node_options.ccβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
10291029
&EnvironmentOptions::coverage_include_pattern,
10301030
kAllowedInEnvvar,
10311031
OptionNamespaces::kTestRunnerNamespace);
1032+
AddOption("--test-coverage-include-all",
1033+
"include source files that were never loaded in the coverage "
1034+
"report",
1035+
&EnvironmentOptions::coverage_include_all,
1036+
kAllowedInEnvvar,
1037+
false,
1038+
OptionNamespaces::kTestRunnerNamespace);
10321039
AddOption("--test-coverage-exclude",
10331040
"exclude files from coverage report that match this glob pattern",
10341041
&EnvironmentOptions::coverage_exclude_pattern,

β€Žsrc/node_options.hβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ class EnvironmentOptions : public Options {
221221
std::vector<std::string> test_skip_pattern;
222222
std::vector<std::string> experimental_test_tag_filter;
223223
std::vector<std::string> coverage_include_pattern;
224+
bool coverage_include_all = false;
224225
std::vector<std::string> coverage_exclude_pattern;
225226
bool throw_deprecation = false;
226227
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)