Skip to content

Commit 0381817

Browse files
MoLowmarco-ippolito
authored andcommitted
test_runner: calculate executed lines using source map
PR-URL: #53315 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Chemi Atlow <[email protected]>
1 parent dc2a4af commit 0381817

File tree

5 files changed

+78
-10
lines changed

5 files changed

+78
-10
lines changed

lib/internal/test_runner/coverage.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ const kLineSplitRegex = /(?<=\r?\n)/u;
3636
const kStatusRegex = /\/\* node:coverage (?<status>enable|disable) \*\//;
3737

3838
class CoverageLine {
39-
constructor(line, src, startOffset) {
40-
const newlineLength =
39+
constructor(line, startOffset, src, length = src?.length) {
40+
const newlineLength = src == null ? 0 :
4141
RegExpPrototypeExec(kLineEndingRegex, src)?.[0].length ?? 0;
4242

4343
this.line = line;
4444
this.src = src;
4545
this.startOffset = startOffset;
46-
this.endOffset = startOffset + src.length - newlineLength;
46+
this.endOffset = startOffset + length - newlineLength;
4747
this.ignore = false;
4848
this.count = this.startOffset === this.endOffset ? 1 : 0;
4949
}
@@ -83,7 +83,7 @@ class TestCoverage {
8383

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

8888
offset += line.length;
8989

@@ -335,8 +335,13 @@ class TestCoverage {
335335
newResult.set(url, script);
336336
continue;
337337
}
338-
const originalLines = this.getLines(url);
339338
const { data, lineLengths } = sourceMapCache[url];
339+
let offset = 0;
340+
const executedLines = ArrayPrototypeMap(lineLengths, (length, i) => {
341+
const coverageLine = new CoverageLine(i + 1, offset, null, length);
342+
offset += length;
343+
return coverageLine;
344+
});
340345
if (data.sourcesContent != null) {
341346
for (let j = 0; j < data.sources.length; ++j) {
342347
this.getLines(data.sources[j], data.sourcesContent[j]);
@@ -353,7 +358,7 @@ class TestCoverage {
353358
const newRanges = [];
354359
for (let k = 0; k < ranges.length; ++k) {
355360
const { startOffset, endOffset, count } = ranges[k];
356-
const { lines } = mapRangeToLines(ranges[k], originalLines);
361+
const { lines } = mapRangeToLines(ranges[k], executedLines);
357362

358363
let startEntry = sourceMap
359364
.findEntry(lines[0].line - 1, MathMax(0, startOffset - lines[0].startOffset));
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
1-
const source = `
1+
const sources = {
2+
// Virtual file. Dosen't exist on disk
3+
"virtual.js": `
24
import { test } from 'node:test';
35
test('test', async () => {});
4-
`;
6+
`,
7+
// file with source map. this emulates the behavior of tsx
8+
"sum.test.ts": `\
9+
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)})});
10+
11+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBQUEsT0FBUyxTQUFVLE9BQVUsWUFDN0IsT0FBTyxXQUFZLGNBQ25CLE9BQVMsUUFBVyxRQUVwQixTQUFTLE1BQU8sSUFBTSxDQUNwQixHQUFHLHlCQUEwQixJQUFNLENBQy9CLE9BQU8sZ0JBQWdCLElBQUksRUFBRSxDQUFDLEVBQUcsQ0FBQyxDQUN0QyxDQUFDLEVBRUQsR0FBRywwQ0FBMkMsSUFBTSxDQUNsRCxPQUFPLE9BQU8sSUFBTSxJQUFJLEVBQUcsR0FBRyxFQUFHLEtBQUssQ0FDeEMsQ0FBQyxDQUNILENBQUMiLCJuYW1lcyI6W10sImlnbm9yZUxpc3QiOltdLCJzb3VyY2VzIjpbIi4vc3VtLnRlc3QudHMiXSwic291cmNlc0NvbnRlbnQiOltudWxsXX0=`,
12+
// file with source map. this emulates the behavior of tsx
13+
"sum.ts": `\
14+
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};
15+
16+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6ImtIQUFPLFNBQVMsT0FBUSxFQUFHLENBQ3pCLEdBQUksQ0FBQyxFQUFFLE1BQU8sS0FBUSxPQUFPLE1BQVEsUUFBUSxFQUFHLE1BQU0sSUFBSSxNQUFNLGNBQWMsRUFDOUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxJQUFLLE1BQVEsSUFBTSxHQUFHLENBQ3pDLENBSGdCIiwibmFtZXMiOltdLCJpZ25vcmVMaXN0IjpbXSwic291cmNlcyI6WyIuL3N1bS50cyJdLCJzb3VyY2VzQ29udGVudCI6W251bGxdfQ==`,
17+
};
518

619
export async function load(url, context, nextLoad) {
7-
if (url.endsWith('virtual.js')) {
8-
return { format: "module", source, shortCircuit: true };
20+
const file = url.split('/').at(-1);
21+
if (sources[file] !== undefined) {
22+
return { format: "module", source: sources[file], shortCircuit: true };
923
}
1024
return nextLoad(url, context);
1125
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, it } from 'node:test'
2+
import assert from 'node:assert'
3+
import { sum } from './sum'
4+
5+
describe('sum', () => {
6+
it('should sum two numbers', () => {
7+
assert.deepStrictEqual(sum(1, 2), 3)
8+
})
9+
10+
it('should error out if one is not a number', () => {
11+
assert.throws(() => sum(1, 'b'), Error)
12+
})
13+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export function sum (...n) {
2+
if (!n.every((num) => typeof num === 'number')) throw new Error('Not a number')
3+
return n.reduce((acc, cur) => acc + cur)
4+
}

test/parallel/test-runner-coverage.js

+32
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,35 @@ test('coverage with ESM hook - source irrelevant', skipIfNoInspector, () => {
303303
assert(result.stdout.toString().includes(report));
304304
assert.strictEqual(result.status, 0);
305305
});
306+
307+
test('coverage with ESM hook - source transpiled', skipIfNoInspector, () => {
308+
let report = [
309+
'# start of coverage report',
310+
'# ------------------------------------------------------------------',
311+
'# file | line % | branch % | funcs % | uncovered lines',
312+
'# ------------------------------------------------------------------',
313+
'# hooks.mjs | 100.00 | 100.00 | 100.00 | ',
314+
'# register-hooks.js | 100.00 | 100.00 | 100.00 | ',
315+
'# sum.test.ts | 100.00 | 100.00 | 100.00 | ',
316+
'# sum.ts | 100.00 | 100.00 | 100.00 | ',
317+
'# ------------------------------------------------------------------',
318+
'# all files | 100.00 | 100.00 | 100.00 |',
319+
'# ------------------------------------------------------------------',
320+
'# end of coverage report',
321+
].join('\n');
322+
323+
if (common.isWindows) {
324+
report = report.replaceAll('/', '\\');
325+
}
326+
327+
const fixture = fixtures.path('test-runner', 'coverage-loader');
328+
const args = [
329+
'--import', './register-hooks.js', '--test', '--experimental-test-coverage',
330+
'--test-reporter', 'tap', 'sum.test.ts',
331+
];
332+
const result = spawnSync(process.execPath, args, { cwd: fixture });
333+
334+
assert.strictEqual(result.stderr.toString(), '');
335+
assert(result.stdout.toString().includes(report));
336+
assert.strictEqual(result.status, 0);
337+
});

0 commit comments

Comments
 (0)