-
Notifications
You must be signed in to change notification settings - Fork 18.7k
Description
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 -covernow 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 -coverhas 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
aexports a high level API and has_test.gofiles. - Packages
b,c,dimplement most of the logic, with no_test.gofiles. - Tests in
aexerciseb,c,dthoroughly.
Under Go 1.22, go test ./... -cover reports:
- A non zero percentage for
a, but coverage: 0.0% of statementsforb,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.gofiles “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:
- “How much code did the test suite execute?” with
- “Which packages contain
_test.gofiles?”
Proposal
-
Add a module wide coverage mode to
go testProvide 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.gofiles 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
.gofiles that is never executed remains at0.0%, preserving the intent of issue 18909 that untested code should not be invisible.
- Instruments all packages in the main module for coverage (similar to
-
Clarify documentation
- Document explicitly that default
go test -coverreports coverage for the instrumented package under test, and that cross package / module wide coverage requires either-coverpkgor the new mode. - Provide a small example where tests in package
aintegration test packageb, and show how to obtain coverage that reflects the executed code inb.
- Document explicitly that default
-
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 thegoversion ingo.mod. This would align the default with the integration coverage definition and avoid surprising drops when new packages are added without local tests.