From 42aa97471cc4afe21e1c2f7869958dee21723830 Mon Sep 17 00:00:00 2001 From: quality Date: Thu, 8 Oct 2026 12:10:09 -0400 Subject: [PATCH] test(e2e): cover the istanbul readers in e2e-coverage-report.mjs The four module-private readers in tests/tools/e2e-coverage-report.mjs -- getLineCoverage, getRegionCoverage, isPhantomRegion and isContradictedRegion -- carry guards against malformed istanbul objects. Those objects are built inside the module by v8-to-istanbul from the V8 payload, so no capture artifact written at the public boundary can reach a guard; 13 regions survived every probe driven through collectE2ECoverage. Export the four readers for tests and drive their guards directly with hand-built istanbul objects. Record the regions that remain unreachable even so -- AnyMap internals, v8-to-istanbul's own converter fields, and two non-Error rejection paths in main() -- as an in-file note naming each by function and expression rather than by line. tests/tools/e2e-coverage-report.mjs: 93.65% -> 96.28% regions, lines unchanged at 100.00%. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: quality --- tests/e2e-coverage-report-readers.test.mjs | 307 +++++++++++++++++++++ tests/tools/e2e-coverage-report.mjs | 43 +++ 2 files changed, 350 insertions(+) create mode 100644 tests/e2e-coverage-report-readers.test.mjs diff --git a/tests/e2e-coverage-report-readers.test.mjs b/tests/e2e-coverage-report-readers.test.mjs new file mode 100644 index 00000000..9b5eaa0e --- /dev/null +++ b/tests/e2e-coverage-report-readers.test.mjs @@ -0,0 +1,307 @@ +// Unit coverage for the four istanbul readers in +// tests/tools/e2e-coverage-report.mjs -- `getLineCoverage`, +// `getRegionCoverage`, `isPhantomRegion` and `isContradictedRegion`. +// +// Every guard in those readers defends against a malformed *istanbul* object. +// Istanbul objects are not inputs to the module: `convertScript` builds them +// from the captured V8 payload with `v8-to-istanbul`, so a guard can only be +// reached if that library emits a shape it does not emit. No capture artifact +// written at the public boundary (`collectE2ECoverage`) can produce one, which +// is why 18 regions survived every probe written against the public surface in +// #1208/#1209 and were recorded as unreachable in #1210. +// +// The resolution chosen there is to drive the readers directly with hand-built +// istanbul objects rather than carry the residual forever. These tests are the +// only consumer of the test-only exports added for that purpose, and they are +// deliberately written against hand-built shapes: a fixture produced by +// `v8-to-istanbul` would by definition be well-formed and reach none of them. +// +// This file is separate from tests/e2e-coverage-report.test.mjs and +// tests/e2e-coverage-region-union.test.mjs so the three bodies of work stay +// independent, matching how tests/e2e-coverage-run.test.mjs and +// tests/e2e-coverage-scripts.test.mjs are already split out. +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + getLineCoverage, + getRegionCoverage, + isPhantomRegion, + isContradictedRegion, +} from './tools/e2e-coverage-report.mjs'; + +// A region as the readers consume it: original-source coordinates plus the +// execution count the fold decided on. +function region(line, column, endLine, endColumn, count = 0) { + return { line, column, endLine, endColumn, count }; +} + +// A witness as `isContradictedRegion` consumes it: the script that produced it, +// its per-line counts, its zero-count regions and the keys of those zeros. +function witness({ script, lines = [], zeros = [] }) { + return { + script, + lines: new Map(lines), + zeros, + zeroKeys: new Set( + zeros.map((zero) => + [zero.line, zero.column, zero.endLine, zero.endColumn].join(':'), + ), + ), + }; +} + +test('getLineCoverage treats a coverage object with no statements as empty', () => { + assert.deepEqual([...getLineCoverage({}).entries()], []); +}); + +test('getLineCoverage skips statements whose map entry is missing or unusable', () => { + const lines = getLineCoverage({ + // Four statements, one usable. The other three are each a different way + // for the map lookup to come back without an integer line: no entry at + // all, an entry with no `start`, and a `start` whose line is not an + // integer. Istanbul never emits any of them. + s: { 0: 3, 1: 7, 2: 7, 3: 7 }, + statementMap: { + 0: { start: { line: 12 } }, + 2: {}, + 3: { start: { line: 'not-a-line' } }, + }, + }); + assert.deepEqual([...lines.entries()], [[12, 3]]); +}); + +test('getLineCoverage keeps the highest count recorded against one line', () => { + // Two statements on line 5: the per-line fold reports the line at the count + // of whichever ran most, not whichever was read last. + const lines = getLineCoverage({ + s: { 0: 1, 1: 9, 2: 4 }, + statementMap: { + 0: { start: { line: 5 } }, + 1: { start: { line: 5 } }, + 2: { start: { line: 5 } }, + }, + }); + assert.equal(lines.get(5), 9); +}); + +test('getRegionCoverage treats a coverage object with no branches as empty', () => { + assert.deepEqual([...getRegionCoverage({}).entries()], []); +}); + +test('getRegionCoverage treats a branch with no map entry as having no locations', () => { + // `b` names a branch id that `branchMap` does not carry, and a second whose + // entry carries no `locations` array. Both fall through to the empty-list + // fallback rather than throwing. + const regions = getRegionCoverage({ + b: { 0: [1], 1: [1] }, + branchMap: { 1: {} }, + }); + assert.deepEqual([...regions.entries()], []); +}); + +test('getRegionCoverage skips locations with no usable start line', () => { + const regions = getRegionCoverage({ + b: { 0: [1, 1, 1, 1] }, + branchMap: { + 0: { + locations: [ + null, + {}, + { start: { line: 1.5 } }, + { start: { line: 8, column: 2 }, end: { line: 8, column: 9 } }, + ], + }, + }, + }); + assert.deepEqual( + [...regions.values()], + [region(8, 2, 8, 9, 1)], + 'only the well-formed location survives', + ); +}); + +test('getRegionCoverage defaults a location missing its end and columns', () => { + // A location with no `end` is a point: it ends on its own line. Absent + // columns default to 0 at both ends, so the key stays well formed. + const regions = getRegionCoverage({ + b: { 0: [2] }, + branchMap: { 0: { locations: [{ start: { line: 4 } }] } }, + }); + assert.deepEqual([...regions.values()], [region(4, 0, 4, 0, 2)]); +}); + +test('getRegionCoverage reads a location with no recorded count as zero', () => { + // `b` carries no counts array for the branch at all, and a second branch + // carries one too short for its locations. Both missing counts read as zero + // rather than `undefined`, which would poison the `count > existing.count` + // comparison the union depends on. + const regions = getRegionCoverage({ + b: { 0: undefined, 1: [] }, + branchMap: { + 0: { + locations: [ + { start: { line: 1, column: 0 }, end: { line: 1, column: 4 } }, + ], + }, + 1: { + locations: [ + { start: { line: 2, column: 0 }, end: { line: 2, column: 4 } }, + ], + }, + }, + }); + assert.deepEqual( + [...regions.values()], + [region(1, 0, 1, 4, 0), region(2, 0, 2, 4, 0)], + ); +}); + +test('isPhantomRegion keeps a region that executed', () => { + assert.equal(isPhantomRegion(region(1, 0, 3, 0, 1), new Map()), false); +}); + +test('isPhantomRegion keeps a single-line zero region', () => { + // Several single-line regions share one line and the per-line fold cannot + // tell them apart, so a zero on one line is never folded away. + assert.equal( + isPhantomRegion(region(7, 4, 7, 20), new Map([[7, 5]])), + false, + 'endLine === line is not a phantom', + ); + assert.equal( + isPhantomRegion(region(9, 4, 8, 0), new Map([[9, 5]])), + false, + 'an end before the start is not a phantom either', + ); +}); + +test('isPhantomRegion folds a multi-line zero whose every executable line ran', () => { + // Line 3 carries no statement -- a blank line or a comment inside the span. + // It neither confirms nor contradicts, so the fold skips it. + assert.equal( + isPhantomRegion( + region(2, 0, 4, 8), + new Map([ + [2, 1], + [4, 1], + ]), + ), + true, + ); +}); + +test('isPhantomRegion keeps a multi-line zero contradicted by an unexecuted line', () => { + // Line 3 is executable and did not run, so the span is genuinely uncovered + // rather than a source-map artifact. + assert.equal( + isPhantomRegion( + region(2, 0, 4, 8), + new Map([ + [2, 1], + [3, 0], + [4, 1], + ]), + ), + false, + ); +}); + +test('isContradictedRegion keeps a region that executed', () => { + assert.equal(isContradictedRegion(region(1, 0, 1, 4, 3), []), false); +}); + +test('isContradictedRegion keeps a zero no script ever recorded', () => { + // The witness recorded a zero at different coordinates, so no script is a + // candidate and the inference never starts. Across scripts the absence of a + // zero proves nothing: a different bundle maps the same arm elsewhere. + const target = region(5, 2, 5, 9); + assert.equal( + isContradictedRegion(target, [ + witness({ + script: 'a.js', + lines: [[5, 1]], + zeros: [region(9, 0, 9, 3)], + }), + ]), + false, + ); +}); + +test('isContradictedRegion keeps a zero whose only witness lines carry no statement', () => { + // The witness covers the script but records no executable line across the + // region's span, so it never saw the text and cannot speak for it. + const target = region(5, 2, 5, 9); + assert.equal( + isContradictedRegion(target, [ + witness({ script: 'a.js', lines: [[40, 1]], zeros: [target] }), + ]), + false, + ); +}); + +test('isContradictedRegion keeps a zero whose witness left one span line unexecuted', () => { + // Line 6 inside the span did not run in the witness, so the witness did not + // execute the text either and contradicts nothing. + const target = region(5, 2, 7, 9); + assert.equal( + isContradictedRegion(target, [ + witness({ + script: 'a.js', + lines: [ + [5, 1], + [6, 0], + [7, 1], + ], + zeros: [target], + }), + ]), + false, + ); +}); + +test('isContradictedRegion keeps a zero the witness itself also recorded', () => { + // The witness covers the span's lines but records a zero touching them, so + // it skipped the same text. Only a witness with no zero over those lines + // proves execution. + const target = region(5, 2, 5, 9); + assert.equal( + isContradictedRegion(target, [ + witness({ + script: 'a.js', + lines: [[5, 1]], + zeros: [target, region(5, 0, 5, 1)], + }), + ]), + false, + ); +}); + +test('isContradictedRegion folds a zero another artifact of the same script executed', () => { + // Two artifacts of one script: the first recorded the zero, the second ran + // the span's lines and recorded no zero on any of them. One source map per + // script means the same skipped text would land on the same span, so the + // second artifact executed it. + const target = region(5, 2, 5, 9); + assert.equal( + isContradictedRegion(target, [ + witness({ script: 'a.js', lines: [[5, 1]], zeros: [target] }), + witness({ script: 'a.js', lines: [[5, 1]], zeros: [] }), + ]), + true, + ); +}); + +test('isContradictedRegion ignores witnesses from a script that never recorded the zero', () => { + // `b.js` covers the lines and carries no zero, but it never produced the + // zero at these coordinates, so it is not a witness for this region. Only + // `a.js` is consulted, and it still records the zero. + const target = region(5, 2, 5, 9); + assert.equal( + isContradictedRegion(target, [ + witness({ script: 'a.js', lines: [[5, 1]], zeros: [target] }), + witness({ script: 'b.js', lines: [[5, 1]], zeros: [] }), + ]), + false, + ); +}); diff --git a/tests/tools/e2e-coverage-report.mjs b/tests/tools/e2e-coverage-report.mjs index ce643b28..93cc97fb 100644 --- a/tests/tools/e2e-coverage-report.mjs +++ b/tests/tools/e2e-coverage-report.mjs @@ -27,6 +27,34 @@ const ERROR_KIND = 'endusers.e2e.coverage-report-error'; const SOURCE_MAPPING_URL = /(?:\/\/[#@]\s*sourceMappingURL=)(\S+)/u; const ORIGINAL_SCRIPT = /\.(?:c|m)?(?:js|jsx|ts|tsx)$/u; +// Residual uncovered regions, recorded so the file's region percentage is not +// re-investigated every cycle (#1210). The guards in the four istanbul readers +// below are reached directly by tests/e2e-coverage-report-readers.test.mjs, +// which is what the test-only export beneath `getRegionCoverage` exists for. +// What remains after that is unreachable from this module, and each entry is +// named by function and expression rather than by line so the note does not go +// stale when the file moves: +// +// normalizeSourceMap `map.resolvedSources ?? ...` -- whether `AnyMap` +// populates `resolvedSources` is private behaviour of the transitive +// dependency `@jridgewell/trace-mapping`, not something a caller sets. +// convertScript `converter.branches ?? {}` and `converter.functions +// ?? {}`, and the `.map(([path]) => path)` arm that only runs when one +// of those is absent -- `v8-to-istanbul` always defines both after +// `load()`, so the fallback needs a shape the library does not emit. +// collectE2ECoverage `converted.coverage ?? {}` -- `convertScript` returns +// `converter.toIstanbul()` on every path and that never returns nullish; +// and `if (lineCoverage.size === 0) continue`, which needs an istanbul +// object carrying branches but no statements at all. +// main `error.message ?? String(error)` and `error.stack ?? +// error` -- both need `main()` to reject with a non-`Error`, and nothing +// in its call graph throws one. +// +// Reachability was established empirically rather than by reading: probe tests +// were written against each candidate and these are the ones no probe could +// close. Raise the per-file region floor rather than this list when a probe +// does land. + class CoverageReportError extends Error { constructor(message, { runId = null, runStatus = null } = {}) { super(message); @@ -484,6 +512,21 @@ function getRegionCoverage(coverageData) { return regions; } +// These four readers are exported for tests only. Every guard below them is a +// defence against a malformed *istanbul* object, and istanbul objects are not +// inputs to this module -- `convertScript` builds them from the V8 payload via +// `v8-to-istanbul`. Reaching a guard from the public surface would require that +// library to emit a shape it does not emit, so no capture artifact written at +// the boundary can exercise one (#1210). Exporting the readers lets the guards +// be driven directly with hand-built istanbul objects instead of being carried +// as a permanent uncovered residual. +export { + getLineCoverage, + getRegionCoverage, + isPhantomRegion, + isContradictedRegion, +}; + // Captured scripts are resolved against each root in order -- the run's own // `scripts/` copies first, then the build directory. A script missing from one // root falls through to the next; a script missing from all of them still