From ce61daa415698700f175a6f193f7894c1bc738a9 Mon Sep 17 00:00:00 2001 From: quality Date: Sun, 4 Oct 2026 08:35:58 -0400 Subject: [PATCH] test: fold out phantom e2e coverage regions over covered lines getRegionCoverage keeps one entry per branch location v8-to-istanbul emits, and v8-to-istanbul derives those locations from *generated* block boundaries mapped back through the source map. Minified output carries no mapping at most boundaries, so an endpoint snaps to whatever mapping precedes it and a block that never ran is reported as an original span that demonstrably did. At 900592b the report put src/components/hooks/useFocusTrap.js at 62.96% with a zero-count region across lines 38-40 -- the Shift+Tab wrap arm that tests/e2e/interactions.spec.js:217 presses and asserts -- while the same union put statement coverage for lines 38, 39 and 40 at 1. A span cannot go unexecuted while every line it covers executed, so multi-line zero-count regions whose every statement-carrying line is covered are no longer counted. Single-line regions are untouched: several of them share one line and the per-line fold cannot tell them apart, which is the whole reason regions are measured. Over a full local coverage run the fold removes 37 of 467 regions and lifts src files from 80.94% to 87.91%; useFocusTrap goes to 85.00% with only its three genuine single-line arms left. Signed-off-by: quality --- tests/e2e-coverage-report.test.mjs | 130 ++++++++++++++++++++++++++++ tests/tools/e2e-coverage-report.mjs | 32 ++++++- 2 files changed, 159 insertions(+), 3 deletions(-) diff --git a/tests/e2e-coverage-report.test.mjs b/tests/e2e-coverage-report.test.mjs index 90efd320..9064884c 100644 --- a/tests/e2e-coverage-report.test.mjs +++ b/tests/e2e-coverage-report.test.mjs @@ -260,6 +260,136 @@ test('collectE2ECoverage maps V8 ranges through an external source map to src li } }); +// v8-to-istanbul builds every region from a *generated* block boundary and maps +// its endpoints back through the source map. Minified output has no mapping at +// most boundaries, so an endpoint lands on whatever mapping precedes it and a +// block that never ran is reported as an original span that demonstrably did: +// at 900592b the report put a zero-count region across useFocusTrap.js lines +// 38-40 -- the Shift+Tab wrap arm that tests/e2e/interactions.spec.js:217 +// presses and asserts -- while statement coverage put all three of those lines +// at 1 in the same union (#1035). Those spans inflated the denominator and sent +// contributors to write tests for branches that were already covered. +test('a zero-count region spanning only covered lines is not counted against the file', async () => { + const fixture = await fixtureRun(); + try { + const lines = [ + 'const a = 1;', + '', + 'const c = 3;', + 'const d = 4;', + 'const e = 5;', + ]; + const source = `${lines.join('\n')}\n`; + const scriptText = `${source}\n//# sourceMappingURL=app.js.map\n`; + const offset = (text) => source.indexOf(text); + const original = join(fixture.root, 'src/components/Example/index.js'); + await mkdir(join(fixture.root, 'src/components/Example'), { + recursive: true, + }); + await writeFile(original, source); + await writeFile(join(fixture.buildDir, 'assets/js/app.js'), scriptText); + await writeFile( + join(fixture.buildDir, 'assets/js/app.js.map'), + JSON.stringify({ + version: 3, + file: 'app.js', + sources: ['webpack://endusers/./src/components/Example/index.js'], + sourcesContent: [source], + names: [], + // Every line maps at column 0; line 4 carries a second mapping at + // column 8, so a block opening mid-line resolves inside it rather + // than snapping back to the start of the line. + mappings: `${mapLines([ + [0, 0], + [0, 1], + [0, 2], + [0, 3], + [0, 4], + ])},${[vlq(8), vlq(0), vlq(0), vlq(8)].join('')}`, + }), + ); + + // One page runs everything but the last line. + await writeCoverageArtifact(fixture.runDir, 'worker-0-page-0', { + schemaVersion: 1, + kind: 'endusers.playwright.v8-coverage', + runId: 'run-1', + result: [ + { + url: 'http://localhost:3000/assets/js/app.js', + scriptId: '1', + functions: [ + { + functionName: '', + isBlockCoverage: true, + ranges: [ + { + startOffset: 0, + endOffset: scriptText.length, + count: 1, + }, + { + startOffset: offset('const e'), + endOffset: scriptText.length, + count: 0, + }, + ], + }, + ], + }, + ], + }); + // A second page contributes two more zero-count blocks: one over lines + // 1-3, standing in for the mis-mapped multi-line span (line 2 is blank, so + // the span has to be judged on the lines that carry statements), and one + // from inside line 4 to the end of line 5, which closes on a line nothing + // ran. Only the first is a phantom. + await writeCoverageArtifact(fixture.runDir, 'worker-0-page-1', { + schemaVersion: 1, + kind: 'endusers.playwright.v8-coverage', + runId: 'run-1', + result: [ + { + url: 'http://localhost:3000/assets/js/app.js', + scriptId: '2', + functions: [ + { + functionName: '', + isBlockCoverage: true, + ranges: [ + { startOffset: 0, endOffset: scriptText.length, count: 1 }, + { startOffset: 0, endOffset: offset('const d'), count: 0 }, + { + startOffset: offset('const d') + 'const d'.length, + endOffset: offset('const e') + 'const e = 5;'.length, + count: 0, + }, + ], + }, + ], + }, + ], + }); + await sealCoverageRun(fixture.runDir, 'passed'); + + const report = await collectE2ECoverage(fixture.runDir, { + root: fixture.root, + buildDir: fixture.buildDir, + }); + assert.equal(report.status, 'ok'); + const [entry] = report.sources; + assert.equal(entry.file, 'src/components/Example/index.js'); + assert.deepEqual(entry.uncoveredLines, [5]); + // Line 1 is gone: nothing in lines 1-3 went unexecuted, so no region can + // span them unexecuted. The span closing on line 5 and the region confined + // to line 5 both stay, and so does their weight in the denominator. + assert.deepEqual(entry.uncoveredRegions, [4, 5]); + assert.equal(entry.regions - entry.coveredRegions, 2); + } finally { + await rm(fixture.root, { recursive: true, force: true }); + } +}); + // `npm run build:e2e:coverage` compiles the site twice: once from the real // data, and once with tests/e2e/fixtures/data-variants/** layered on, into // build/e2e-coverage-variant under its own base URL. That is what makes a diff --git a/tests/tools/e2e-coverage-report.mjs b/tests/tools/e2e-coverage-report.mjs index e9efcfc3..fc144bb4 100644 --- a/tests/tools/e2e-coverage-report.mjs +++ b/tests/tools/e2e-coverage-report.mjs @@ -306,6 +306,29 @@ function getLineCoverage(coverageData) { return lines; } +// v8-to-istanbul derives every region from a *generated* block boundary and +// maps its endpoints back through the source map. In minified output those +// endpoints land on whatever mapping precedes them, so a block that never ran +// can be reported as a multi-line original span that demonstrably did run -- +// `useFocusTrap`'s Shift+Tab arm reported a zero-count region over lines +// 38-40 while statement coverage put all three lines at 1 (#1035). A span +// cannot be unexecuted while every line it covers executed, so those spans are +// dropped rather than counted against the file. Single-line regions are kept: +// several of them share one line and the per-line fold cannot tell them apart, +// which is the whole reason regions are measured. +function isPhantomRegion(region, lines) { + if (region.count > 0) return false; + if (region.endLine <= region.line) return false; + for (let line = region.line; line <= region.endLine; line += 1) { + const count = lines.get(line); + // Blank lines and comments inside the span carry no statement, so they + // neither confirm nor contradict the region. + if (count === undefined) continue; + if (count <= 0) return false; + } + return true; +} + // A region is one branch location from the istanbul object convertScript // already builds -- the arm of a ternary, a short-circuit operand, a default // parameter -- data the line map cannot see because several of them share a @@ -320,16 +343,17 @@ function getRegionCoverage(coverageData) { for (const [index, location] of locations.entries()) { const line = location?.start?.line; if (!Number.isInteger(line)) continue; + const endLine = location.end?.line ?? line; const key = [ line, location.start?.column ?? 0, - location.end?.line ?? line, + endLine, location.end?.column ?? 0, ].join(':'); const count = counts?.[index] ?? 0; const existing = regions.get(key); if (!existing || count > existing.count) { - regions.set(key, { line, count }); + regions.set(key, { line, endLine, count }); } } } @@ -551,7 +575,9 @@ export async function collectE2ECoverage( const coveredLines = [...coverage.lines.values()].filter( (count) => count > 0, ).length; - const regionValues = [...coverage.regions.values()]; + const regionValues = [...coverage.regions.values()].filter( + (region) => !isPhantomRegion(region, coverage.lines), + ); const regions = regionValues.length; const coveredRegions = regionValues.filter( (region) => region.count > 0,