diff --git a/doc/api/test.md b/doc/api/test.md index 715086cafd475a..b4a5b7e6ca5bb3 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -403,6 +403,18 @@ if (anAlwaysFalseCondition) { } ``` +### Coverage reporters + +The tap and spec reporters will print a summary of the coverage statistics. +There is also an lcov reporter that will generate an lcov file which can be +used as an in depth coverage report. + +```bash +node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info +``` + +### Limitations + The test runner's code coverage functionality has the following limitations, which will be addressed in a future Node.js release: @@ -850,6 +862,10 @@ The following built-reporters are supported: * `junit` The junit reporter outputs test results in a jUnit XML format +* `lcov` + The `lcov` reporter outputs test coverage when used with the + [`--experimental-test-coverage`][] flag. + When `stdout` is a [TTY][], the `spec` reporter is used by default. Otherwise, the `tap` reporter is used by default. @@ -861,11 +877,11 @@ to the test runner's output is required, use the events emitted by the The reporters are available via the `node:test/reporters` module: ```mjs -import { tap, spec, dot, junit } from 'node:test/reporters'; +import { tap, spec, dot, junit, lcov } from 'node:test/reporters'; ``` ```cjs -const { tap, spec, dot, junit } = require('node:test/reporters'); +const { tap, spec, dot, junit, lcov } = require('node:test/reporters'); ``` ### Custom reporters diff --git a/lib/internal/test_runner/reporter/lcov.js b/lib/internal/test_runner/reporter/lcov.js new file mode 100644 index 00000000000000..698913d79dec02 --- /dev/null +++ b/lib/internal/test_runner/reporter/lcov.js @@ -0,0 +1,107 @@ +'use strict'; + +const { relative } = require('path'); +const Transform = require('internal/streams/transform'); + +// This reporter is based on the LCOV format, as described here: +// https://ltp.sourceforge.net/coverage/lcov/geninfo.1.php +// Excerpts from this documentation are included in the comments that make up +// the _transform function below. +class LcovReporter extends Transform { + constructor(options) { + super({ ...options, writableObjectMode: true, __proto__: null }); + } + + _transform(event, _encoding, callback) { + if (event.type !== 'test:coverage') { + return callback(null); + } + let lcov = ''; + // A tracefile is made up of several human-readable lines of text, divided + // into sections. If available, a tracefile begins with the testname which + // is stored in the following format: + // ## TN:\ + lcov += 'TN:\n'; + const { + data: { + summary: { workingDirectory }, + }, + } = event; + try { + for (let i = 0; i < event.data.summary.files.length; i++) { + const file = event.data.summary.files[i]; + // For each source file referenced in the .da file, there is a section + // containing filename and coverage data: + // ## SF:\ + lcov += `SF:${relative(workingDirectory, file.path)}\n`; + + // Following is a list of line numbers for each function name found in + // the source file: + // ## FN:\,\ + // + // After, there is a list of execution counts for each instrumented + // function: + // ## FNDA:\,\ + // + // This loop adds the FN lines to the lcov variable as it goes and + // gathers the FNDA lines to be added later. This way we only loop + // through the list of functions once. + let fnda = ''; + for (let j = 0; j < file.functions.length; j++) { + const func = file.functions[j]; + const name = func.name || `anonymous_${j}`; + lcov += `FN:${func.line},${name}\n`; + fnda += `FNDA:${func.count},${name}\n`; + } + lcov += fnda; + + // This list is followed by two lines containing the number of + // functions found and hit: + // ## FNF:\ + // ## FNH:\ + lcov += `FNF:${file.totalFunctionCount}\n`; + lcov += `FNH:${file.coveredFunctionCount}\n`; + + // Branch coverage information is stored which one line per branch: + // ## BRDA:\,\,\,\ + // Block number and branch number are gcc internal IDs for the branch. + // Taken is either '-' if the basic block containing the branch was + // never executed or a number indicating how often that branch was + // taken. + for (let j = 0; j < file.branches.length; j++) { + lcov += `BRDA:${file.branches[j].line},${j},0,${file.branches[j].count}\n`; + } + + // Branch coverage summaries are stored in two lines: + // ## BRF:\ + // ## BRH:\ + lcov += `BRF:${file.totalBranchCount}\n`; + lcov += `BRH:${file.coveredBranchCount}\n`; + + // Then there is a list of execution counts for each instrumented line + // (i.e. a line which resulted in executable code): + // ## DA:\,\[,\] + const sortedLines = file.lines.toSorted((a, b) => a.line - b.line); + for (let j = 0; j < sortedLines.length; j++) { + lcov += `DA:${sortedLines[j].line},${sortedLines[j].count}\n`; + } + + // At the end of a section, there is a summary about how many lines + // were found and how many were actually instrumented: + // ## LH:\ + // ## LF:\ + lcov += `LH:${file.coveredLineCount}\n`; + lcov += `LF:${file.totalLineCount}\n`; + + // Each sections ends with: + // end_of_record + lcov += 'end_of_record\n'; + } + } catch (error) { + return callback(error); + } + return callback(null, lcov); + } +} + +module.exports = LcovReporter; diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index b70ca649c8b8df..6b4663f14302c3 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -112,6 +112,7 @@ const kBuiltinReporters = new SafeMap([ ['dot', 'internal/test_runner/reporter/dot'], ['tap', 'internal/test_runner/reporter/tap'], ['junit', 'internal/test_runner/reporter/junit'], + ['lcov', 'internal/test_runner/reporter/lcov'], ]); const kDefaultReporter = process.stdout.isTTY ? 'spec' : 'tap'; diff --git a/lib/test/reporters.js b/lib/test/reporters.js index 06a0b27ee58275..6316074e6e64aa 100644 --- a/lib/test/reporters.js +++ b/lib/test/reporters.js @@ -6,6 +6,7 @@ let dot; let junit; let spec; let tap; +let lcov; ObjectDefineProperties(module.exports, { __proto__: null, @@ -45,4 +46,13 @@ ObjectDefineProperties(module.exports, { return tap; }, }, + lcov: { + __proto__: null, + configurable: true, + enumerable: true, + get() { + lcov ??= require('internal/test_runner/reporter/lcov'); + return ReflectConstruct(lcov, arguments); + }, + }, }); diff --git a/test/fixtures/test-runner/output/lcov_reporter.js b/test/fixtures/test-runner/output/lcov_reporter.js new file mode 100644 index 00000000000000..a6d17432d18c23 --- /dev/null +++ b/test/fixtures/test-runner/output/lcov_reporter.js @@ -0,0 +1,7 @@ +'use strict'; +require('../../../common'); +const fixtures = require('../../../common/fixtures'); +const spawn = require('node:child_process').spawn; + +spawn(process.execPath, + ['--no-warnings', '--experimental-test-coverage', '--test-reporter', 'lcov', fixtures.path('test-runner/output/output.js')], { stdio: 'inherit' }); diff --git a/test/fixtures/test-runner/output/lcov_reporter.snapshot b/test/fixtures/test-runner/output/lcov_reporter.snapshot new file mode 100644 index 00000000000000..c14f4c7ce2baee --- /dev/null +++ b/test/fixtures/test-runner/output/lcov_reporter.snapshot @@ -0,0 +1,699 @@ +TN: +SF:test/fixtures/test-runner/output/output.js +FN:8,anonymous_0 +FN:12,anonymous_1 +FN:16,anonymous_2 +FN:21,anonymous_3 +FN:26,anonymous_4 +FN:30,anonymous_5 +FN:34,anonymous_6 +FN:38,anonymous_7 +FN:42,anonymous_8 +FN:46,anonymous_9 +FN:50,anonymous_10 +FN:54,anonymous_11 +FN:59,anonymous_12 +FN:64,anonymous_13 +FN:68,anonymous_14 +FN:72,anonymous_15 +FN:76,anonymous_16 +FN:80,anonymous_17 +FN:81,anonymous_18 +FN:86,anonymous_19 +FN:87,anonymous_20 +FN:92,anonymous_21 +FN:93,anonymous_22 +FN:94,anonymous_23 +FN:100,anonymous_24 +FN:101,anonymous_25 +FN:107,anonymous_26 +FN:111,anonymous_27 +FN:112,anonymous_28 +FN:113,anonymous_29 +FN:114,anonymous_30 +FN:122,anonymous_31 +FN:123,anonymous_32 +FN:130,anonymous_33 +FN:131,anonymous_34 +FN:132,anonymous_35 +FN:140,anonymous_36 +FN:141,anonymous_37 +FN:142,anonymous_38 +FN:150,anonymous_39 +FN:151,anonymous_40 +FN:159,anonymous_41 +FN:160,anonymous_42 +FN:161,anonymous_43 +FN:166,anonymous_44 +FN:167,anonymous_45 +FN:171,anonymous_46 +FN:172,anonymous_47 +FN:173,anonymous_48 +FN:179,anonymous_49 +FN:183,anonymous_50 +FN:187,anonymous_51 +FN:195,functionOnly +FN:198,anonymous_53 +FN:213,functionAndOptions +FN:215,anonymous_55 +FN:219,anonymous_56 +FN:220,anonymous_57 +FN:225,anonymous_58 +FN:229,anonymous_59 +FN:233,anonymous_60 +FN:238,anonymous_61 +FN:242,anonymous_62 +FN:246,anonymous_63 +FN:251,anonymous_64 +FN:256,anonymous_65 +FN:257,anonymous_66 +FN:263,anonymous_67 +FN:264,anonymous_68 +FN:269,anonymous_69 +FN:270,anonymous_70 +FN:277,anonymous_71 +FN:287,anonymous_72 +FN:289,obj +FN:298,anonymous_74 +FN:300,obj +FN:309,anonymous_76 +FN:310,anonymous_77 +FN:313,anonymous_78 +FN:318,anonymous_79 +FN:319,anonymous_80 +FN:324,anonymous_81 +FN:329,anonymous_82 +FN:330,anonymous_83 +FN:335,anonymous_84 +FN:339,anonymous_85 +FN:342,get then +FN:345,anonymous_87 +FN:350,anonymous_88 +FN:353,get then +FN:356,anonymous_90 +FN:361,anonymous_91 +FN:362,anonymous_92 +FN:363,anonymous_93 +FN:367,anonymous_94 +FN:368,anonymous_95 +FN:369,anonymous_96 +FN:375,anonymous_97 +FN:379,anonymous_98 +FNDA:1,anonymous_0 +FNDA:1,anonymous_1 +FNDA:1,anonymous_2 +FNDA:1,anonymous_3 +FNDA:1,anonymous_4 +FNDA:1,anonymous_5 +FNDA:1,anonymous_6 +FNDA:1,anonymous_7 +FNDA:1,anonymous_8 +FNDA:1,anonymous_9 +FNDA:1,anonymous_10 +FNDA:1,anonymous_11 +FNDA:1,anonymous_12 +FNDA:1,anonymous_13 +FNDA:1,anonymous_14 +FNDA:1,anonymous_15 +FNDA:1,anonymous_16 +FNDA:1,anonymous_17 +FNDA:1,anonymous_18 +FNDA:1,anonymous_19 +FNDA:1,anonymous_20 +FNDA:1,anonymous_21 +FNDA:1,anonymous_22 +FNDA:1,anonymous_23 +FNDA:1,anonymous_24 +FNDA:1,anonymous_25 +FNDA:1,anonymous_26 +FNDA:1,anonymous_27 +FNDA:1,anonymous_28 +FNDA:1,anonymous_29 +FNDA:1,anonymous_30 +FNDA:1,anonymous_31 +FNDA:1,anonymous_32 +FNDA:1,anonymous_33 +FNDA:1,anonymous_34 +FNDA:1,anonymous_35 +FNDA:1,anonymous_36 +FNDA:1,anonymous_37 +FNDA:1,anonymous_38 +FNDA:1,anonymous_39 +FNDA:1,anonymous_40 +FNDA:1,anonymous_41 +FNDA:1,anonymous_42 +FNDA:1,anonymous_43 +FNDA:1,anonymous_44 +FNDA:1,anonymous_45 +FNDA:1,anonymous_46 +FNDA:1,anonymous_47 +FNDA:1,anonymous_48 +FNDA:0,anonymous_49 +FNDA:0,anonymous_50 +FNDA:1,anonymous_51 +FNDA:1,functionOnly +FNDA:1,anonymous_53 +FNDA:0,functionAndOptions +FNDA:1,anonymous_55 +FNDA:1,anonymous_56 +FNDA:1,anonymous_57 +FNDA:1,anonymous_58 +FNDA:1,anonymous_59 +FNDA:1,anonymous_60 +FNDA:1,anonymous_61 +FNDA:1,anonymous_62 +FNDA:1,anonymous_63 +FNDA:1,anonymous_64 +FNDA:1,anonymous_65 +FNDA:1,anonymous_66 +FNDA:1,anonymous_67 +FNDA:1,anonymous_68 +FNDA:1,anonymous_69 +FNDA:1,anonymous_70 +FNDA:1,anonymous_71 +FNDA:1,anonymous_72 +FNDA:1,obj +FNDA:1,anonymous_74 +FNDA:1,obj +FNDA:1,anonymous_76 +FNDA:1,anonymous_77 +FNDA:1,anonymous_78 +FNDA:1,anonymous_79 +FNDA:1,anonymous_80 +FNDA:1,anonymous_81 +FNDA:1,anonymous_82 +FNDA:1,anonymous_83 +FNDA:1,anonymous_84 +FNDA:1,anonymous_85 +FNDA:1,get then +FNDA:1,anonymous_87 +FNDA:1,anonymous_88 +FNDA:1,get then +FNDA:1,anonymous_90 +FNDA:1,anonymous_91 +FNDA:1,anonymous_92 +FNDA:1,anonymous_93 +FNDA:1,anonymous_94 +FNDA:1,anonymous_95 +FNDA:1,anonymous_96 +FNDA:1,anonymous_97 +FNDA:1,anonymous_98 +FNF:99 +FNH:96 +BRDA:1,0,0,1 +BRDA:8,1,0,1 +BRDA:12,2,0,1 +BRDA:16,3,0,1 +BRDA:21,4,0,1 +BRDA:26,5,0,1 +BRDA:30,6,0,1 +BRDA:34,7,0,1 +BRDA:38,8,0,1 +BRDA:42,9,0,1 +BRDA:46,10,0,1 +BRDA:50,11,0,1 +BRDA:54,12,0,1 +BRDA:59,13,0,1 +BRDA:64,14,0,1 +BRDA:68,15,0,1 +BRDA:72,16,0,1 +BRDA:76,17,0,1 +BRDA:80,18,0,1 +BRDA:81,19,0,1 +BRDA:86,20,0,1 +BRDA:87,21,0,1 +BRDA:92,22,0,1 +BRDA:93,23,0,1 +BRDA:94,24,0,1 +BRDA:100,25,0,1 +BRDA:101,26,0,1 +BRDA:107,27,0,1 +BRDA:111,28,0,1 +BRDA:112,29,0,1 +BRDA:113,30,0,1 +BRDA:114,31,0,1 +BRDA:122,32,0,1 +BRDA:123,33,0,1 +BRDA:130,34,0,1 +BRDA:131,35,0,1 +BRDA:132,36,0,1 +BRDA:140,37,0,1 +BRDA:141,38,0,1 +BRDA:142,39,0,1 +BRDA:150,40,0,1 +BRDA:151,41,0,1 +BRDA:159,42,0,1 +BRDA:160,43,0,1 +BRDA:161,44,0,1 +BRDA:166,45,0,1 +BRDA:167,46,0,1 +BRDA:171,47,0,1 +BRDA:172,48,0,1 +BRDA:173,49,0,1 +BRDA:187,50,0,1 +BRDA:195,51,0,1 +BRDA:198,52,0,1 +BRDA:215,53,0,1 +BRDA:219,54,0,1 +BRDA:220,55,0,1 +BRDA:225,56,0,1 +BRDA:229,57,0,1 +BRDA:233,58,0,1 +BRDA:238,59,0,1 +BRDA:242,60,0,1 +BRDA:246,61,0,1 +BRDA:251,62,0,1 +BRDA:256,63,0,1 +BRDA:257,64,0,1 +BRDA:263,65,0,1 +BRDA:264,66,0,1 +BRDA:269,67,0,1 +BRDA:270,68,0,1 +BRDA:277,69,0,1 +BRDA:287,70,0,1 +BRDA:289,71,0,1 +BRDA:298,72,0,1 +BRDA:300,73,0,1 +BRDA:309,74,0,1 +BRDA:310,75,0,1 +BRDA:313,76,0,1 +BRDA:318,77,0,1 +BRDA:319,78,0,1 +BRDA:324,79,0,1 +BRDA:329,80,0,1 +BRDA:330,81,0,1 +BRDA:335,82,0,1 +BRDA:339,83,0,1 +BRDA:342,84,0,1 +BRDA:343,85,0,0 +BRDA:345,86,0,1 +BRDA:350,87,0,1 +BRDA:353,88,0,1 +BRDA:354,89,0,0 +BRDA:356,90,0,1 +BRDA:361,91,0,1 +BRDA:364,92,0,0 +BRDA:362,93,0,1 +BRDA:363,94,0,1 +BRDA:367,95,0,1 +BRDA:370,96,0,0 +BRDA:368,97,0,1 +BRDA:369,98,0,1 +BRDA:375,99,0,1 +BRDA:379,100,0,1 +BRF:101 +BRH:97 +DA:1,1 +DA:2,1 +DA:3,1 +DA:4,1 +DA:5,1 +DA:6,1 +DA:7,1 +DA:8,1 +DA:9,1 +DA:10,1 +DA:11,1 +DA:12,1 +DA:13,1 +DA:14,1 +DA:15,1 +DA:16,1 +DA:17,1 +DA:18,1 +DA:19,1 +DA:20,1 +DA:21,1 +DA:22,1 +DA:23,1 +DA:24,1 +DA:25,1 +DA:26,1 +DA:27,1 +DA:28,1 +DA:29,1 +DA:30,1 +DA:31,1 +DA:32,1 +DA:33,1 +DA:34,1 +DA:35,1 +DA:36,1 +DA:37,1 +DA:38,1 +DA:39,1 +DA:40,1 +DA:41,1 +DA:42,1 +DA:43,1 +DA:44,1 +DA:45,1 +DA:46,1 +DA:47,1 +DA:48,1 +DA:49,1 +DA:50,1 +DA:51,1 +DA:52,1 +DA:53,1 +DA:54,1 +DA:55,1 +DA:56,1 +DA:57,1 +DA:58,1 +DA:59,1 +DA:60,1 +DA:61,1 +DA:62,1 +DA:63,1 +DA:64,1 +DA:65,1 +DA:66,1 +DA:67,1 +DA:68,1 +DA:69,1 +DA:70,1 +DA:71,1 +DA:72,1 +DA:73,1 +DA:74,1 +DA:75,1 +DA:76,1 +DA:77,1 +DA:78,1 +DA:79,1 +DA:80,1 +DA:81,1 +DA:82,1 +DA:83,1 +DA:84,1 +DA:85,1 +DA:86,1 +DA:87,1 +DA:88,1 +DA:89,1 +DA:90,1 +DA:91,1 +DA:92,1 +DA:93,1 +DA:94,1 +DA:95,1 +DA:96,1 +DA:97,1 +DA:98,1 +DA:99,1 +DA:100,1 +DA:101,1 +DA:102,1 +DA:103,1 +DA:104,1 +DA:105,1 +DA:106,1 +DA:107,1 +DA:108,1 +DA:109,1 +DA:110,1 +DA:111,1 +DA:112,1 +DA:113,1 +DA:114,1 +DA:115,1 +DA:116,1 +DA:117,1 +DA:118,1 +DA:119,1 +DA:120,1 +DA:121,1 +DA:122,1 +DA:123,1 +DA:124,1 +DA:125,1 +DA:126,1 +DA:127,1 +DA:128,1 +DA:129,1 +DA:130,1 +DA:131,1 +DA:132,1 +DA:133,1 +DA:134,1 +DA:135,1 +DA:136,1 +DA:137,1 +DA:138,1 +DA:139,1 +DA:140,1 +DA:141,1 +DA:142,1 +DA:143,1 +DA:144,1 +DA:145,1 +DA:146,1 +DA:147,1 +DA:148,1 +DA:149,1 +DA:150,1 +DA:151,1 +DA:152,1 +DA:153,1 +DA:154,1 +DA:155,1 +DA:156,1 +DA:157,1 +DA:158,1 +DA:159,1 +DA:160,1 +DA:161,1 +DA:162,1 +DA:163,1 +DA:164,1 +DA:165,1 +DA:166,1 +DA:167,1 +DA:168,1 +DA:169,1 +DA:170,1 +DA:171,1 +DA:172,1 +DA:173,1 +DA:174,1 +DA:175,1 +DA:176,1 +DA:177,1 +DA:178,1 +DA:179,1 +DA:180,0 +DA:181,1 +DA:182,1 +DA:183,1 +DA:184,0 +DA:185,1 +DA:186,1 +DA:187,1 +DA:188,1 +DA:189,1 +DA:190,1 +DA:191,1 +DA:192,1 +DA:193,1 +DA:194,1 +DA:195,1 +DA:196,1 +DA:197,1 +DA:198,1 +DA:199,1 +DA:200,1 +DA:201,1 +DA:202,1 +DA:203,1 +DA:204,1 +DA:205,1 +DA:206,1 +DA:207,1 +DA:208,1 +DA:209,1 +DA:210,1 +DA:211,1 +DA:212,1 +DA:213,1 +DA:214,1 +DA:215,1 +DA:216,1 +DA:217,1 +DA:218,1 +DA:219,1 +DA:220,1 +DA:221,1 +DA:222,1 +DA:223,1 +DA:224,1 +DA:225,1 +DA:226,1 +DA:227,1 +DA:228,1 +DA:229,1 +DA:230,1 +DA:231,1 +DA:232,1 +DA:233,1 +DA:234,1 +DA:235,1 +DA:236,1 +DA:237,1 +DA:238,1 +DA:239,1 +DA:240,1 +DA:241,1 +DA:242,1 +DA:243,1 +DA:244,1 +DA:245,1 +DA:246,1 +DA:247,1 +DA:248,1 +DA:249,1 +DA:250,1 +DA:251,1 +DA:252,1 +DA:253,1 +DA:254,1 +DA:255,1 +DA:256,1 +DA:257,1 +DA:258,1 +DA:259,1 +DA:260,1 +DA:261,1 +DA:262,1 +DA:263,1 +DA:264,1 +DA:265,1 +DA:266,1 +DA:267,1 +DA:268,1 +DA:269,1 +DA:270,1 +DA:271,1 +DA:272,1 +DA:273,1 +DA:274,1 +DA:275,1 +DA:276,1 +DA:277,1 +DA:278,1 +DA:279,1 +DA:280,1 +DA:281,1 +DA:282,1 +DA:283,1 +DA:284,1 +DA:285,1 +DA:286,1 +DA:287,1 +DA:288,1 +DA:289,1 +DA:290,1 +DA:291,1 +DA:292,1 +DA:293,1 +DA:294,1 +DA:295,1 +DA:296,1 +DA:297,1 +DA:298,1 +DA:299,1 +DA:300,1 +DA:301,1 +DA:302,1 +DA:303,1 +DA:304,1 +DA:305,1 +DA:306,1 +DA:307,1 +DA:308,1 +DA:309,1 +DA:310,1 +DA:311,1 +DA:312,1 +DA:313,1 +DA:314,1 +DA:315,1 +DA:316,1 +DA:317,1 +DA:318,1 +DA:319,1 +DA:320,1 +DA:321,1 +DA:322,1 +DA:323,1 +DA:324,1 +DA:325,1 +DA:326,1 +DA:327,1 +DA:328,1 +DA:329,1 +DA:330,1 +DA:331,1 +DA:332,1 +DA:333,1 +DA:334,1 +DA:335,1 +DA:336,1 +DA:337,1 +DA:338,1 +DA:339,1 +DA:340,1 +DA:341,1 +DA:342,1 +DA:343,1 +DA:344,1 +DA:345,1 +DA:346,1 +DA:347,1 +DA:348,1 +DA:349,1 +DA:350,1 +DA:351,1 +DA:352,1 +DA:353,1 +DA:354,1 +DA:355,1 +DA:356,1 +DA:357,1 +DA:358,1 +DA:359,1 +DA:360,1 +DA:361,1 +DA:362,1 +DA:363,1 +DA:364,1 +DA:365,1 +DA:366,1 +DA:367,1 +DA:368,1 +DA:369,1 +DA:370,1 +DA:371,1 +DA:372,1 +DA:373,1 +DA:374,1 +DA:375,1 +DA:376,1 +DA:377,1 +DA:378,1 +DA:379,1 +DA:380,1 +DA:381,1 +DA:382,1 +DA:383,1 +DA:384,1 +DA:385,1 +DA:386,1 +DA:387,1 +DA:388,1 +DA:389,1 +DA:390,1 +DA:391,1 +LH:389 +LF:391 +end_of_record diff --git a/test/parallel/test-runner-output.mjs b/test/parallel/test-runner-output.mjs index 372ca8f3bae0ff..e2fa99e17cf8c8 100644 --- a/test/parallel/test-runner-output.mjs +++ b/test/parallel/test-runner-output.mjs @@ -40,6 +40,23 @@ function replaceTestLocationLine(str) { return str.replaceAll(/(js:)(\d+)(:\d+)/g, '$1(LINE)$3'); } +// The Node test coverage returns results for all files called by the test. This +// will make the output file change if files like test/common/index.js change. +// This transform picks only the first line and then the lines from the test +// file. +function pickTestFileFromLcov(str) { + const lines = str.split(/\n/); + const firstLineOfTestFile = lines.findIndex( + (line) => line.startsWith('SF:') && line.trim().endsWith('output.js') + ); + const lastLineOfTestFile = lines.findIndex( + (line, index) => index > firstLineOfTestFile && line.trim() === 'end_of_record' + ); + return ( + lines[0] + '\n' + lines.slice(firstLineOfTestFile, lastLineOfTestFile + 1).join('\n') + '\n' + ); +} + const defaultTransform = snapshot.transform( snapshot.replaceWindowsLineEndings, snapshot.replaceStackTrace, @@ -59,6 +76,14 @@ const junitTransform = snapshot.transform( snapshot.replaceWindowsLineEndings, snapshot.replaceStackTrace, ); +const lcovTransform = snapshot.transform( + snapshot.replaceWindowsLineEndings, + snapshot.replaceStackTrace, + snapshot.replaceFullPaths, + snapshot.replaceWindowsPaths, + pickTestFileFromLcov +); + const tests = [ { name: 'test-runner/output/abort.js' }, @@ -80,6 +105,7 @@ const tests = [ { name: 'test-runner/output/spec_reporter_successful.js', transform: specTransform }, { name: 'test-runner/output/spec_reporter.js', transform: specTransform }, { name: 'test-runner/output/spec_reporter_cli.js', transform: specTransform }, + process.features.inspector ? { name: 'test-runner/output/lcov_reporter.js', transform: lcovTransform } : false, { name: 'test-runner/output/output.js' }, { name: 'test-runner/output/output_cli.js' }, { name: 'test-runner/output/name_pattern.js' },