Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 63a795a

Browse files
cjihrigMoLow
authored andcommittedFeb 25, 2023
test_runner: add initial code coverage support
This commit adds code coverage functionality to the node:test module. When node:test is used in conjunction with the new --test-coverage CLI flag, a coverage report is created when the test runner finishes. The coverage summary is forwarded to any test runner reporters so that the display can be customized as desired. This new functionality is compatible with the existing NODE_V8_COVERAGE environment variable as well. There are still several limitations, which will be addressed in subsequent pull requests: - Coverage is only reported for a single process. It is possible to merge coverage reports together. Once this is done, the --test flag will be supported as well. - Source maps are not currently supported. - Excluding specific files or directories from the coverage report is not currently supported. Node core modules and node_modules/ are excluded though. PR-URL: nodejs#46017 Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]>
1 parent 6d9ec6c commit 63a795a

File tree

16 files changed

+814
-33
lines changed

16 files changed

+814
-33
lines changed
 

‎doc/api/cli.md

+12
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,17 @@ Starts the Node.js command line test runner. This flag cannot be combined with
12091209
See the documentation on [running tests from the command line][]
12101210
for more details.
12111211

1212+
### `--test-coverage`
1213+
1214+
<!-- YAML
1215+
added: REPLACEME
1216+
-->
1217+
1218+
When used in conjunction with the `node:test` module, a code coverage report is
1219+
generated as part of the test runner output. If no tests are run, a coverage
1220+
report is not generated. See the documentation on
1221+
[collecting code coverage from tests][] for more details.
1222+
12121223
### `--test-name-pattern`
12131224

12141225
<!-- YAML
@@ -2313,6 +2324,7 @@ done
23132324
[`unhandledRejection`]: process.md#event-unhandledrejection
23142325
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
23152326
[`worker_threads.threadId`]: worker_threads.md#workerthreadid
2327+
[collecting code coverage from tests]: test.md#collecting-code-coverage
23162328
[conditional exports]: packages.md#conditional-exports
23172329
[context-aware]: addons.md#context-aware-addons
23182330
[customizing ESM specifier resolution]: esm.md#customizing-esm-specifier-resolution-algorithm

‎doc/api/test.md

+87
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,54 @@ Otherwise, the test is considered to be a failure. Test files must be
368368
executable by Node.js, but are not required to use the `node:test` module
369369
internally.
370370

371+
## Collecting code coverage
372+
373+
When Node.js is started with the [`--test-coverage`][] command-line flag, code
374+
coverage is collected and statistics are reported once all tests have completed.
375+
If the [`NODE_V8_COVERAGE`][] environment variable is used to specify a
376+
code coverage directory, the generated V8 coverage files are written to that
377+
directory. Node.js core modules and files within `node_modules/` directories
378+
are not included in the coverage report. If coverage is enabled, the coverage
379+
report is sent to any [test reporters][] via the `'test:coverage'` event.
380+
381+
Coverage can be disabled on a series of lines using the following
382+
comment syntax:
383+
384+
```js
385+
/* node:coverage disable */
386+
if (anAlwaysFalseCondition) {
387+
// Code in this branch will never be executed, but the lines are ignored for
388+
// coverage purposes. All lines following the 'disable' comment are ignored
389+
// until a corresponding 'enable' comment is encountered.
390+
console.log('this is never executed');
391+
}
392+
/* node:coverage enable */
393+
```
394+
395+
Coverage can also be disabled for a specified number of lines. After the
396+
specified number of lines, coverage will be automatically reenabled. If the
397+
number of lines is not explicitly provided, a single line is ignored.
398+
399+
```js
400+
/* node:coverage ignore next */
401+
if (anAlwaysFalseCondition) { console.log('this is never executed'); }
402+
403+
/* node:coverage ignore next 3 */
404+
if (anAlwaysFalseCondition) {
405+
console.log('this is never executed');
406+
}
407+
```
408+
409+
The test runner's code coverage functionality has the following limitations,
410+
which will be addressed in a future Node.js release:
411+
412+
* Although coverage data is collected for child processes, this information is
413+
not included in the coverage report. Because the command line test runner uses
414+
child processes to execute test files, it cannot be used with `--test-coverage`.
415+
* Source maps are not supported.
416+
* Excluding specific files or directories from the coverage report is not
417+
supported.
418+
371419
## Mocking
372420

373421
The `node:test` module supports mocking during testing via a top-level `mock`
@@ -1201,6 +1249,42 @@ A successful call to [`run()`][] method will return a new {TestsStream}
12011249
object, streaming a series of events representing the execution of the tests.
12021250
`TestsStream` will emit events, in the order of the tests definition
12031251

