# What Is a Go Coverprofile? — The coverage.out Format, Block by Block

Canonical: https://commareports.com/glossary/go-coverprofile
Published: 2026-11-04

> The file go test -coverprofile writes: the mode line, one block per line with start and end positions, statement count and hit count. How Go computes its percentage and how to read one without go tool cover.

# 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

```text
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:

```text
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 -race` switches 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 from `go 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.** `-coverpkg` and concatenated runs
  list the same block more than once. `go tool cover` merges 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 -cover` plus `GOCOVERDIR`
  writes binary `covmeta`/`covcounters` files for integration tests.
  `go tool covdata textfmt -i=<dir> -o=coverage.out` turns 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](/tools/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.

**[Open the coverage viewer →](/tools/coverage-viewer)**

### Related

- [Code coverage](/glossary/code-coverage) — line, branch, statement, and what a percentage hides
- [LCOV](/glossary/lcov) — the lcov.info format most other languages write
- [Cobertura](/glossary/cobertura) — the XML format GitLab and Azure DevOps read
- [Share a Go test report](/share-go-test-report) · [Share a coverage report](/share-coverage-report) · [Glossary](/glossary)
