Go coverprofile
A Go coverprofile is the plain-text file go test -coverprofile writes:
a mode: line, then one line per basic block with where the block starts
and ends, how many statements it holds, and how many times it ran. It is
Go's own coverage format — not LCOV, not Cobertura — and the input to
go tool cover.
What is in a file
mode: set
github.com/acme/api/internal/auth/token.go:12.40,14.16 2 1
github.com/acme/api/internal/auth/token.go:14.16,16.3 1 0
github.com/acme/api/internal/auth/token.go:17.2,17.15 1 1
github.com/acme/api/internal/store/db.go:8.30,10.2 2 0
| Part | Meaning |
|---|---|
mode: set |
How hits were recorded: set, count or atomic |
…/auth/token.go |
The file, as an import path plus file name |
12.40,14.16 |
Block start line.column, end line.column |
2 |
Statements in the block |
1 |
Times it ran (0 or 1 in set mode) |
A block is a straight run of code with one entry and one exit — the body
of an if, a case, the code after a closing brace. Line 14.16 above ends
one block and starts the next one: the if body that never ran.
How the percentage is computed
By statements. Add up the statements in blocks that ran at least once, divide by all statements:
token.go 3 of 4 statements 75.0%
db.go 0 of 2 statements 0.0%
total 3 of 6 statements 50.0%
go test -cover prints this per package (coverage: 50.0% of statements). go tool cover -func=coverage.out breaks it down per
function and ends with total: (statements) 50.0%.
The three modes
set— did the block run. The default.count— how many times it ran.atomic— counts with atomic increments, correct under parallel goroutines.go test -raceswitches to it automatically.
The percentage is the same in all three; only the hit counts change.
count is what you want for a heat map of hot paths.
The parts that bite
- Lines are not the unit. One line can belong to two blocks — the
if err != nil {line is in the parent block, while the body that starts on the same line may never have run. Tools that convert to line coverage (gcov2lcov, Codecov) have to decide what that line is, which is why their numbers drift fromgo test -cover. - Only tested packages appear. By default each package is measured
by its own tests. Code exercised from another package's tests counts
only with
-coverpkg=./..., and packages with no test files can be missing entirely, which inflates the total. - Merged profiles repeat blocks.
-coverpkgand concatenated runs list the same block more than once.go tool covermerges them; hand-rolled scripts that just sum statements double-count. - Import paths, not repo paths. Lines start with the module path
(
github.com/acme/api/…), so tools that map files to the repository need the module prefix stripped. - Binary coverage since Go 1.20.
go build -coverplusGOCOVERDIRwrites binarycovmeta/covcountersfiles for integration tests.go tool covdata textfmt -i=<dir> -o=coverage.outturns them back into a coverprofile.
Reading one without go tool cover
go tool cover -html=coverage.out -o coverage.html writes annotated
source — the best view of which branch is untested. For the summary a
pull request needs, the free coverage viewer
reads coverage.out in the browser: the same statement total as
go tool cover -func, a roll-up per package directory, files sorted
least-covered first with their uncovered line ranges, and a Markdown
table to paste.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.
Related
- Code coverage — line, branch, statement, and what a percentage hides
- LCOV — the lcov.info format most other languages write
- Cobertura — the XML format GitLab and Azure DevOps read
- Share a Go test report · Share a coverage report · Glossary