Skip to content

Commit e5c95e4

Browse files
authored
feat (coverage): Load branch coverage from coverage files (#389)
This commit teaches the LCOV parser to also read branch coverage data. Works towards #362
1 parent 60051c5 commit e5c95e4

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

src/test-explorer/lcov_parser.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export function parseLcov(
4848
class FileCoverageInfo {
4949
functionsByLine: Map<number, vscode.DeclarationCoverage> = new Map();
5050
lineCoverage: Map<number, vscode.StatementCoverage> = new Map();
51+
coverageByLineAndBranch: Map<number, Map<string, vscode.BranchCoverage>> =
52+
new Map();
5153
}
5254
const infosByFile: Map<string, FileCoverageInfo> = new Map();
5355
for (const block of lcov.split(/end_of_record(\n|$)/)) {
@@ -191,12 +193,54 @@ export function parseLcov(
191193
// Ignored. Reconstructed from DA entries
192194
break;
193195
case "BRDA": {
194-
// branch coverage: <line_number>,[<exception>]<block>,<branch>,<taken>
196+
// branch coverage: <line_number>,[<exception>]<block>,<branch>,<hitCount>
197+
// Note that the <branch> might contain commas, which requires being
198+
// a bit careful while parsing.
195199
const match = value.match(/(\d+),(e?)(\d+),(.+)/);
196200
if (!match) {
197201
throw new Error(`Invalid FNDA entry`);
198202
}
199-
// TODO: Add support for branch coverage
203+
const lineNumber = Number.parseInt(match[1], 10) - 1;
204+
if (lineNumber < 0) {
205+
throw new Error("Negative line number in DA entry");
206+
}
207+
const isException = match[2] === "e";
208+
const blockId = Number.parseInt(match[3], 10);
209+
const rest = match[4];
210+
const commaOffset = rest.lastIndexOf(",");
211+
if (commaOffset === undefined) {
212+
throw new Error(`Invalid FNDA entry`);
213+
}
214+
const label = rest.substring(0, commaOffset);
215+
const hitCountStr = rest.substring(commaOffset + 1);
216+
const hitCount =
217+
hitCountStr === "-" ? 0 : Number.parseInt(hitCountStr, 10);
218+
if (hitCount < 0) {
219+
throw new Error("Negative hit count in DA entry");
220+
}
221+
222+
if (info === undefined) {
223+
throw new Error(`Missing filename`);
224+
}
225+
226+
// We don't want to display coverage for exception edges.
227+
if (isException) break;
228+
229+
// Insert into `branchByLineAndBranch`
230+
if (!info.coverageByLineAndBranch.has(lineNumber)) {
231+
info.coverageByLineAndBranch.set(lineNumber, new Map());
232+
}
233+
const coverageByBranch = info.coverageByLineAndBranch.get(lineNumber);
234+
const branchId = `${blockId}:${label}`;
235+
if (!coverageByBranch.has(branchId)) {
236+
coverageByBranch.set(
237+
branchId,
238+
new vscode.BranchCoverage(0, undefined, label),
239+
);
240+
}
241+
const branchCoverage = coverageByBranch.get(branchId);
242+
assert(typeof branchCoverage.executed == "number");
243+
branchCoverage.executed += hitCount;
200244
break;
201245
}
202246
case "BRF": // branches found
@@ -216,7 +260,16 @@ export function parseLcov(
216260
Array.from(info.functionsByLine.values()),
217261
);
218262
detailedCoverage = detailedCoverage.concat(
219-
Array.from(info.lineCoverage.values()),
263+
Array.from(info.lineCoverage.values()).map((c) => {
264+
assert("line" in c.location);
265+
const branchCoverage = info.coverageByLineAndBranch.get(
266+
c.location.line,
267+
);
268+
if (branchCoverage) {
269+
c.branches = Array.from(branchCoverage.values());
270+
}
271+
return c;
272+
}),
220273
);
221274
fileCoverages.push(
222275
BazelFileCoverage.fromDetails(

test/lcov_parser.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ describe("The lcov parser", () => {
8989
assert.strictEqual(fileCov.statementCoverage.covered, 11);
9090
});
9191
it("branch coverage", () => {
92-
assert(fileCov.branchCoverage === undefined);
92+
assert(fileCov.branchCoverage !== undefined);
93+
assert.strictEqual(fileCov.branchCoverage.total, 6);
94+
assert.strictEqual(fileCov.branchCoverage.covered, 3);
9395
});
9496
it("function coverage details", () => {
9597
const initFunc = getFunctionByLine(fileCov, 24);
@@ -109,6 +111,12 @@ describe("The lcov parser", () => {
109111
assert.equal(getLineCoverageForLine(fileCov, 38).executed, 0);
110112
assert.equal(getLineCoverageForLine(fileCov, 40).executed, 1);
111113
});
114+
it("branch coverage data", () => {
115+
const branchCoverage = getLineCoverageForLine(fileCov, 37).branches;
116+
assert.equal(branchCoverage.length, 2);
117+
assert.equal(branchCoverage[0].executed, 1);
118+
assert.equal(branchCoverage[1].executed, 0);
119+
});
112120
});
113121

114122
describe("parses C++ coverage data", () => {
@@ -128,7 +136,9 @@ describe("The lcov parser", () => {
128136
assert.strictEqual(fileCov.statementCoverage.covered, 505);
129137
});
130138
it("branch coverage", () => {
131-
assert(fileCov.branchCoverage === undefined);
139+
assert(fileCov.branchCoverage !== undefined);
140+
assert.strictEqual(fileCov.branchCoverage.total, 2560);
141+
assert.strictEqual(fileCov.branchCoverage.covered, 843);
132142
});
133143
it("function coverage details", () => {
134144
const initFunc = getFunctionByLine(fileCov, 71);
@@ -141,6 +151,14 @@ describe("The lcov parser", () => {
141151
assert.equal(getLineCoverageForLine(fileCov, 178).executed, 0);
142152
assert.equal(getLineCoverageForLine(fileCov, 193).executed, 4);
143153
});
154+
it("branch coverage data", () => {
155+
const branchCoverage = getLineCoverageForLine(fileCov, 479).branches;
156+
assert.equal(branchCoverage.length, 2);
157+
assert.equal(branchCoverage[0].executed, 1);
158+
assert.equal(branchCoverage[1].executed, 0);
159+
const branchCoverage2 = getLineCoverageForLine(fileCov, 481).branches;
160+
assert.equal(branchCoverage2.length, 12);
161+
});
144162
});
145163

146164
describe("parses Rust coverage data", () => {
@@ -161,6 +179,8 @@ describe("The lcov parser", () => {
161179
assert.strictEqual(fileCov.statementCoverage.covered, 426);
162180
});
163181
it("branch coverage", () => {
182+
// Rust has no branch coverage data, as of writing this test case.
183+
// Also see https://github.com/rust-lang/rust/issues/79649
164184
assert(fileCov.branchCoverage === undefined);
165185
});
166186
it("function coverage details", () => {
@@ -194,6 +214,7 @@ describe("The lcov parser", () => {
194214
assert.strictEqual(fileCov.statementCoverage.covered, 133);
195215
});
196216
it("branch coverage", () => {
217+
// Go has no branch coverage data.
197218
assert(fileCov.branchCoverage === undefined);
198219
});
199220
it("line coverage details", () => {

0 commit comments

Comments
 (0)