| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -403,6 +403,18 @@ if (anAlwaysFalseCondition) { | |||
| 403 | 403 | } | |
| 404 | 404 | ``` | |
| 405 | 405 | ||
| 406 | + ### Coverage reporters | ||
| 407 | + | ||
| 408 | + The tap and spec reporters will print a summary of the coverage statistics. | ||
| 409 | + There is also an lcov reporter that will generate an lcov file which can be | ||
| 410 | + used as an in depth coverage report. | ||
| 411 | + | ||
| 412 | + ```bash | ||
| 413 | + node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info | ||
| 414 | + ``` | ||
| 415 | + | ||
| 416 | + ### Limitations | ||
| 417 | + | ||
| 406 | 418 | The test runner's code coverage functionality has the following limitations, | |
| 407 | 419 | which will be addressed in a future Node.js release: | |
| 408 | 420 | ||
@@ -850,6 +862,10 @@ The following built-reporters are supported: | |||
| 850 | 862 | * `junit` | |
| 851 | 863 | The junit reporter outputs test results in a jUnit XML format | |
| 852 | 864 | ||
| 865 | + * `lcov` | ||
| 866 | + The `lcov` reporter outputs test coverage when used with the | ||
| 867 | + [`--experimental-test-coverage`][] flag. | ||
| 868 | + | ||
| 853 | 869 | When `stdout` is a [TTY][], the `spec` reporter is used by default. | |
| 854 | 870 | Otherwise, the `tap` reporter is used by default. | |
| 855 | 871 | ||
@@ -861,11 +877,11 @@ to the test runner's output is required, use the events emitted by the | |||
| 861 | 877 | The reporters are available via the `node:test/reporters` module: | |
| 862 | 878 | ||
| 863 | 879 | ```mjs | |
| 864 | - import { tap, spec, dot, junit } from 'node:test/reporters'; | ||
| 880 | + import { tap, spec, dot, junit, lcov } from 'node:test/reporters'; | ||
| 865 | 881 | ``` | |
| 866 | 882 | ||
| 867 | 883 | ```cjs | |
| 868 | - const { tap, spec, dot, junit } = require('node:test/reporters'); | ||
| 884 | + const { tap, spec, dot, junit, lcov } = require('node:test/reporters'); | ||
| 869 | 885 | ``` | |
| 870 | 886 | ||
| 871 | 887 | ### Custom reporters | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,107 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const { relative } = require('path'); | ||
| 4 | + const Transform = require('internal/streams/transform'); | ||
| 5 | + | ||
| 6 | + // This reporter is based on the LCOV format, as described here: | ||
| 7 | + // https://ltp.sourceforge.net/coverage/lcov/geninfo.1.php | ||
| 8 | + // Excerpts from this documentation are included in the comments that make up | ||
| 9 | + // the _transform function below. | ||
| 10 | + class LcovReporter extends Transform { | ||
| 11 | + constructor(options) { | ||
| 12 | + super({ ...options, writableObjectMode: true, __proto__: null }); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + _transform(event, _encoding, callback) { | ||
| 16 | + if (event.type !== 'test:coverage') { | ||
| 17 | + return callback(null); | ||
| 18 | + } | ||
| 19 | + let lcov = ''; | ||
| 20 | + // A tracefile is made up of several human-readable lines of text, divided | ||
| 21 | + // into sections. If available, a tracefile begins with the testname which | ||
| 22 | + // is stored in the following format: | ||
| 23 | + // ## TN:\<test name\> | ||
| 24 | + lcov += 'TN:\n'; | ||
| 25 | + const { | ||
| 26 | + data: { | ||
| 27 | + summary: { workingDirectory }, | ||
| 28 | + }, | ||
| 29 | + } = event; | ||
| 30 | + try { | ||
| 31 | + for (let i = 0; i < event.data.summary.files.length; i++) { | ||
| 32 | + const file = event.data.summary.files[i]; | ||
| 33 | + // For each source file referenced in the .da file, there is a section | ||
| 34 | + // containing filename and coverage data: | ||
| 35 | + // ## SF:\<path to the source file\> | ||
| 36 | + lcov += `SF:${relative(workingDirectory, file.path)}\n`; | ||
| 37 | + | ||
| 38 | + // Following is a list of line numbers for each function name found in | ||
| 39 | + // the source file: | ||
| 40 | + // ## FN:\<line number of function start\>,\<function name\> | ||
| 41 | + // | ||
| 42 | + // After, there is a list of execution counts for each instrumented | ||
| 43 | + // function: | ||
| 44 | + // ## FNDA:\<execution count\>,\<function name\> | ||
| 45 | + // | ||
| 46 | + // This loop adds the FN lines to the lcov variable as it goes and | ||
| 47 | + // gathers the FNDA lines to be added later. This way we only loop | ||
| 48 | + // through the list of functions once. | ||
| 49 | + let fnda = ''; | ||
| 50 | + for (let j = 0; j < file.functions.length; j++) { | ||
| 51 | + const func = file.functions[j]; | ||
| 52 | + const name = func.name || `anonymous_${j}`; | ||
| 53 | + lcov += `FN:${func.line},${name}\n`; | ||
| 54 | + fnda += `FNDA:${func.count},${name}\n`; | ||
| 55 | + } | ||
| 56 | + lcov += fnda; | ||
| 57 | + | ||
| 58 | + // This list is followed by two lines containing the number of | ||
| 59 | + // functions found and hit: | ||
| 60 | + // ## FNF:\<number of functions found\> | ||
| 61 | + // ## FNH:\<number of function hit\> | ||
| 62 | + lcov += `FNF:${file.totalFunctionCount}\n`; | ||
| 63 | + lcov += `FNH:${file.coveredFunctionCount}\n`; | ||
| 64 | + | ||
| 65 | + // Branch coverage information is stored which one line per branch: | ||
| 66 | + // ## BRDA:\<line number\>,\<block number\>,\<branch number\>,\<taken\> | ||
| 67 | + // Block number and branch number are gcc internal IDs for the branch. | ||
| 68 | + // Taken is either '-' if the basic block containing the branch was | ||
| 69 | + // never executed or a number indicating how often that branch was | ||
| 70 | + // taken. | ||
| 71 | + for (let j = 0; j < file.branches.length; j++) { | ||
| 72 | + lcov += `BRDA:${file.branches[j].line},${j},0,${file.branches[j].count}\n`; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + // Branch coverage summaries are stored in two lines: | ||
| 76 | + // ## BRF:\<number of branches found\> | ||
| 77 | + // ## BRH:\<number of branches hit\> | ||
| 78 | + lcov += `BRF:${file.totalBranchCount}\n`; | ||
| 79 | + lcov += `BRH:${file.coveredBranchCount}\n`; | ||
| 80 | + | ||
| 81 | + // Then there is a list of execution counts for each instrumented line | ||
| 82 | + // (i.e. a line which resulted in executable code): | ||
| 83 | + // ## DA:\<line number\>,\<execution count\>[,\<checksum\>] | ||
| 84 | + const sortedLines = file.lines.toSorted((a, b) => a.line - b.line); | ||
| 85 | + for (let j = 0; j < sortedLines.length; j++) { | ||
| 86 | + lcov += `DA:${sortedLines[j].line},${sortedLines[j].count}\n`; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + // At the end of a section, there is a summary about how many lines | ||
| 90 | + // were found and how many were actually instrumented: | ||
| 91 | + // ## LH:\<number of lines with a non-zero execution count\> | ||
| 92 | + // ## LF:\<number of instrumented lines\> | ||
| 93 | + lcov += `LH:${file.coveredLineCount}\n`; | ||
| 94 | + lcov += `LF:${file.totalLineCount}\n`; | ||
| 95 | + | ||
| 96 | + // Each sections ends with: | ||
| 97 | + // end_of_record | ||
| 98 | + lcov += 'end_of_record\n'; | ||
| 99 | + } | ||
| 100 | + } catch (error) { | ||
| 101 | + return callback(error); | ||
| 102 | + } | ||
| 103 | + return callback(null, lcov); | ||
| 104 | + } | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + module.exports = LcovReporter; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -112,6 +112,7 @@ const kBuiltinReporters = new SafeMap([ | |||
| 112 | 112 | ['dot', 'internal/test_runner/reporter/dot'], | |
| 113 | 113 | ['tap', 'internal/test_runner/reporter/tap'], | |
| 114 | 114 | ['junit', 'internal/test_runner/reporter/junit'], | |
| 115 | + ['lcov', 'internal/test_runner/reporter/lcov'], | ||
| 115 | 116 | ]); | |
| 116 | 117 | ||
| 117 | 118 | const kDefaultReporter = process.stdout.isTTY ? 'spec' : 'tap'; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,7 @@ let dot; | |||
| 6 | 6 | let junit; | |
| 7 | 7 | let spec; | |
| 8 | 8 | let tap; | |
| 9 | + let lcov; | ||
| 9 | 10 | ||
| 10 | 11 | ObjectDefineProperties(module.exports, { | |
| 11 | 12 | __proto__: null, | |
@@ -45,4 +46,13 @@ ObjectDefineProperties(module.exports, { | |||
| 45 | 46 | return tap; | |
| 46 | 47 | }, | |
| 47 | 48 | }, | |
| 49 | + lcov: { | ||
| 50 | + __proto__: null, | ||
| 51 | + configurable: true, | ||
| 52 | + enumerable: true, | ||
| 53 | + get() { | ||
| 54 | + lcov ??= require('internal/test_runner/reporter/lcov'); | ||
| 55 | + return ReflectConstruct(lcov, arguments); | ||
| 56 | + }, | ||
| 57 | + }, | ||
| 48 | 58 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + require('../../../common'); | ||
| 3 | + const fixtures = require('../../../common/fixtures'); | ||
| 4 | + const spawn = require('node:child_process').spawn; | ||
| 5 | + | ||
| 6 | + spawn(process.execPath, | ||
| 7 | + ['--no-warnings', '--experimental-test-coverage', '--test-reporter', 'lcov', fixtures.path('test-runner/output/output.js')], { stdio: 'inherit' }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments