diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 8fa9c872568d1e..7cd57509857fd9 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -197,6 +197,7 @@ class TestCoverage { __proto__: null, line: range.lines[0]?.line, count: range.count, + ignored: range.ignoredLines !== 0, }); if (range.count !== 0 || diff --git a/lib/internal/test_runner/reporter/lcov.js b/lib/internal/test_runner/reporter/lcov.js index 698913d79dec02..38d683046ffa1d 100644 --- a/lib/internal/test_runner/reporter/lcov.js +++ b/lib/internal/test_runner/reporter/lcov.js @@ -68,15 +68,26 @@ 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. + let lcovBranchCount = 0; + let lcovCoveredBranchCount = 0; for (let j = 0; j < file.branches.length; j++) { - lcov += `BRDA:${file.branches[j].line},${j},0,${file.branches[j].count}\n`; + const branch = file.branches[j]; + // Ignored lines should not produce uncovered BRDA entries. + if (branch.ignored && branch.count === 0) { + continue; + } + lcov += `BRDA:${branch.line},${j},0,${branch.count}\n`; + lcovBranchCount++; + if (branch.count > 0) { + lcovCoveredBranchCount++; + } } // Branch coverage summaries are stored in two lines: // ## BRF:\ // ## BRH:\ - lcov += `BRF:${file.totalBranchCount}\n`; - lcov += `BRH:${file.coveredBranchCount}\n`; + lcov += `BRF:${lcovBranchCount}\n`; + lcov += `BRH:${lcovCoveredBranchCount}\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/fixtures/test-runner/coverage-ignore-next-branch.js b/test/fixtures/test-runner/coverage-ignore-next-branch.js new file mode 100644 index 00000000000000..1b7cd0c3b88b91 --- /dev/null +++ b/test/fixtures/test-runner/coverage-ignore-next-branch.js @@ -0,0 +1,16 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert'); + +function getValue(condition) { + if (condition) { + return 'truthy'; + } + /* node:coverage ignore next */ + return 'falsy'; +} + +test('returns truthy', () => { + assert.strictEqual(getValue(true), 'truthy'); +}); diff --git a/test/parallel/test-runner-coverage.js b/test/parallel/test-runner-coverage.js index 5a8f3d743538cb..4ab3ec1ac79140 100644 --- a/test/parallel/test-runner-coverage.js +++ b/test/parallel/test-runner-coverage.js @@ -511,6 +511,26 @@ test('coverage with included and excluded files', skipIfNoInspector, () => { assert(!findCoverageFileForPid(result.pid)); }); +test('lcov omits uncovered ignored branches', skipIfNoInspector, () => { + const fixture = fixtures.path('test-runner', 'coverage-ignore-next-branch.js'); + const args = [ + '--test', + '--experimental-test-coverage', + '--test-reporter', + 'lcov', + fixture, + ]; + const result = spawnSync(process.execPath, args); + const stdout = result.stdout.toString(); + + assert.strictEqual(result.stderr.toString(), ''); + assert.strictEqual(result.status, 0); + assert.match(stdout, /SF:test\/fixtures\/test-runner\/coverage-ignore-next-branch\.js/); + assert.match(stdout, /BRF:\d+/); + assert.match(stdout, /BRH:\d+/); + assert.doesNotMatch(stdout, /^BRDA:.*,0$/m); +}); + test('correctly prints the coverage report of files contained in parent directories', skipIfNoInspector, () => { let report = [ '# start of coverage report',