diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index b6f3dd5aa9e080..264ebfc41a506e 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -1,6 +1,7 @@ 'use strict'; const { ArrayFrom, + ArrayPrototypeForEach, ArrayPrototypeMap, ArrayPrototypePush, JSONParse, @@ -57,12 +58,21 @@ class CoverageLine { } class TestCoverage { - constructor(coverageDirectory, originalCoverageDirectory, workingDirectory, excludeGlobs, includeGlobs, thresholds) { + constructor( + coverageDirectory, + originalCoverageDirectory, + workingDirectory, + excludeGlobs, + includeGlobs, + sourceMaps, + thresholds, + ) { this.coverageDirectory = coverageDirectory; this.originalCoverageDirectory = originalCoverageDirectory; this.workingDirectory = workingDirectory; this.excludeGlobs = excludeGlobs; this.includeGlobs = includeGlobs; + this.sourceMaps = sourceMaps; this.thresholds = thresholds; } @@ -366,7 +376,13 @@ class TestCoverage { this.getLines(data.sources[j], data.sourcesContent[j]); } } + const sourceMap = new SourceMap(data, { __proto__: null, lineLengths }); + const linesToCover = new SafeSet(); + + for (let i = 0; i < sourceMap[kMappings].length; i++) { + linesToCover.add(sourceMap[kMappings][i][3] + 1); + } for (let j = 0; j < functions.length; ++j) { const { ranges, functionName, isBlockCoverage } = functions[j]; @@ -415,6 +431,15 @@ class TestCoverage { // No mappable ranges. Skip the function. continue; } + + if (this.sourceMaps) { + ArrayPrototypeForEach(this.getLines(newUrl), (mappedLine) => { + if (!linesToCover.has(mappedLine.line)) { + mappedLine.ignore = true; + } + }); + } + const newScript = newResult.get(newUrl) ?? { __proto__: null, url: newUrl, functions: [] }; ArrayPrototypePush(newScript.functions, { __proto__: null, functionName, ranges: newRanges, isBlockCoverage }); newResult.set(newUrl, newScript); @@ -516,6 +541,7 @@ function setupCoverage(options) { options.cwd, options.coverageExcludeGlobs, options.coverageIncludeGlobs, + options.sourceMaps, { __proto__: null, line: options.lineCoverage, diff --git a/test/parallel/test-runner-coverage-source-map.js b/test/parallel/test-runner-coverage-source-map.js index 0ded33713c784a..9dc2a85f2e523f 100644 --- a/test/parallel/test-runner-coverage-source-map.js +++ b/test/parallel/test-runner-coverage-source-map.js @@ -45,6 +45,31 @@ describe('Coverage with source maps', async () => { t.assert.strictEqual(spawned.code, 1); }); + it('accounts only mapped lines when --enable-source-maps is provided', async (t) => { + const report = generateReport([ + '# --------------------------------------------------------------', + '# file | line % | branch % | funcs % | uncovered lines', + '# --------------------------------------------------------------', + '# a.test.ts | 100.00 | 100.00 | 100.00 | ', // part of a bundle + '# b.test.ts | 88.89 | 100.00 | 100.00 | 1', // part of a bundle + '# index.test.js | 71.43 | 66.67 | 100.00 | 6-7', // no source map + '# stdin.test.ts | 100.00 | 100.00 | 100.00 | ', // Source map without original file + '# --------------------------------------------------------------', + '# all files | 91.67 | 87.50 | 100.00 | ', + '# --------------------------------------------------------------', + ]); + + const spawned = await common.spawnPromisified(process.execPath, [ + '--test', '--experimental-test-coverage', '--enable-source-maps', '--test-reporter', 'tap', + ], { + cwd: fixtures.path('test-runner', 'coverage') + }); + + t.assert.strictEqual(spawned.stderr, ''); + t.assert.ok(spawned.stdout.includes(report)); + t.assert.strictEqual(spawned.code, 1); + }); + await it('properly accounts for line endings in source maps', async (t) => { const report = generateReport([ '# ------------------------------------------------------------------', @@ -89,6 +114,7 @@ describe('Coverage with source maps', async () => { const spawned = await common.spawnPromisified(process.execPath, [...flags, file]); const error = `The source map for '${pathToFileURL(file)}' does not exist or is corrupt`; + t.assert.strictEqual(spawned.stderr, ''); t.assert.ok(spawned.stdout.includes(error)); t.assert.strictEqual(spawned.code, 1);