Skip to content

Commit fe30882

Browse files
committed
fix(ci): treat negative v8 branch deltas as uncovered when merging lcov
1 parent 8e76b80 commit fe30882

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

‎src/scripts/__tests__/merge-lcov.spec.mjs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ describe("mergeLcov", () => {
4242
expect(merged).not.toContain("DA:3,1")
4343
})
4444

45+
it("treats a negative v8 branch delta as uncovered instead of failing the merge", () => {
46+
// The v8 provider emits negative BRDA counts for short-circuit conditions
47+
// (e.g. `freshState === "aborted" || signal?.aborted`), which previously
48+
// aborted the CI coverage merge with "Invalid BRDA ... -3".
49+
const merged = mergeLcov([
50+
["core", report(new Set([1])).replace("BRDA:2,0,0,-", "BRDA:2,0,0,-3")],
51+
["api", report(new Set([1, 2]))],
52+
])
53+
54+
// The negative-delta branch merges to uncovered, and the positive
55+
// duplicate from the other lane still wins the union.
56+
expect(merged).toContain("BRDA:2,0,0,1")
57+
58+
const solo = mergeLcov([["core", report(new Set([1])).replace("BRDA:2,0,0,-", "BRDA:2,0,0,-3")]])
59+
expect(solo).toContain("BRDA:2,0,0,-")
60+
expect(solo).not.toContain("-3")
61+
})
62+
4563
it("merges disjoint source records without changing their paths", () => {
4664
const merged = mergeLcov([
4765
["api", report(new Set([1])).replaceAll("src/example.ts", "src/api.ts")],

‎src/scripts/merge-lcov.mjs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ const parseCount = (value, description) => {
77
return count
88
}
99

10+
// The v8 provider emits negative BRDA counts for short-circuit conditions, where
11+
// an uncovered inner branch range is subtracted from a covered outer one
12+
// (e.g. `a === X || b?.flag`). A negative delta means the branch was never taken
13+
// on its own, so clamp it to uncovered instead of failing the merge.
14+
const parseBranchCount = (value, description) => {
15+
const count = Number(value)
16+
if (!Number.isSafeInteger(count)) throw new Error(`Invalid ${description}: ${value}`)
17+
return count > 0 ? count : 0
18+
}
19+
1020
const mergeCount = (records, key, count) => records.set(key, Math.max(records.get(key) ?? 0, count))
1121

1222
const parseLcov = (lcov, label) => {
@@ -48,7 +58,7 @@ const parseLcov = (lcov, label) => {
4858
} else if (record && line.startsWith("BRDA:")) {
4959
const [lineNumber, block, branch, taken] = line.slice(5).split(",")
5060
const key = `${lineNumber},${block},${branch}`
51-
const count = taken === "-" ? 0 : parseCount(taken, `BRDA for ${record.source}`)
61+
const count = taken === "-" ? 0 : parseBranchCount(taken, `BRDA for ${record.source}`)
5262
mergeCount(record.branches, key, count)
5363
} else if (record && line.startsWith("DA:")) {
5464
const [lineNumber, count, checksum] = line.slice(3).split(",")

0 commit comments

Comments
 (0)