Skip to content

Commit dccee04

Browse files
avivkelleraduh95
authored andcommitted
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 e024712 commit dccee04

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
@@ -2867,6 +2867,21 @@ This option may be specified multiple times to include multiple glob patterns.
28672867
If both `--test-coverage-exclude` and `--test-coverage-include` are provided,
28682868
files must meet **both** criteria to be included in the coverage report.
28692869

2870+
### `--test-coverage-include-all`
2871+
2872+
<!-- YAML
2873+
added: REPLACEME
2874+
-->
2875+
2876+
> Stability: 1 - Experimental
2877+
2878+
Includes source files that were never loaded by the test run in the coverage
2879+
report, where they are reported as having zero coverage.
2880+
2881+
Candidate files are searched for in the current working directory, and are
2882+
subject to the same `--test-coverage-include` and `--test-coverage-exclude`
2883+
filtering as the rest of the report.
2884+
28702885
### `--test-coverage-lines=threshold`
28712886

28722887
<!-- YAML
@@ -3904,6 +3919,7 @@ one is included in the list below.
39043919
* `--test-coverage-branches`
39053920
* `--test-coverage-exclude`
39063921
* `--test-coverage-functions`
3922+
* `--test-coverage-include-all`
39073923
* `--test-coverage-include`
39083924
* `--test-coverage-lines`
39093925
* `--test-global-setup`

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

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

β€Ždoc/node.1β€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,13 @@ This option may be specified multiple times to include multiple glob patterns.
14101410
If both \fB--test-coverage-exclude\fR and \fB--test-coverage-include\fR are provided,
14111411
files must meet \fBboth\fR criteria to be included in the coverage report.
14121412
.
1413+
.It Fl -test-coverage-include-all
1414+
Includes source files that were never loaded by the test run in the coverage
1415+
report, where they are reported as having zero coverage.
1416+
Candidate files are searched for in the current working directory, and are
1417+
subject to the same \fB--test-coverage-include\fR and \fB--test-coverage-exclude\fR
1418+
filtering as the rest of the report.
1419+
.
14131420
.It Fl -test-coverage-lines Ns = Ns Ar threshold
14141421
Require a minimum percent of covered lines. If code coverage does not reach
14151422
the threshold specified, the process will exit with code \fB1\fR.
@@ -2135,6 +2142,8 @@ one is included in the list below.
21352142
.It
21362143
\fB--test-coverage-functions\fR
21372144
.It
2145+
\fB--test-coverage-include-all\fR
2146+
.It
21382147
\fB--test-coverage-include\fR
21392148
.It
21402149
\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
@@ -1025,6 +1025,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
10251025
&EnvironmentOptions::coverage_include_pattern,
10261026
kAllowedInEnvvar,
10271027
OptionNamespaces::kTestRunnerNamespace);
1028+
AddOption("--test-coverage-include-all",
1029+
"include source files that were never loaded in the coverage "
1030+
"report",
1031+
&EnvironmentOptions::coverage_include_all,
1032+
kAllowedInEnvvar,
1033+
false,
1034+
OptionNamespaces::kTestRunnerNamespace);
10281035
AddOption("--test-coverage-exclude",
10291036
"exclude files from coverage report that match this glob pattern",
10301037
&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)