Parses LCOV `.info` text files (the de-facto coverage interchange format produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others). Extracts per-file line / function / branch metrics from the canonical record keywords (TN/SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LH/LF), computes the diff vs a baseline, and emits per-file gating verdicts. Use for PR coverage gates that don't depend on a specific language runtime.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
LCOV is "a tool suite for manipulating and displaying code coverage
information" with three command-line utilities: geninfo
(creates LCOV data files from raw coverage data), lcov
(captures, filters, manipulates, processes in parallel), and
genhtml (HTML report generation) (lcov-readme).
The toolchain is "language-agnostic (via converter scripts:
llvm2lcov, py2lcov, perl2lcov, xml2lcov)" (lcov-readme) -
LCOV .info is the lingua franca every coverage UI (Coveralls,
Codecov, Codacy, SonarQube, in-house dashboards) ingests.
This skill covers parsing the .info text format directly so the
team can gate PRs without running the full HTML generation step.
py2lcov,
Java via xml2lcov, Node via nyc/jest's lcov reporter, C++
via gcov → lcov)..info (natively or via a converter:
py2lcov, xml2lcov, nyc / Jest's lcov reporter,
gcov → lcov)..info into per-file line / function / branch metrics
(Parse below); recompute totals from the per-line records rather
than trusting the summary fields.main's .info as the baseline; diff each PR's coverage
against it.A single source file's .info record, and how to read it:
TN:
SF:src/checkout/cart.ts
FN:10,addItem
FN:32,removeItem
FNDA:42,addItem
FNDA:0,removeItem
FNF:2
FNH:1
DA:11,42
DA:12,42
DA:13,0
DA:33,0
DA:34,0
LF:5
LH:2
BRDA:13,0,0,42
BRDA:13,0,1,0
BRF:2
BRH:1
end_of_recordReading: cart.ts has 2 functions, 1 hit (50% function coverage); 5
lines, 2 hit (40% line); 2 branches, 1 hit (50% branch).
removeItem was never called (FNDA:0). SF opens the record,
DA:<line>,<count> gives per-line hits, LH/LF and BRH/BRF
are the roll-ups, and end_of_record closes the file. The full
record-keyword table is in
references/format-diff-and-ci.md.
# scripts/parse_lcov.py
from collections import defaultdict
def parse_lcov(path):
files = []
cur = None
with open(path) as f:
for line in f:
line = line.strip()
if line.startswith('SF:'):
cur = {
'path': line[3:],
'functions': [],
'lines': {},
'branches': defaultdict(list),
'fnf': 0, 'fnh': 0,
'lf': 0, 'lh': 0,
'brf': 0, 'brh': 0,
}
elif line.startswith('FN:'):
lineno, name = line[3:].split(',', 1)
cur['functions'].append({'line': int(lineno), 'name': name, 'hits': 0})
elif line.startswith('FNDA:'):
hits, name = line[5:].split(',', 1)
for fn in cur['functions']:
if fn['name'] == name:
fn['hits'] = int(hits)
break
elif line.startswith('DA:'):
lineno, hits = line[3:].split(',', 1)
cur['lines'][int(lineno)] = int(hits.split(',')[0]) # checksum optional
elif line.startswith('BRDA:'):
lineno, block, branch, taken = line[5:].split(',', 3)
cur['branches'][int(lineno)].append({
'block': int(block),
'branch': int(branch),
'taken': 0 if taken == '-' else int(taken),
})
elif line.startswith(('FNF:', 'FNH:', 'LF:', 'LH:', 'BRF:', 'BRH:')):
key, val = line.split(':', 1)
cur[key.lower()] = int(val)
elif line == 'end_of_record':
files.append(cur)
cur = None
return filesDon't trust the FNF/FNH/LF/LH/BRF/BRH summary fields blindly -
some buggy emitters produce summaries that don't match the per-line
data. For correctness, recompute from lines, functions,
branches.
A defensible per-file gate has three rules:
Per-file gates beat whole-repo gates: an aggregate drop hides which
file caused it; per-file output gives the reviewer a direct target.
The coverage_diff + gate reference implementation and the PR
coverage-gate CI job are in
references/format-diff-and-ci.md.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Whole-repo gate only | Aggregate drops hide which file caused them; review focus is unclear. | Per-file gates; per-file PR comments. |
BRH < BRF ignored | A single uncovered branch on a critical path silently slips through. | Track branch% separately from line%; gate threshold differs. |
Trusting FNF/FNH/LF/LH summary fields without recomputing | Some emitters produce wrong summaries; gate verdict drifts from the data. | Recompute from per-line records (see the Parse note). |
| Gate against the PR's own merge base (running coverage twice) | Slow; flaky if coverage itself is non-deterministic. | Cache main's LCOV as an artifact; PRs diff against it (see the CI wiring in references). |
| Treating new test files as "new code, gate at 80%" | The new test file is the test, not the SUT. | Filter the file list to source paths only (e.g. src/**, not tests/**). |
| Strict mode that fails on any drop | Refactors that legitimately remove dead code drop coverage; team disables gate. | Allow whole-repo drop ≤0.5pp; allow per-file drop ≤5pp; only new files have a hard min. |
| One unified threshold for line + branch | Branch coverage is harder; identical thresholds always fail one or the other. | Separate thresholds (e.g. line 80, branch 70). |
.info is text, not standardized via a formal spec. The
authoritative source is the lcov-readme + the codebase
itself; format extensions vary by emitter. Tolerant parsing is
required.git diff - pair with git diff --name-only to scope coverage
changes to PR-touched files when needed.BRDA); some report per-decision
(single arm). Normalize before cross-tool comparisons..info format keywords, language-agnostic converters..info record-keyword table, the baseline-diff + gate
reference implementation, and the PR coverage-gate CI job.cobertura-analysis - sibling
for the Cobertura XML format (same PR-gating shape, different
parser).coverage-diff-reporter -
build-an-X workflow that consumes parsed coverage and emits a PR
comment with file-level deltas.test-coverage-targeter - picks which uncovered branches to target first using the parsed
output.