diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 8fa9c872568d1e..8756d02b206f71 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -193,18 +193,23 @@ class TestCoverage { ObjectAssign(range, mapRangeToLines(range, lines)); if (isBlockCoverage) { - ArrayPrototypePush(branchReports, { - __proto__: null, - line: range.lines[0]?.line, - count: range.count, - }); - - if (range.count !== 0 || - range.ignoredLines === range.lines.length) { + // Skip branches that are completely ignored + if (range.ignoredLines === range.lines.length) { + totalBranches++; branchesCovered++; + } else { + ArrayPrototypePush(branchReports, { + __proto__: null, + line: range.lines[0]?.line, + count: range.count, + }); + + if (range.count !== 0) { + branchesCovered++; + } + + totalBranches++; } - - totalBranches++; } } diff --git a/test/fixtures/test-runner/coverage-ignore-branch.js b/test/fixtures/test-runner/coverage-ignore-branch.js new file mode 100644 index 00000000000000..097dd38a4cd71f --- /dev/null +++ b/test/fixtures/test-runner/coverage-ignore-branch.js @@ -0,0 +1,13 @@ +// Test fixture for issue #61586 +// Tests that node:coverage ignore next also excludes BRDA entries + +function getValue(condition) { + if (condition) { + return 'truthy'; + } + /* node:coverage ignore next */ + return 'falsy'; +} + +// Call only the truthy branch +getValue(true); diff --git a/test/parallel/test-runner-coverage-ignore-branch.js b/test/parallel/test-runner-coverage-ignore-branch.js new file mode 100644 index 00000000000000..f6b8e776a5fb53 --- /dev/null +++ b/test/parallel/test-runner-coverage-ignore-branch.js @@ -0,0 +1,43 @@ +'use strict'; +const common = require('../common'); +const assert = require('node:assert'); +const { spawnSync } = require('node:child_process'); +const { test } = require('node:test'); +const fixtures = require('../common/fixtures'); +const skipIfNoInspector = { + skip: !process.features.inspector ? 'inspector disabled' : false +}; + +test('coverage ignore next excludes BRDA for ignored branches', skipIfNoInspector, async (t) => { + const fixture = fixtures.path('test-runner', 'coverage-ignore-branch.js'); + const args = [ + '--experimental-test-coverage', + '--test-reporter', 'lcov', + fixture, + ]; + const result = spawnSync(process.execPath, args); + const lcovOutput = result.stdout.toString(); + + // Parse the LCOV output + const lines = lcovOutput.split('\n'); + const brdaLines = lines.filter(l => l.startsWith('BRDA:')); + + // All branches should be covered (no uncovered branches) + // The branch leading to the ignored code should be excluded + const uncoveredBranches = brdaLines.filter(l => l.endsWith(',0')); + + assert.strictEqual(uncoveredBranches.length, 0, + `Expected no uncovered branches, but found: ${uncoveredBranches.join(', ')}`); + + // Verify branch coverage is 100% + const brfLine = lines.find(l => l.startsWith('BRF:')); + const brhLine = lines.find(l => l.startsWith('BRH:')); + assert(brfLine, 'Should have BRF line'); + assert(brhLine, 'Should have BRH line'); + + const brf = parseInt(brfLine.split(':')[1], 10); + const brh = parseInt(brhLine.split(':')[1], 10); + + assert.strictEqual(brf, brh, + `Expected branch coverage to be 100% (${brh}/${brf}), but found ${brh}/${brf}`); +});