diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 798e41f0ac4be6..a682d37a99044b 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -263,14 +263,16 @@ class TestCoverage { ObjectAssign(range, mapRangeToLines(range, lines)); if (isBlockCoverage) { + const ignored = range.ignoredLines === range.lines.length; + ArrayPrototypePush(branchReports, { __proto__: null, line: range.lines[0]?.line, count: range.count, + ignored, }); - if (range.count !== 0 || - range.ignoredLines === range.lines.length) { + if (range.count !== 0 || ignored) { branchesCovered++; } diff --git a/lib/internal/test_runner/reporter/lcov.js b/lib/internal/test_runner/reporter/lcov.js index 698913d79dec02..3c56fcd00c67e4 100644 --- a/lib/internal/test_runner/reporter/lcov.js +++ b/lib/internal/test_runner/reporter/lcov.js @@ -34,6 +34,7 @@ class LcovReporter extends Transform { // containing filename and coverage data: // ## SF:\ lcov += `SF:${relative(workingDirectory, file.path)}\n`; + const branches = file.branches.filter((branch) => !branch.ignored); // Following is a list of line numbers for each function name found in // the source file: @@ -68,15 +69,15 @@ class LcovReporter extends Transform { // 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`; + for (let j = 0; j < branches.length; j++) { + lcov += `BRDA:${branches[j].line},${j},0,${branches[j].count}\n`; } // Branch coverage summaries are stored in two lines: // ## BRF:\ // ## BRH:\ - lcov += `BRF:${file.totalBranchCount}\n`; - lcov += `BRH:${file.coveredBranchCount}\n`; + lcov += `BRF:${branches.length}\n`; + lcov += `BRH:${branches.filter((branch) => branch.count !== 0).length}\n`; // Then there is a list of execution counts for each instrumented line // (i.e. a line which resulted in executable code): diff --git a/test/parallel/test-runner-coverage.js b/test/parallel/test-runner-coverage.js index 5a8f3d743538cb..dbcb8d515ffad8 100644 --- a/test/parallel/test-runner-coverage.js +++ b/test/parallel/test-runner-coverage.js @@ -310,9 +310,11 @@ test('coverage reports on lines, functions, and branches', skipIfNoInspector, as await t.test('reports on branch coverage', () => { const uncalledBranch = file.branches.find((b) => b.line === 6); assert.strictEqual(uncalledBranch.count, 0); + assert.strictEqual(uncalledBranch.ignored, true); const calledTwice = file.branches.find((b) => b.line === 35); assert.strictEqual(calledTwice.count, 2); + assert.strictEqual(calledTwice.ignored, false); }); await t.test('reports on line coverage', () => { diff --git a/test/parallel/test-runner-reporters.js b/test/parallel/test-runner-reporters.js index 50a47578a1da7e..a9acf45f9877ae 100644 --- a/test/parallel/test-runner-reporters.js +++ b/test/parallel/test-runner-reporters.js @@ -4,9 +4,11 @@ require('../common'); const fixtures = require('../common/fixtures'); const tmpdir = require('../common/tmpdir'); const { describe, it } = require('node:test'); +const { once } = require('node:events'); const { spawnSync } = require('node:child_process'); const assert = require('node:assert'); const fs = require('node:fs'); +const LcovReporter = require('internal/test_runner/reporter/lcov'); const testFile = fixtures.path('test-runner/reporters.js'); tmpdir.refresh(); @@ -105,6 +107,46 @@ describe('node:test reporters', { concurrency: true }, () => { assert.match(file2Contents, /✖ failing/); }); + it('should omit ignored branches from lcov output', async () => { + const reporter = new LcovReporter(); + const chunks = []; + reporter.setEncoding('utf8'); + reporter.on('data', common.mustCallAtLeast((chunk) => { + chunks.push(chunk); + })); + + reporter.end({ + type: 'test:coverage', + data: { + summary: { + workingDirectory: '/tmp/project', + files: [{ + path: '/tmp/project/file.js', + functions: [], + totalFunctionCount: 0, + coveredFunctionCount: 0, + branches: [ + { line: 1, count: 0, ignored: true }, + { line: 2, count: 1, ignored: false }, + ], + totalBranchCount: 2, + coveredBranchCount: 1, + lines: [], + totalLineCount: 0, + coveredLineCount: 0, + }], + }, + }, + }); + + await once(reporter, 'finish'); + const output = chunks.join(''); + assert.match(output, /BRDA:2,0,0,1/); + assert(!output.includes('BRDA:1,0,0,0')); + assert.match(output, /BRF:1/); + assert.match(output, /BRH:1/); + }); + ['js', 'cjs', 'mjs'].forEach((ext) => { it(`should support a '${ext}' file as a custom reporter`, async () => { const filename = `custom.${ext}`;