|
| 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; |
0 commit comments