Skip to content

cmd/go, cmd/cover: make coverage reflect executed code across packages, not only packages with tests #76789

@corbym

Description

@corbym

Proposal Details

Summary

go test -cover currently treats coverage as a property of packages that have local _test.go files. As of Go 1.22, packages that have Go source but no _test.go files are reported as

mymod/mypack  coverage: 0.0% of statements

instead of ? mymod/mypack [no test files].

This behaviour stems from the accepted proposal in cmd/go: write empty coverage report when there are no tests (issue 18909). It fixed one misleading scenario, but for projects that centralise tests or rely on integration tests across packages it makes coverage appear worse even when their code is well exercised.

I propose adding a mode in go test that defines coverage in terms of executed code across packages, not tests written per package, so that when package A’s tests exercise package B, coverage for B reflects that execution even if B has no _test.go of its own.


Background and current behaviour

Issue 18909 noted that if a repository has many packages but tests only in one, go test ./... -cover could report “100% coverage” based on a single instrumented package:

Say you have 100 packages of Go code and only one has tests, and those tests cover 100% of that package's code, then coverage for the repo will be 100%. But it is really 1% coverage.

The issue requested “negative coverage reports” for packages that have .go files but no tests, instead of skipping them. That proposal was accepted.

Go 1.22 then changed go test -cover to treat such packages as 0% covered rather than “no test files”:

go test -cover now prints coverage summaries for covered packages that do not have their own test files … now with Go 1.22, functions in the package are treated as uncovered:
mymod/mypack coverage: 0.0% of statements.

At the same time, Go’s own docs define coverage in terms of code execution:

  • “Test coverage is a term that describes how much of a package’s code is exercised by running the package’s tests.” go.dev blog
  • Integration coverage docs describe coverage tools as determining “what fraction of a source code base is executed (covered) when a given test suite is executed”, and note that go test -cover has long provided package level coverage based on this idea. go.dev blog

So the documentation frames coverage as “executed statements”, but the current go test -cover behaviour effectively ties the result to “packages with local test files.”


Problem

Consider a typical multi package library:

  • Package a exports a high level API and has _test.go files.
  • Packages b, c, d implement most of the logic, with no _test.go files.
  • Tests in a exercise b, c, d thoroughly.

Under Go 1.22, go test ./... -cover reports:

  • A non zero percentage for a, but
  • coverage: 0.0% of statements for b, c, d, even though their code runs under the tests.

For projects that intentionally:

  • centralise tests in a top level package, or
  • rely on integration tests that cross package boundaries,

this behaviour:

  • makes coverage regress when upgrading Go, without any change in tests or code,
  • pushes authors to add trivial _test.go files “per package” just to avoid apparent 0%
  • pushes authors to double test code, even though other tests may test path execution of package code already, and
  • conflicts with the integration coverage story, which is explicitly about “what fraction of the source base did this test suite execute”.

Conceptually, it conflates:

  1. “How much code did the test suite execute?” with
  2. “Which packages contain _test.go files?”

Proposal

  1. Add a module wide coverage mode to go test

    Provide a flag or documented mode that:

    • Instruments all packages in the main module for coverage (similar to -coverpkg=./...).
    • Attributes coverage to any package whose statements were executed by any test, regardless of where the _test.go files live.

    For example:

    go test ./... -cover -coverpkg=./...

    could be documented and supported as the canonical “module coverage” mode, or wrapped as a dedicated flag (for example -covermode=module).

    Under this mode:

    • A package with no tests of its own, but whose code is executed by other packages’ tests, reports a meaningful non zero percentage.
    • A package with .go files that is never executed remains at 0.0%, preserving the intent of issue 18909 that untested code should not be invisible.
  2. Clarify documentation

    • Document explicitly that default go test -cover reports coverage for the instrumented package under test, and that cross package / module wide coverage requires either -coverpkg or the new mode.
    • Provide a small example where tests in package a integration test package b, and show how to obtain coverage that reflects the executed code in b.
  3. Optionally consider this as a future default

    If performance and compatibility allow, a future Go version could consider making “module wide executed statements” the default for go test ./... -cover, gated by the go version in go.mod. This would align the default with the integration coverage definition and avoid surprising drops when new packages are added without local tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ProposalToolProposalIssues describing a requested change to a Go tool or command-line program.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions