Skip to content

Commit 6119289

Browse files
cjihrigMylesBorins
authored andcommittedFeb 18, 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: #46017 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1 parent c9d5dfd commit 6119289

File tree

16 files changed

+796
-33
lines changed

16 files changed

+796
-33
lines changed
 

‎doc/api/cli.md

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

1231+
### `--test-coverage`
1232+
1233+
<!-- YAML
1234+
added: REPLACEME
1235+
-->
1236+
1237+
When used in conjunction with the `node:test` module, a code coverage report is
1238+
generated as part of the test runner output. If no tests are run, a coverage
1239+
report is not generated. See the documentation on
1240+
[collecting code coverage from tests][] for more details.
1241+
12311242
### `--test-name-pattern`
12321243

12331244
<!-- YAML
@@ -2346,6 +2357,7 @@ done
23462357
[`unhandledRejection`]: process.md#event-unhandledrejection
23472358
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
23482359
[`worker_threads.threadId`]: worker_threads.md#workerthreadid
2360+
[collecting code coverage from tests]: test.md#collecting-code-coverage
23492361
[conditional exports]: packages.md#conditional-exports
23502362
[context-aware]: addons.md#context-aware-addons
23512363
[debugger]: debugger.md

‎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`
@@ -1215,6 +1263,42 @@ A successful call to [`run()`][] method will return a new {TestsStream}
12151263
object, streaming a series of events representing the execution of the tests.
12161264
`TestsStream` will emit events, in the order of the tests definition
12171265

1266+
### Event: `'test:coverage'`
1267+
1268+
* `data` {Object}
1269+
* `summary` {Object} An object containing the coverage report.
1270+
* `files` {Array} An array of coverage reports for individual files. Each
1271+
report is an object with the following schema:
1272+
* `path` {string} The absolute path of the file.
1273+
* `totalLineCount` {number} The total number of lines.
1274+
* `totalBranchCount` {number} The total number of branches.
1275+
* `totalFunctionCount` {number} The total number of functions.
1276+
* `coveredLineCount` {number} The number of covered lines.
1277+
* `coveredBranchCount` {number} The number of covered branches.
1278+
* `coveredFunctionCount` {number} The number of covered functions.
1279+
* `coveredLinePercent` {number} The percentage of lines covered.
1280+
* `coveredBranchPercent` {number} The percentage of branches covered.
1281+
* `coveredFunctionPercent` {number} The percentage of functions covered.
1282+
* `uncoveredLineNumbers` {Array} An array of integers representing line
1283+
numbers that are uncovered.
1284+
* `totals` {Object} An object containing a summary of coverage for all
1285+
files.
1286+
* `totalLineCount` {number} The total number of lines.
1287+
* `totalBranchCount` {number} The total number of branches.
1288+
* `totalFunctionCount` {number} The total number of functions.
1289+
* `coveredLineCount` {number} The number of covered lines.
1290+
* `coveredBranchCount` {number} The number of covered branches.
1291+
* `coveredFunctionCount` {number} The number of covered functions.
1292+
* `coveredLinePercent` {number} The percentage of lines covered.
1293+
* `coveredBranchPercent` {number} The percentage of branches covered.
1294+
* `coveredFunctionPercent` {number} The percentage of functions covered.
1295+
* `workingDirectory` {string} The working directory when code coverage
1296+
began. This is useful for displaying relative path names in case the tests
1297+
changed the working directory of the Node.js process.
1298+
* `nesting` {number} The nesting level of the test.
1299+
1300+
Emitted when code coverage is enabled and all tests have completed.
1301+
12181302
### Event: `'test:diagnostic'`
12191303

12201304
* `data` {Object}
@@ -1595,6 +1679,7 @@ added:
15951679

15961680
[TAP]: https://testanything.org/
15971681
[`--import`]: cli.md#--importmodule
1682+
[`--test-coverage`]: cli.md#--test-coverage
15981683
[`--test-name-pattern`]: cli.md#--test-name-pattern
15991684
[`--test-only`]: cli.md#--test-only
16001685
[`--test-reporter-destination`]: cli.md#--test-reporter-destination
@@ -1603,6 +1688,7 @@ added:
16031688
[`MockFunctionContext`]: #class-mockfunctioncontext
16041689
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
16051690
[`MockTracker`]: #class-mocktracker
1691+
[`NODE_V8_COVERAGE`]: cli.md#node_v8_coveragedir
16061692
[`SuiteContext`]: #class-suitecontext
16071693
[`TestContext`]: #class-testcontext
16081694
[`context.diagnostic`]: #contextdiagnosticmessage
@@ -1613,4 +1699,5 @@ added:
16131699
[describe options]: #describename-options-fn
16141700
[it options]: #testname-options-fn
16151701
[stream.compose]: stream.md#streamcomposestreams
1702+
[test reporters]: #test-reporters
16161703
[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 {
2020
exposeInterface,
2121
exposeLazyInterfaces,
2222
defineReplaceableLazyAttribute,
23+
setupCoverageHooks,
2324
} = require('internal/util');
2425

2526
const {
@@ -66,15 +67,7 @@ function prepareExecution(options) {
6667
setupFetch();
6768
setupWebCrypto();
6869
setupCustomEvent();
69-
70-
// Resolve the coverage directory to an absolute path, and
71-
// overwrite process.env so that the original path gets passed
72-
// to child processes even when they switch cwd.
73-
if (process.env.NODE_V8_COVERAGE) {
74-
process.env.NODE_V8_COVERAGE =
75-
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
76-
}
77-
70+
setupCodeCoverage();
7871
setupDebugEnv();
7972
// Process initial diagnostic reporting configuration, if present.
8073
initializeReport();
@@ -304,6 +297,17 @@ function setupWebCrypto() {
304297
}
305298
}
306299

300+
function setupCodeCoverage() {
301+
// Resolve the coverage directory to an absolute path, and
302+
// overwrite process.env so that the original path gets passed
303+
// to child processes even when they switch cwd. Don't do anything if the
304+
// --test-coverage flag is present, as the test runner will handle coverage.
305+
if (process.env.NODE_V8_COVERAGE && !getOptionValue('--test-coverage')) {
306+
process.env.NODE_V8_COVERAGE =
307+
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
308+
}
309+
}
310+
307311
// TODO(daeyeon): move this to internal/bootstrap/browser when the CLI flag is
308312
// removed.
309313
function setupCustomEvent() {
@@ -315,27 +319,6 @@ function setupCustomEvent() {
315319
exposeInterface(globalThis, 'CustomEvent', CustomEvent);
316320
}
317321

318-
// Setup User-facing NODE_V8_COVERAGE environment variable that writes
319-
// ScriptCoverage to a specified file.
320-
function setupCoverageHooks(dir) {
321-
const cwd = require('internal/process/execution').tryGetCwd();
322-
const { resolve } = require('path');
323-
const coverageDirectory = resolve(cwd, dir);
324-
const { sourceMapCacheToObject } =
325-
require('internal/source_map/source_map_cache');
326-
327-
if (process.features.inspector) {
328-
internalBinding('profiler').setCoverageDirectory(coverageDirectory);
329-
internalBinding('profiler').setSourceMapCacheGetter(sourceMapCacheToObject);
330-
} else {
331-
process.emitWarning('The inspector is disabled, ' +
332-
'coverage could not be collected',
333-
'Warning');
334-
return '';
335-
}
336-
return coverageDirectory;
337-
}
338-
339322
function setupStacktracePrinterOnSigint() {
340323
if (!getOptionValue('--trace-sigint')) {
341324
return;

0 commit comments

Comments
 (0)