Go version

go1.26-devel_bb124921e9 Sun Jul 27 12:36:07 2025 -0400 darwin/amd64

Output of go env in your module/workspace:

╰─ go env
AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/ryancurrah/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/ryancurrah/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/fk/8lgm5_252mb0ln68l_z9wlz80000gn/T/go-build3317734112=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/Users/ryancurrah/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/ryancurrah/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/ryancurrah/git/github.com/ryancurrah/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/ryancurrah/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/ryancurrah/git/github.com/ryancurrah/go/pkg/tool/darwin_amd64'
GOVCS=''
GOVERSION='go1.26-devel_bb124921e9 Sun Jul 27 12:36:07 2025 -0400'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Hello guys,

Thanks a lot for resolving https://github.com/golang/go/issues/23565.

I started testing this feature and encountered a problem that occurs when I use the go test command with the -coverpkg flag.

When coverage is collected with the -coverpkg flag and test result caching is enabled, the generated report may include lines that no longer exist. This happens because:

Each test package attempts to collect coverage for all packages matching the -coverpkg pattern.

If the test result is loaded from the cache, the coverage data may be outdated—especially if the test package does not directly or indirectly depend on the modified code, leaving the cache uninvalidated.

for example we have project with layout

Project layout
proj/
  some_func.go
  some_func_test.go
  sub/
    sub.go
    sub_test.go
  sum/
    sum.go

Files Content

some_func.go

package proj

import "proj/sum"

func SomeFunc(a, b int) int {
    if a == 0 && b == 0 {
        return 0
    }
    return sum.Sum(a, b)
}

sub.go

package sub

func Sub(a, b int) int {
    if a == 0 && b == 0 {
        return 0
    }
    return a - b
}

sum.go

package sum

func Sum(a, b int) int {
    if a == 0 {
        return b
    }
    return a + b
}

some_func_test.go

package proj

import (
    "github.com/stretchr/testify/require"
    "testing"
)

func Test_SomeFunc(t *testing.T) {
    t.Run("test1", func(t *testing.T) {
        require.Equal(t, 2, SomeFunc(1, 1))
    })
}

sub_test.go

package sub

import (
    "github.com/stretchr/testify/require"
    "testing"
)

func Test_Sub(t *testing.T) {
    t.Run("test_sub1", func(t *testing.T) {
        require.Equal(t, 0, Sub(1, 1))
    })
}

Coverage result of this tests

mode: set
proj/some_func.go:5.29,6.22 1 1
proj/some_func.go:6.22,8.3 1 0
proj/some_func.go:9.2,9.22 1 1
proj/sub/sub.go:3.24,4.22 1 0
proj/sub/sub.go:4.22,6.3 1 0
proj/sub/sub.go:7.2,7.14 1 0
proj/sum/sum.go:3.24,4.12 1 1
proj/sum/sum.go:4.12,6.3 1 0
proj/sum/sum.go:7.2,7.14 1 1
proj/some_func.go:5.29,6.22 1 0
proj/some_func.go:6.22,8.3 1 0
proj/some_func.go:9.2,9.22 1 0
proj/sub/sub.go:3.24,4.22 1 1
proj/sub/sub.go:4.22,6.3 1 0
proj/sub/sub.go:7.2,7.14 1 1
proj/sum/sum.go:3.24,4.12 1 0
proj/sum/sum.go:4.12,6.3 1 0
proj/sum/sum.go:7.2,7.14 1 0
proj/sum/sum.go:3.24,4.12 1 0
proj/sum/sum.go:4.12,6.3 1 0
proj/sum/sum.go:7.2,7.14 1 0

Change a bit sub.go

sub.go

package sub

func Sub(a, b int) int {
    if a == 0 && b == 0 || a == -100 {
        return 0
    }
    return a - b
}

Coverage result after change

mode: set
proj/some_func.go:5.29,6.22 1 1
proj/some_func.go:6.22,8.3 1 0
proj/some_func.go:9.2,9.22 1 1
proj/sub/sub.go:3.24,4.22 1 0  <- Old (Should have been invalidated)
proj/sub/sub.go:4.22,6.3 1 0
proj/sub/sub.go:7.2,7.14 1 0
proj/sum/sum.go:3.24,4.12 1 1
proj/sum/sum.go:4.12,6.3 1 0
proj/sum/sum.go:7.2,7.14 1 1
proj/some_func.go:5.29,6.22 1 0
proj/some_func.go:6.22,8.3 1 0
proj/some_func.go:9.2,9.22 1 0
proj/sub/sub.go:3.24,4.35 1 1  <- New
proj/sub/sub.go:4.35,6.3 1 0
proj/sub/sub.go:7.2,7.14 1 1
proj/sum/sum.go:3.24,4.12 1 0
proj/sum/sum.go:4.12,6.3 1 0
proj/sum/sum.go:7.2,7.14 1 0
proj/sum/sum.go:3.24,4.12 1 0
proj/sum/sum.go:4.12,6.3 1 0
proj/sum/sum.go:7.2,7.14 1 0

Commands

First run

go test -coverpkg=proj/... -coverprofile=cover.out ./proj/...
ok      proj    (cached)    coverage: 44.4% of statements in proj/...
ok      proj/sub    (cached)    coverage: 22.2% of statements in proj/...
    proj/sum        coverage: 0.0% of statements

Second run

go test -coverpkg=proj/... -coverprofile=cover.out ./proj/...
ok      proj    (cached)    coverage: 44.4% of statements in proj/...
ok      proj/sub    0.005s  coverage: 22.2% of statements in proj/...
    proj/sum        coverage: 0.0% of statements

Conclusion

So there are now 2 versions of sub.go:3.24 line in one report and proj/sub/sub.go:3.24,4.22 1 0 doesn't exist, as a result it can break coverage tools which generate cobertura coverage.

What did you see happen?

Duplicate coverage results. The cached coverage and the new coverage.

What did you expect to see?

Only the new coverage results and the cached results to be invalidated.