1252+
### Event: `'test:coverage'`
1253+
1254+
* `data` {Object}
1255+
* `summary` {Object} An object containing the coverage report.
1256+
* `files` {Array} An array of coverage reports for individual files. Each
1257+
report is an object with the following schema:
1258+
* `path` {string} The absolute path of the file.
1259+
* `totalLineCount` {number} The total number of lines.
1260+
* `totalBranchCount` {number} The total number of branches.
1261+
* `totalFunctionCount` {number} The total number of functions.
1262+
* `coveredLineCount` {number} The number of covered lines.
1263+
* `coveredBranchCount` {number} The number of covered branches.
1264+
* `coveredFunctionCount` {number} The number of covered functions.
1265+
* `coveredLinePercent` {number} The percentage of lines covered.
1266+
* `coveredBranchPercent` {number} The percentage of branches covered.
1267+
* `coveredFunctionPercent` {number} The percentage of functions covered.
1268+
* `uncoveredLineNumbers` {Array} An array of integers representing line
1269+
numbers that are uncovered.
1270+
* `totals` {Object} An object containing a summary of coverage for all
1271+
files.
1272+
* `totalLineCount` {number} The total number of lines.
1273+
* `totalBranchCount` {number} The total number of branches.
1274+
* `totalFunctionCount` {number} The total number of functions.
1275+
* `coveredLineCount` {number} The number of covered lines.
1276+
* `coveredBranchCount` {number} The number of covered branches.
1277+
* `coveredFunctionCount` {number} The number of covered functions.
1278+
* `coveredLinePercent` {number} The percentage of lines covered.
1279+
* `coveredBranchPercent` {number} The percentage of branches covered.
1280+
* `coveredFunctionPercent` {number} The percentage of functions covered.
1281+
* `workingDirectory` {string} The working directory when code coverage
1282+
began. This is useful for displaying relative path names in case the tests
1283+
changed the working directory of the Node.js process.
1284+
* `nesting` {number} The nesting level of the test.
1285+
1286+
Emitted when code coverage is enabled and all tests have completed.
1287+
12041288
### Event: `'test:diagnostic'`
12051289

12061290
* `data` {Object}
@@ -1550,6 +1634,7 @@ added: v18.7.0
15501634
aborted.
15511635

15521636
[TAP]: https://testanything.org/
1637+
[`--test-coverage`]: cli.md#--test-coverage
15531638
[`--test-name-pattern`]: cli.md#--test-name-pattern
15541639
[`--test-only`]: cli.md#--test-only
15551640
[`--test-reporter-destination`]: cli.md#--test-reporter-destination
@@ -1558,6 +1643,7 @@ added: v18.7.0
15581643
[`MockFunctionContext`]: #class-mockfunctioncontext
15591644
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
15601645
[`MockTracker`]: #class-mocktracker
1646+
[`NODE_V8_COVERAGE`]: cli.md#node_v8_coveragedir
15611647
[`SuiteContext`]: #class-suitecontext
15621648
[`TestContext`]: #class-testcontext
15631649
[`context.diagnostic`]: #contextdiagnosticmessage
@@ -1568,4 +1654,5 @@ added: v18.7.0
15681654
[describe options]: #describename-options-fn
15691655
[it options]: #testname-options-fn
15701656
[stream.compose]: stream.md#streamcomposestreams
1657+
[test reporters]: #test-reporters
15711658
[test runner execution model]: #test-runner-execution-model

‎doc/node.1

+3
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ Specify the minimum allocation from the OpenSSL secure heap. The default is 2. T
391391
.It Fl -test
392392
Starts the Node.js command line test runner.
393393
.
394+
.It Fl -test-coverage
395+
Enable code coverage in the test runner.
396+
.
394397
.It Fl -test-name-pattern
395398
A regular expression that configures the test runner to only execute tests
396399
whose name matches the provided pattern.

‎lib/internal/process/pre_execution.js

+13-30
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const { reconnectZeroFillToggle } = require('internal/buffer');
2020
const {
2121
defineOperation,
2222
exposeInterface,
23+
setupCoverageHooks,
2324
} = require('internal/util');
2425

2526
const {
@@ -63,15 +64,7 @@ function prepareExecution(options) {
6364
setupFetch();
6465
setupWebCrypto();
6566
setupCustomEvent();
66-
67-
// Resolve the coverage directory to an absolute path, and
68-
// overwrite process.env so that the original path gets passed
69-
// to child processes even when they switch cwd.
70-
if (process.env.NODE_V8_COVERAGE) {
71-
process.env.NODE_V8_COVERAGE =
72-
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
73-
}
74-
67+
setupCodeCoverage();
7568
setupDebugEnv();
7669
// Process initial diagnostic reporting configuration, if present.
7770
initializeReport();
@@ -288,6 +281,17 @@ function setupWebCrypto() {
288281
}
289282
}
290283

284+
function setupCodeCoverage() {
285+
// Resolve the coverage directory to an absolute path, and
286+
// overwrite process.env so that the original path gets passed
287+
// to child processes even when they switch cwd. Don't do anything if the
288+
// --test-coverage flag is present, as the test runner will handle coverage.
289+
if (process.env.NODE_V8_COVERAGE && !getOptionValue('--test-coverage')) {
290+
process.env.NODE_V8_COVERAGE =
291+
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
292+
}
293+
}
294+
291295
// TODO(daeyeon): move this to internal/bootstrap/browser when the CLI flag is
292296
// removed.
293297
function setupCustomEvent() {
@@ -299,27 +303,6 @@ function setupCustomEvent() {
299303
exposeInterface(globalThis, 'CustomEvent', CustomEvent);
300304
}
301305

302-
// Setup User-facing NODE_V8_COVERAGE environment variable that writes
303-
// ScriptCoverage to a specified file.
304-
function setupCoverageHooks(dir) {
305-
const cwd = require('internal/process/execution').tryGetCwd();
306-
const { resolve } = require('path');
307-
const coverageDirectory = resolve(cwd, dir);
308-
const { sourceMapCacheToObject } =
309-
require('internal/source_map/source_map_cache');
310-
311-
if (process.features.inspector) {
312-
internalBinding('profiler').setCoverageDirectory(coverageDirectory);
313-
internalBinding('profiler').setSourceMapCacheGetter(sourceMapCacheToObject);
314-
} else {
315-
process.emitWarning('The inspector is disabled, ' +
316-
'coverage could not be collected',
317-
'Warning');
318-
return '';
319-
}
320-
return coverageDirectory;
321-
}
322-
323306
function setupStacktracePrinterOnSigint() {
324307
if (!getOptionValue('--trace-sigint')) {
325308
return;

0 commit comments

Comments
 (0)
Please sign in to comment.