Skip to content

Commit 59a4ee0

Browse files
committed
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.
1 parent 4830a6c commit 59a4ee0

File tree

12 files changed

+537
-31
lines changed

12 files changed

+537
-31
lines changed

doc/api/cli.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,16 @@ Starts the Node.js command line test runner. This flag cannot be combined with
12291229
See the documentation on [running tests from the command line][]
12301230
for more details.
12311231

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

12341244
<!-- YAML
@@ -2358,6 +2368,7 @@ done
23582368
[`unhandledRejection`]: process.md#event-unhandledrejection
23592369
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
23602370
[`worker_threads.threadId`]: worker_threads.md#workerthreadid
2371+
[collecting code coverage from tests]: test.md#collecting-code-coverage
23612372
[conditional exports]: packages.md#conditional-exports
23622373
[context-aware]: addons.md#context-aware-addons
23632374
[debugger]: debugger.md

doc/api/test.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,26 @@ 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+
The test runner's code coverage functionality has the following limitations,
382+
which will be addressed in a future Node.js release:
383+
384+
* Although coverage data is collected for child processes, this information is
385+
not included in the coverage report. Because the command line test runner uses
386+
child processes to execute test files, it cannot be used with `--test-coverage`.
387+
* Source maps are not supported.
388+
* Excluding specific files or directories from the coverage report is not
389+
supported.
390+
371391
## Mocking
372392

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

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

12241280
* `data` {Object}
@@ -1589,6 +1645,7 @@ added:
15891645

15901646
[TAP]: https://testanything.org/
15911647
[`--import`]: cli.md#--importmodule
1648+
[`--test-coverage`]: cli.md#--test-coverage
15921649
[`--test-name-pattern`]: cli.md#--test-name-pattern
15931650
[`--test-only`]: cli.md#--test-only
15941651
[`--test-reporter-destination`]: cli.md#--test-reporter-destination
@@ -1597,6 +1654,7 @@ added:
15971654
[`MockFunctionContext`]: #class-mockfunctioncontext
15981655
[`MockTracker.method`]: #mockmethodobject-methodname-implementation-options
15991656
[`MockTracker`]: #class-mocktracker
1657+
[`NODE_V8_COVERAGE`]: cli.md#node_v8_coveragedir
16001658
[`SuiteContext`]: #class-suitecontext
16011659
[`TestContext`]: #class-testcontext
16021660
[`context.diagnostic`]: #contextdiagnosticmessage
@@ -1607,4 +1665,5 @@ added:
16071665
[describe options]: #describename-options-fn
16081666
[it options]: #testname-options-fn
16091667
[stream.compose]: stream.md#streamcomposestreams
1668+
[test reporters]: #test-reporters
16101669
[test runner execution model]: #test-runner-execution-model

doc/node.1

Lines changed: 3 additions & 0 deletions
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

Lines changed: 13 additions & 30 deletions
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)