Skip to content

Commit

Permalink
test_runner: calculate executed lines using source map
Browse files Browse the repository at this point in the history
PR-URL: nodejs#53315
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
  • Loading branch information
MoLow authored and bmeck committed Jun 22, 2024
1 parent 13526fd commit 2ef52f4
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 10 deletions.
17 changes: 11 additions & 6 deletions lib/internal/test_runner/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ const kLineSplitRegex = /(?<=\r?\n)/u;
const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;

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

this.line = line;
this.src = src;
this.startOffset = startOffset;
this.endOffset = startOffset + src.length - newlineLength;
this.endOffset = startOffset + length - newlineLength;
this.ignore = false;
this.count = this.startOffset === this.endOffset ? 1 : 0;
}
Expand Down Expand Up @@ -83,7 +83,7 @@ class TestCoverage {

const lines = ArrayPrototypeMap(linesWithBreaks, (line, i) => {
const startOffset = offset;
const coverageLine = new CoverageLine(i + 1, line, startOffset);
const coverageLine = new CoverageLine(i + 1, startOffset, line);

offset += line.length;

Expand Down Expand Up @@ -335,8 +335,13 @@ class TestCoverage {
newResult.set(url, script);
continue;
}
const originalLines = this.getLines(url);
const { data, lineLengths } = sourceMapCache[url];
let offset = 0;
const executedLines = ArrayPrototypeMap(lineLengths, (length, i) => {
const coverageLine = new CoverageLine(i + 1, offset, null, length);
offset += length;
return coverageLine;
});
if (data.sourcesContent != null) {
for (let j = 0; j < data.sources.length; ++j) {
this.getLines(data.sources[j], data.sourcesContent[j]);
Expand All @@ -353,7 +358,7 @@ class TestCoverage {
const newRanges = [];
for (let k = 0; k < ranges.length; ++k) {
const { startOffset, endOffset, count } = ranges[k];
const { lines } = mapRangeToLines(ranges[k], originalLines);
const { lines } = mapRangeToLines(ranges[k], executedLines);

let startEntry = sourceMap
.findEntry(lines[0].line - 1, MathMax(0, startOffset - lines[0].startOffset));
Expand Down
22 changes: 18 additions & 4 deletions test/fixtures/test-runner/coverage-loader/hooks.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
const source = `
const sources = {
// Virtual file. Dosen't exist on disk
"virtual.js": `
import { test } from 'node:test';
test('test', async () => {});
`;
`,
// file with source map. this emulates the behavior of tsx
"sum.test.ts": `\
import{describe,it}from"node:test";import assert from"node:assert";import{sum}from"./sum.ts";describe("sum",()=>{it("should sum two numbers",()=>{assert.deepStrictEqual(sum(1,2),3)});it("should error out if one is not a number",()=>{assert.throws(()=>sum(1,"b"),Error)})});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBQUEsT0FBUyxTQUFVLE9BQVUsWUFDN0IsT0FBTyxXQUFZLGNBQ25CLE9BQVMsUUFBVyxRQUVwQixTQUFTLE1BQU8sSUFBTSxDQUNwQixHQUFHLHlCQUEwQixJQUFNLENBQy9CLE9BQU8sZ0JBQWdCLElBQUksRUFBRSxDQUFDLEVBQUcsQ0FBQyxDQUN0QyxDQUFDLEVBRUQsR0FBRywwQ0FBMkMsSUFBTSxDQUNsRCxPQUFPLE9BQU8sSUFBTSxJQUFJLEVBQUcsR0FBRyxFQUFHLEtBQUssQ0FDeEMsQ0FBQyxDQUNILENBQUMiLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VzIjpbIi4vc3VtLnRlc3QudHMiXSwic291cmNlc0NvbnRlbnQiOltudWxsXX0=`,
// file with source map. this emulates the behavior of tsx
"sum.ts": `\
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:true});function sum(...n){if(!n.every(num=>typeof num==="number"))throw new Error("Not a number");return n.reduce((acc,cur)=>acc+cur)}__name(sum,"sum");export{sum};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6ImtIQUFPLFNBQVMsT0FBUSxFQUFHLENBQ3pCLEdBQUksQ0FBQyxFQUFFLE1BQU8sS0FBUSxPQUFPLE1BQVEsUUFBUSxFQUFHLE1BQU0sSUFBSSxNQUFNLGNBQWMsRUFDOUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxJQUFLLE1BQVEsSUFBTSxHQUFHLENBQ3pDLENBSGdCIiwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlcyI6WyIuL3N1bS50cyJdLCJzb3VyY2VzQ29udGVudCI6W251bGxdfQ==`,
};

export async function load(url, context, nextLoad) {
if (url.endsWith('virtual.js')) {
return { format: "module", source, shortCircuit: true };
const file = url.split('/').at(-1);
if (sources[file] !== undefined) {
return { format: "module", source: sources[file], shortCircuit: true };
}
return nextLoad(url, context);
}
13 changes: 13 additions & 0 deletions test/fixtures/test-runner/coverage-loader/sum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { sum } from './sum'

describe('sum', () => {
it('should sum two numbers', () => {
assert.deepStrictEqual(sum(1, 2), 3)
})

it('should error out if one is not a number', () => {
assert.throws(() => sum(1, 'b'), Error)
})
})
4 changes: 4 additions & 0 deletions test/fixtures/test-runner/coverage-loader/sum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function sum (...n) {
if (!n.every((num) => typeof num === 'number')) throw new Error('Not a number')
return n.reduce((acc, cur) => acc + cur)
}
32 changes: 32 additions & 0 deletions test/parallel/test-runner-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,35 @@ test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
assert(result.stdout.toString().includes(report));
assert.strictEqual(result.status, 0);
});

test('coverage with ESM hook - source transpiled', skipIfNoInspector, () => {
let report = [
'# start of coverage report',
'# ------------------------------------------------------------------',
'# file | line % | branch % | funcs % | uncovered lines',
'# ------------------------------------------------------------------',
'# hooks.mjs | 100.00 | 100.00 | 100.00 | ',
'# register-hooks.js | 100.00 | 100.00 | 100.00 | ',
'# sum.test.ts | 100.00 | 100.00 | 100.00 | ',
'# sum.ts | 100.00 | 100.00 | 100.00 | ',
'# ------------------------------------------------------------------',
'# all files | 100.00 | 100.00 | 100.00 |',
'# ------------------------------------------------------------------',
'# end of coverage report',
].join('\n');

if (common.isWindows) {
report = report.replaceAll('/', '\\');
}

const fixture = fixtures.path('test-runner', 'coverage-loader');
const args = [
'--import', './register-hooks.js', '--test', '--experimental-test-coverage',
'--test-reporter', 'tap', 'sum.test.ts',
];
const result = spawnSync(process.execPath, args, { cwd: fixture });

assert.strictEqual(result.stderr.toString(), '');
assert(result.stdout.toString().includes(report));
assert.strictEqual(result.status, 0);
});

0 comments on commit 2ef52f4

Please sign in to comment.