diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 3d0fcde700a4..91269be36b83 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -754,19 +754,17 @@ function mergeCoverageScripts(oldScript, newScript) { // These are the same functions. found = true; - // If newFn is block level coverage, then it will: - // - Replace oldFn if oldFn is not block level coverage. - // - Merge with oldFn if it is also block level coverage. - // If newFn is not block level coverage, then it has no new data. - if (newFn.isBlockCoverage) { - if (oldFn.isBlockCoverage) { - // Merge the oldFn ranges with the newFn ranges. - mergeCoverageRanges(oldFn, newFn); - } else { - // Replace oldFn with newFn. + if (newFn.isBlockCoverage && oldFn.isBlockCoverage) { + mergeCoverageRanges(oldFn, newFn); + } else { + // A function without block level coverage only has a call count. + // Keep the block level ranges if either side has them. + const count = oldFn.ranges[0].count + newFn.ranges[0].count; + if (newFn.isBlockCoverage) { oldFn.isBlockCoverage = true; oldFn.ranges = newFn.ranges; } + oldFn.ranges[0].count = count; } break; diff --git a/test/fixtures/test-runner/coverage-function-counts/lib.js b/test/fixtures/test-runner/coverage-function-counts/lib.js new file mode 100644 index 000000000000..24b6028bd863 --- /dev/null +++ b/test/fixtures/test-runner/coverage-function-counts/lib.js @@ -0,0 +1,4 @@ +function a() {} +function b() {} +function c() {} +function d() {} diff --git a/test/fixtures/test-runner/coverage-function-counts/test.js b/test/fixtures/test-runner/coverage-function-counts/test.js new file mode 100644 index 000000000000..b8a70e95f892 --- /dev/null +++ b/test/fixtures/test-runner/coverage-function-counts/test.js @@ -0,0 +1,25 @@ +'use strict'; +const { writeFileSync } = require('node:fs'); +const { join } = require('node:path'); +const { pathToFileURL } = require('node:url'); + +function fn(functionName, startOffset, count, isBlockCoverage = false) { + const endOffset = functionName ? startOffset + 15 : 64; + return { functionName, ranges: [{ startOffset, endOffset, count }], isBlockCoverage }; +} + +function writeCoverage(id, functions) { + writeFileSync( + join(process.env.NODE_V8_COVERAGE, `coverage-${id}-0000000000000-0.json`), + JSON.stringify({ + result: [{ + scriptId: '1', + url: pathToFileURL(join(__dirname, 'lib.js')).href, + functions: [fn('', 0, 1), ...functions], + }], + }), + ); +} + +writeCoverage(1, [fn('a', 0, 1), fn('b', 16, 0), fn('c', 32, 1), fn('d', 48, 0, true)]); +writeCoverage(2, [fn('a', 0, 0), fn('b', 16, 1), fn('c', 32, 0, true), fn('d', 48, 1)]); diff --git a/test/parallel/test-runner-coverage.js b/test/parallel/test-runner-coverage.js index fa56c5af3e80..36e3e727d503 100644 --- a/test/parallel/test-runner-coverage.js +++ b/test/parallel/test-runner-coverage.js @@ -657,3 +657,21 @@ test('coverage with directory and file named "file"', skipIfNoInspector, () => { assert.strictEqual(result.status, 0); assertIncludesReport(result, 'start of coverage report'); }); + +test('merges call counts of functions without block coverage', skipIfNoInspector, () => { + const args = [ + '--test', + '--experimental-test-coverage', + '--test-coverage-exclude=!test/**', + '--test-reporter', + 'tap', + 'test.js', + ]; + const result = spawnSync(process.execPath, args, { + cwd: fixtures.path('test-runner', 'coverage-function-counts'), + }); + + assert.strictEqual(result.stderr.toString(), ''); + assert.strictEqual(result.status, 0); + assert.match(result.stdout.toString(), /# lib\.js +\| 100\.00 \| +100\.00 \| +100\.00 \|/); +});