LCOV
LCOV is a plain-text code coverage format: a tracefile (usually
lcov.info) that lists each source file followed by one record per
line, branch and function with how many times it ran. It is named after
lcov, the front-end to GCC's gcov, but today it is the format almost
every coverage tool can write and almost every coverage service can read.
What is in a file
TN:
SF:src/cart/cart.ts
FN:15,applyCoupon
FNDA:9,applyCoupon
FNF:1
FNH:1
DA:15,9
DA:16,9
DA:20,7
DA:21,0
LF:4
LH:3
BRDA:20,0,0,0
BRDA:20,0,1,7
BRF:2
BRH:1
end_of_record
| Record | Meaning |
|---|---|
TN:<name> |
Test name — often empty; several runs can be concatenated |
SF:<path> |
Starts a source file (usually an absolute path) |
FN:<line>,<name> |
A function declared at that line (LCOV 2 adds an end line) |
FNDA:<hits>,<name> |
How many times that function was called |
DA:<line>,<hits> |
An executable line and its hit count |
BRDA:<line>,<block>,<branch>,<taken> |
One side of a branch; - means the condition never ran |
LF / LH, BRF / BRH, FNF / FNH |
Lines, branches, functions found / hit |
end_of_record |
Closes the file |
Line coverage is LH / LF; branch coverage is BRH / BRF.
Who writes it
- C and C++:
lcov --captureover gcov data, orllvm-cov export -format=lcov - JavaScript and TypeScript: Istanbul/nyc, c8, Jest and Vitest with the
lcovreporter, Bun (--coverage-reporter=lcov), Deno (deno coverage --lcov) - Python:
coverage lcov(coverage.py 6.3+) - Rust:
cargo llvm-cov --lcov, grcov - Dart and Flutter:
flutter test --coveragewritescoverage/lcov.info - Go:
go test -coverprofileplusgcov2lcov
Codecov, Coveralls, SonarQube and the coverage gutters in VS Code and JetBrains all read it.
The parts that bite
- Absolute paths.
SF:usually holds the CI runner's path (/home/runner/work/app/app/src/…), so tools that match files against the repository need a path rewrite or--base-directory. - Summaries can disagree with records.
LF/LHare written by the tool, not checked by the reader. When tracefiles are concatenated or edited, recount from theDAlines. - Merging is a sum. The same
SF:in two runs should be added together (lcov --add-tracefile), so a line hit by either suite counts as covered — appending files without merging double-lists them. - No statements. LCOV has lines, branches and functions. Istanbul's "statements" percentage does not survive the conversion, which is why numbers drift between tools.
Reading one without genhtml
genhtml coverage.info -o out turns a tracefile into a browsable HTML tree,
but it needs Perl and the lcov package. The free
coverage viewer reads lcov.info (and Cobertura
or Clover XML) in the browser: line, branch and function coverage, a
roll-up per directory, files sorted least-covered first with their
uncovered line ranges, and a Markdown summary for the pull request.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.
Related
- Cobertura — the XML alternative GitLab and Azure DevOps read
- Code coverage — line, branch, statement, and what a percentage hides
- Share an lcov / genhtml report · Share a coverage report
- JUnit XML · Coverage report links 404 · Glossary