Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions lib/internal/test_runner/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/;
const kIgnoreRegex = /\/\* node:coverage ignore next (?<count>\d+ )?\*\//;
const kLineEndingRegex = /\r?\n$/u;
const kLineSplitRegex = /(?<=\r?\n)/u;
const kNonExecutableLineRegex = /^\s*(?:$|[{}()[\];,]+|\/\/.*|\/\*.*\*\/)\s*$/;
const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;

class CoverageLine {
Expand Down Expand Up @@ -193,13 +194,15 @@ class TestCoverage {
ObjectAssign(range, mapRangeToLines(range, lines));

if (isBlockCoverage) {
const branchCount = isIgnoredBranchRange(range) ? 1 : range.count;

ArrayPrototypePush(branchReports, {
__proto__: null,
line: range.lines[0]?.line,
count: range.count,
count: branchCount,
});

if (range.count !== 0 ||
if (branchCount !== 0 ||
range.ignoredLines === range.lines.length) {
branchesCovered++;
}
Expand Down Expand Up @@ -585,6 +588,26 @@ function mapRangeToLines(range, lines) {
return { __proto__: null, lines: mappedLines, ignoredLines };
}

function isIgnoredBranchRange(range) {
if (range.count !== 0 || range.ignoredLines === 0) {
return false;
}

for (let i = 0; i < range.lines.length; ++i) {
const line = range.lines[i];

if (line.ignore ||
line.count > 0 ||
RegExpPrototypeExec(kNonExecutableLineRegex, line.src) !== null) {
continue;
}

return false;
}

return true;
}

function mergeCoverageScripts(oldScript, newScript) {
// Merge the functions from the new coverage into the functions from the
// existing (merged) coverage.
Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/test-runner/coverage-ignored-branch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

function getValue(condition) {
if (condition) {
return 'truthy';
}
/* node:coverage ignore next */
return 'falsy';
}

module.exports = { getValue };
9 changes: 9 additions & 0 deletions test/fixtures/test-runner/coverage-ignored-branch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

const assert = require('node:assert');
const test = require('node:test');
const { getValue } = require('./coverage-ignored-branch');

test('returns truthy', () => {
assert.strictEqual(getValue(true), 'truthy');
});
32 changes: 31 additions & 1 deletion test/parallel/test-runner-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const common = require('../common');
const assert = require('node:assert');
const { spawnSync } = require('node:child_process');
const { readdirSync } = require('node:fs');
const { readFileSync, readdirSync } = require('node:fs');
const { test } = require('node:test');
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
Expand Down Expand Up @@ -333,6 +333,36 @@ test('coverage reports on lines, functions, and branches', skipIfNoInspector, as
});
});

test('lcov reporter excludes ignored branch ranges', skipIfNoInspector, () => {
const fixture = fixtures.path('test-runner', 'coverage-ignored-branch.test.js');
const destination = tmpdir.resolve('coverage-ignored-branch.lcov');
const env = { ...process.env };
delete env.NODE_TEST_CONTEXT;
const args = [
'--test',
'--experimental-test-coverage',
'--test-coverage-include=coverage-ignored-branch.js',
'--test-reporter',
'lcov',
'--test-reporter-destination',
destination,
fixture,
];
const result = spawnSync(process.execPath, args, {
cwd: fixtures.path('test-runner'),
env,
});

assert.strictEqual(result.stderr.toString(), '');
assert.strictEqual(result.status, 0);

const report = readFileSync(destination, 'utf8');
assert.match(report, /^BRF:3$/m);
assert.match(report, /^BRH:3$/m);
assert.doesNotMatch(report, /^BRDA:.*,0$/m);
assert.doesNotMatch(report, /^DA:8,/m);
});

test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
let report = [
'# start of coverage report',
Expand Down
Loading