Skip to content
Open
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
135 changes: 93 additions & 42 deletions lib/internal/test_runner/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ const { join, resolve, relative } = require('path');
const { fileURLToPath, URL } = require('internal/url');
const { kMappings, SourceMap } = require('internal/source_map/source_map');
const {
codes: {
ERR_SOURCE_MAP_CORRUPT,
ERR_SOURCE_MAP_MISSING_SOURCE,
},
codes: { ERR_SOURCE_MAP_CORRUPT, ERR_SOURCE_MAP_MISSING_SOURCE },
} = require('internal/errors');
const { matchGlobPattern } = require('internal/fs/glob');
const { constants: { kMockSearchParam } } = require('internal/test_runner/mock/loader');
const {
constants: { kMockSearchParam },
} = require('internal/test_runner/mock/loader');

const kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/;
const kIgnoreRegex = /\/\* node:coverage ignore next (?<count>\d+ )?\*\//;
Expand All @@ -47,8 +46,10 @@ const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;

class CoverageLine {
constructor(line, startOffset, src, length = src?.length) {
const newlineLength = src == null ? 0 :
RegExpPrototypeExec(kLineEndingRegex, src)?.[0].length ?? 0;
const newlineLength =
src == null
? 0
: (RegExpPrototypeExec(kLineEndingRegex, src)?.[0].length ?? 0);

this.line = line;
this.src = src;
Expand All @@ -60,9 +61,7 @@ class CoverageLine {
}

class TestCoverage {
constructor(coverageDirectory,
originalCoverageDirectory,
options) {
constructor(coverageDirectory, originalCoverageDirectory, options) {
this.coverageDirectory = coverageDirectory;
this.originalCoverageDirectory = originalCoverageDirectory;
this.options = options;
Expand All @@ -87,8 +86,7 @@ class TestCoverage {
return;
}

const linesWithBreaks =
RegExpPrototypeSymbolSplit(kLineSplitRegex, source);
const linesWithBreaks = RegExpPrototypeSymbolSplit(kLineSplitRegex, source);
let ignoreCount = 0;
let enabled = true;
let offset = 0;
Expand Down Expand Up @@ -179,7 +177,6 @@ class TestCoverage {
continue;
}


for (let j = 0; j < functions.length; ++j) {
const { isBlockCoverage, ranges } = functions[j];

Expand All @@ -193,14 +190,18 @@ class TestCoverage {
ObjectAssign(range, mapRangeToLines(range, lines));

if (isBlockCoverage) {
// Skip branches where all lines are ignored
if (range.ignoredLines === range.lines.length) {
continue;
}

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

if (range.count !== 0 ||
range.ignoredLines === range.lines.length) {
if (range.count !== 0) {
branchesCovered++;
}

Expand Down Expand Up @@ -297,10 +298,13 @@ class TestCoverage {
let dir;

try {
mkdirSync(this.originalCoverageDirectory, { __proto__: null, recursive: true });
mkdirSync(this.originalCoverageDirectory, {
__proto__: null,
recursive: true,
});
dir = opendirSync(this.coverageDirectory);

for (let entry; (entry = dir.readSync()) !== null;) {
for (let entry; (entry = dir.readSync()) !== null; ) {
const src = join(this.coverageDirectory, entry.name);
const dst = join(this.originalCoverageDirectory, entry.name);
copyFileSync(src, dst);
Expand All @@ -326,7 +330,7 @@ class TestCoverage {
try {
dir = opendirSync(this.coverageDirectory);

for (let entry; (entry = dir.readSync()) !== null;) {
for (let entry; (entry = dir.readSync()) !== null; ) {
if (RegExpPrototypeExec(kCoverageFileRegex, entry.name) === null) {
continue;
}
Expand All @@ -344,7 +348,6 @@ class TestCoverage {
}
}


mapCoverageWithSourceMap(coverage) {
const { result } = coverage;
const sourceMapCache = coverage['source-map-cache'];
Expand Down Expand Up @@ -386,18 +389,39 @@ class TestCoverage {
const { startOffset, endOffset, count } = ranges[k];
const { lines } = mapRangeToLines(ranges[k], executedLines);

let startEntry = sourceMap
.findEntry(lines[0].line - 1, MathMax(0, startOffset - lines[0].startOffset));
const endEntry = sourceMap
.findEntry(lines[lines.length - 1].line - 1, (endOffset - lines[lines.length - 1].startOffset) - 1);
if (!startEntry.originalSource && endEntry.originalSource &&
lines[0].line === 1 && startOffset === 0 && lines[0].startOffset === 0) {
let startEntry = sourceMap.findEntry(
lines[0].line - 1,
MathMax(0, startOffset - lines[0].startOffset),
);
const endEntry = sourceMap.findEntry(
lines[lines.length - 1].line - 1,
endOffset - lines[lines.length - 1].startOffset - 1,
);
if (
!startEntry.originalSource &&
endEntry.originalSource &&
lines[0].line === 1 &&
startOffset === 0 &&
lines[0].startOffset === 0
) {
// Edge case when the first line is not mappable
const { 2: originalSource, 3: originalLine, 4: originalColumn } = sourceMap[kMappings][0];
startEntry = { __proto__: null, originalSource, originalLine, originalColumn };
const {
2: originalSource,
3: originalLine,
4: originalColumn,
} = sourceMap[kMappings][0];
startEntry = {
__proto__: null,
originalSource,
originalLine,
originalColumn,
};
}

if (!startEntry.originalSource || startEntry.originalSource !== endEntry.originalSource) {
if (
!startEntry.originalSource ||
startEntry.originalSource !== endEntry.originalSource
) {
// The range is not mappable. Skip it.
continue;
}
Expand All @@ -413,21 +437,37 @@ class TestCoverage {
// The range is not mappable. Skip it.
continue;
}
for (let l = startEntry.originalLine; l <= endEntry.originalLine; l++) {
for (
let l = startEntry.originalLine;
l <= endEntry.originalLine;
l++
) {
mappedLines[l].count = count;
}

ArrayPrototypePush(newRanges, {
__proto__: null, startOffset: mappedStartOffset, endOffset: mappedEndOffset, count,
__proto__: null,
startOffset: mappedStartOffset,
endOffset: mappedEndOffset,
count,
});
}

if (!newUrl) {
// No mappable ranges. Skip the function.
continue;
}
const newScript = newResult.get(newUrl) ?? { __proto__: null, url: newUrl, functions: [] };
ArrayPrototypePush(newScript.functions, { __proto__: null, functionName, ranges: newRanges, isBlockCoverage });
const newScript = newResult.get(newUrl) ?? {
__proto__: null,
url: newUrl,
functions: [],
};
ArrayPrototypePush(newScript.functions, {
__proto__: null,
functionName,
ranges: newRanges,
isBlockCoverage,
});
newResult.set(newUrl, newScript);
}
}
Expand All @@ -442,7 +482,10 @@ class TestCoverage {
// Return -1 if the line is not mappable.
return -1;
}
return MathMin(mappedLine.startOffset + entry.originalColumn, mappedLine.endOffset);
return MathMin(
mappedLine.startOffset + entry.originalColumn,
mappedLine.endOffset,
);
}

mergeCoverage(merged, coverage) {
Expand Down Expand Up @@ -483,7 +526,8 @@ class TestCoverage {
if (
matchGlobPattern(relativePath, excludeGlobs[i]) ||
matchGlobPattern(absolutePath, excludeGlobs[i])
) return true;
)
return true;
}
}

Expand All @@ -493,7 +537,8 @@ class TestCoverage {
if (
matchGlobPattern(relativePath, includeGlobs[i]) ||
matchGlobPattern(absolutePath, includeGlobs[i])
) return false;
)
return false;
}
return true;
}
Expand Down Expand Up @@ -595,9 +640,11 @@ function mergeCoverageScripts(oldScript, newScript) {
for (let j = 0; j < oldScript.functions.length; ++j) {
const oldFn = oldScript.functions[j];

if (newFn.functionName === oldFn.functionName &&
newFn.ranges?.[0].startOffset === oldFn.ranges?.[0].startOffset &&
newFn.ranges?.[0].endOffset === oldFn.ranges?.[0].endOffset) {
if (
newFn.functionName === oldFn.functionName &&
newFn.ranges?.[0].startOffset === oldFn.ranges?.[0].startOffset &&
newFn.ranges?.[0].endOffset === oldFn.ranges?.[0].endOffset
) {
// These are the same functions.
found = true;

Expand Down Expand Up @@ -686,13 +733,17 @@ function mergeCoverageRanges(oldFn, newFn) {
}

function doesRangeEqualOtherRange(range, otherRange) {
return range.startOffset === otherRange.startOffset &&
range.endOffset === otherRange.endOffset;
return (
range.startOffset === otherRange.startOffset &&
range.endOffset === otherRange.endOffset
);
}

function doesRangeContainOtherRange(range, otherRange) {
return range.startOffset <= otherRange.startOffset &&
range.endOffset >= otherRange.endOffset;
return (
range.startOffset <= otherRange.startOffset &&
range.endOffset >= otherRange.endOffset
);
}

module.exports = { setupCoverage, TestCoverage };