[go/release-branch.go1.26] [release-branch.go1.26] cmd/go: invalidate test cache when -coverpkg dependencies change

0 views
Skip to first unread message

Gerrit Bot (Gerrit)

unread,
Apr 8, 2026, 10:52:48 PM (2 hours ago) Apr 8
to goph...@pubsubhelper.golang.org, Ryan Currah, golang-co...@googlegroups.com

Gerrit Bot has uploaded the change for review

Commit message

[release-branch.go1.26] cmd/go: invalidate test cache when -coverpkg dependencies change

When running tests with -cover and -coverpkg, the resulting coverage profile includes data from all packages specified in -coverpkg, not just the test package. Previously, the test cache key did not account for changes in these out-of-band covered packages, causing stale coverage profiles to be reused even when source files in covered packages were modified.

Fix this by hashing the BuildActionIDs of the writeCoverMetaAct's dependencies (the compile actions for all covered packages) and incorporating that hash into the coverage profile cache key via cache.Subkey.

The covMeta hash is now computed directly in tryCacheWithID by locating the "write coverage meta-data file" action among the run action's dependencies, keeping all cache logic in one place. When -coverpkg is used without -coverprofile, a sentinel cache entry is written so the cache can still detect when covered packages change.

Fixes #74873

GitHub-Last-Rev: 84aa5376f471704b0ee7be79ab33a1d5bba71c5a
GitHub-Pull-Request: golang/go#74773
Reviewed-on: https://go-review.googlesource.com/c/go/+/690775
Reviewed-by: David Chase <drc...@google.com>
LUCI-TryBot-Result: Go LUCI <golang...@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Matloob <mat...@google.com>
Reviewed-by: Michael Matloob <mat...@golang.org>
Change-Id: Ice84557789e325330759442689d0e28f871858bb
GitHub-Last-Rev: 28a02c4288e8093d7069721f247a707f92f627f6
GitHub-Pull-Request: golang/go#78596

Change diff

diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 9309aa6..1eb7905 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -1405,9 +1405,10 @@
type runCache struct {
disableCache bool // cache should be disabled for this run

- buf *bytes.Buffer
- id1 cache.ActionID
- id2 cache.ActionID
+ buf *bytes.Buffer
+ id1 cache.ActionID
+ id2 cache.ActionID
+ covMeta cache.ActionID // Hash of writeCoverMetaAct dependencies, for invalidating coverage profiles
}

func coverProfTempFile(a *work.Action) string {
@@ -1800,6 +1801,33 @@
return false
}

+ // If we are collecting coverage for out-of-band packages (-coverpkg),
+ // find the writeCoverMetaAct among the run action's dependencies and hash
+ // its deps to ensure the cache invalidates when covered packages change.
+ // Note: the run action's original deps may be wrapped inside a "test barrier"
+ // action, so we search both a.Deps and any barrier's deps.
+ if len(testCoverPkgs) != 0 {
+ searchDeps := a.Deps
+ for _, dep := range a.Deps {
+ if dep.Mode == "test barrier" {
+ searchDeps = dep.Deps
+ break
+ }
+ }
+ for _, dep := range searchDeps {
+ if dep.Mode == "write coverage meta-data file" {
+ h := cache.NewHash("covermeta")
+ for _, metaDep := range dep.Deps {
+ if aid := metaDep.BuildActionID(); aid != "" {
+ fmt.Fprintf(h, "dep %s\n", aid)
+ }
+ }
+ c.covMeta = h.Sum()
+ break
+ }
+ }
+ }
+
var cacheArgs []string
for _, arg := range testArgs {
i := strings.Index(arg, "=")
@@ -1906,7 +1934,7 @@
// Merge cached cover profile data to cover profile.
if testCoverProfile != "" {
// Specifically ignore entry as it will be the same as above.
- cpData, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID))
+ cpData, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID, c.covMeta))
if err != nil {
if cache.DebugTest {
fmt.Fprintf(os.Stderr, "testcache: %s: cached cover profile missing: %v\n", a.Package.ImportPath, err)
@@ -1914,6 +1942,18 @@
return false
}
mergeCoverProfile(cpData)
+ } else if c.covMeta != (cache.ActionID{}) {
+ // If we have a coverage metadata hash but no testCoverProfile, we're collecting
+ // coverage for out-of-band packages. Check if the coverage profile cache is still
+ // valid. If c.covMeta changed (meaning a covered package changed), the coverage
+ // profile cache will miss and we need to re-run the test.
+ _, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID, c.covMeta))
+ if err != nil {
+ if cache.DebugTest {
+ fmt.Fprintf(os.Stderr, "testcache: %s: coverage metadata changed, re-running test: %v\n", a.Package.ImportPath, err)
+ }
+ return false
+ }
}

if len(data) == 0 || data[len(data)-1] != '\n' {
@@ -2104,9 +2144,15 @@
return cache.Subkey(testID, fmt.Sprintf("inputs:%x", testInputsID))
}

-// coverProfileAndInputKey returns the "coverprofile" cache key for the pair (testID, testInputsID).
-func coverProfileAndInputKey(testID, testInputsID cache.ActionID) cache.ActionID {
- return cache.Subkey(testAndInputKey(testID, testInputsID), "coverprofile")
+// coverProfileAndInputKey returns the "coverprofile" cache key.
+// If covMetaID is non-zero, it is included in the hash to ensure coverage profiles are invalidated
+// when the coverage metadata changes (e.g., when source files in covered packages are modified).
+func coverProfileAndInputKey(testID, testInputsID, covMetaID cache.ActionID) cache.ActionID {
+ key := testAndInputKey(testID, testInputsID)
+ if covMetaID != (cache.ActionID{}) {
+ key = cache.Subkey(key, fmt.Sprintf("coverdeps:%x", covMetaID))
+ }
+ return cache.Subkey(key, "coverprofile")
}

func (c *runCache) saveOutput(a *work.Action) {
@@ -2147,7 +2193,11 @@
cache.PutNoVerify(cache.Default(), c.id1, bytes.NewReader(testlog))
cache.PutNoVerify(cache.Default(), testAndInputKey(c.id1, testInputsID), bytes.NewReader(a.TestOutput.Bytes()))
if coverProfile != nil {
- cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID), bytes.NewReader(coverProfile))
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID, c.covMeta), bytes.NewReader(coverProfile))
+ } else if c.covMeta != (cache.ActionID{}) {
+ // Write a sentinel so the else-if branch in tryCacheWithID can verify
+ // that the covMeta hash has not changed since the last run.
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID, c.covMeta), bytes.NewReader(nil))
}
}
if c.id2 != (cache.ActionID{}) {
@@ -2157,7 +2207,10 @@
cache.PutNoVerify(cache.Default(), c.id2, bytes.NewReader(testlog))
cache.PutNoVerify(cache.Default(), testAndInputKey(c.id2, testInputsID), bytes.NewReader(a.TestOutput.Bytes()))
if coverProfile != nil {
- cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id2, testInputsID), bytes.NewReader(coverProfile))
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id2, testInputsID, c.covMeta), bytes.NewReader(coverProfile))
+ } else if c.covMeta != (cache.ActionID{}) {
+ // Sentinel for covMeta validity; see comment in id1 block above.
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id2, testInputsID, c.covMeta), bytes.NewReader(nil))
}
}
}
diff --git a/src/cmd/go/testdata/script/test_cache_coverpkg_bug.txt b/src/cmd/go/testdata/script/test_cache_coverpkg_bug.txt
new file mode 100644
index 0000000..71cd3b3
--- /dev/null
+++ b/src/cmd/go/testdata/script/test_cache_coverpkg_bug.txt
@@ -0,0 +1,134 @@
+# Test for bug where cached coverage profiles with -coverpkg can contain
+# outdated line references when source files are modified.
+# This reproduces the issue where coverage data from cache may reference
+# lines that no longer exist in the updated source files.
+
+[short] skip
+[GODEBUG:gocacheverify=1] skip
+
+# We're testing cache behavior, so start with a clean GOCACHE.
+env GOCACHE=$WORK/cache
+
+# Create a project structure with multiple packages
+# proj/
+# some_func.go
+# some_func_test.go
+# sub/
+# sub.go
+# sub_test.go
+# sum/
+# sum.go
+
+# Switch to the proj directory
+cd proj
+
+# Run tests with -coverpkg to collect coverage for all packages
+go test -coverpkg=proj/... -coverprofile=cover1.out ./...
+stdout 'coverage:'
+
+# Verify the first coverage profile exists and has expected content
+exists cover1.out
+grep -q 'proj/sub/sub.go:' cover1.out
+
+# Run again to ensure caching works
+go test -coverpkg=proj/... -coverprofile=cover1_cached.out ./...
+stdout '\(cached\)'
+stdout 'coverage:'
+
+# Note: Due to the bug, cached coverage profiles may have duplicate entries.
+# The duplicate entries are the entries for the previous file structure and the new file structure.
+
+# Now modify sub.go to change line structure - this will invalidate
+# the cache for the sub package but not for the proj package.
+cp ../sub_modified.go sub/sub.go
+
+# After modifying sub.go, we should not have both old and new line references.
+go test -coverpkg=proj/... -coverprofile=cover2.out ./...
+stdout 'coverage:'
+
+# With the bug present, we would see duplicate entries for the same lines.
+# With the bug fixed, there should be no duplicate or stale entries in the coverage profile.
+grep 'proj/sub/sub.go:' cover2.out
+# The fix should ensure that only the new line format exists, not the old one
+grep 'proj/sub/sub.go:3.24,4.35' cover2.out
+# This should fail if the stale coverage line exists (the bug is present)
+! grep 'proj/sub/sub.go:3.24,4.22' cover2.out
+
+-- proj/go.mod --
+module proj
+
+go 1.21
+
+-- proj/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)
+}
+
+-- proj/some_func_test.go --
+package proj
+
+import (
+ "testing"
+)
+
+func Test_SomeFunc(t *testing.T) {
+ t.Run("test1", func(t *testing.T) {
+ result := SomeFunc(1, 1)
+ if result != 2 {
+ t.Errorf("Expected 2, got %d", result)
+ }
+ })
+}
+
+-- proj/sub/sub.go --
+package sub
+
+func Sub(a, b int) int {
+ if a == 0 && b == 0 {
+ return 0
+ }
+ return a - b
+}
+
+-- proj/sub/sub_test.go --
+package sub
+
+import (
+ "testing"
+)
+
+func Test_Sub(t *testing.T) {
+ t.Run("test_sub1", func(t *testing.T) {
+ result := Sub(1, 1)
+ if result != 0 {
+ t.Errorf("Expected 0, got %d", result)
+ }
+ })
+}
+
+-- proj/sum/sum.go --
+package sum
+
+func Sum(a, b int) int {
+ if a == 0 {
+ return b
+ }
+ return a + b
+}
+
+-- sub_modified.go --
+package sub
+
+func Sub(a, b int) int {
+ if a == 0 && b == 0 || a == -100 {
+ return 0
+ }
+ return a - b
+}
diff --git a/src/cmd/go/testdata/script/test_cache_coverpkg_no_profile.txt b/src/cmd/go/testdata/script/test_cache_coverpkg_no_profile.txt
new file mode 100644
index 0000000..263400a
--- /dev/null
+++ b/src/cmd/go/testdata/script/test_cache_coverpkg_no_profile.txt
@@ -0,0 +1,80 @@
+# Test that the test cache is invalidated when -coverpkg dependencies change,
+# even when -coverprofile is not specified. This exercises the else-if branch
+# in tryCacheWithID that checks covMeta without a cover profile file.
+
+[short] skip
+[GODEBUG:gocacheverify=1] skip
+
+# Start with a clean GOCACHE.
+env GOCACHE=$WORK/cache
+
+cd proj
+
+# Run tests with -cover and -coverpkg but without -coverprofile.
+go test -cover -coverpkg=proj/... ./...
+stdout 'coverage:'
+
+# Run again — should be served from cache.
+go test -cover -coverpkg=proj/... ./...
+stdout '\(cached\)'
+stdout 'coverage:'
+
+# Modify a covered package that is not directly under test.
+cp ../sub_modified.go sub/sub.go
+
+# Run again — the cache must be invalidated because a covered package changed.
+go test -cover -coverpkg=proj/... ./...
+! stdout '\(cached\)'
+stdout 'coverage:'
+
+-- proj/go.mod --
+module proj
+
+go 1.21
+
+-- proj/main.go --
+package proj
+
+import "proj/sub"
+
+func Add(a, b int) int {
+ return sub.Sub(a, -b)
+}
+
+-- proj/main_test.go --
+package proj
+
+import "testing"
+
+func TestAdd(t *testing.T) {
+ if Add(1, 2) != 3 {
+ t.Fatal("expected 3")
+ }
+}
+
+-- proj/sub/sub.go --
+package sub
+
+func Sub(a, b int) int {
+ return a - b
+}
+
+-- proj/sub/sub_test.go --
+package sub
+
+import "testing"
+
+func TestSub(t *testing.T) {
+ if Sub(3, 1) != 2 {
+ t.Fatal("expected 2")
+ }
+}
+
+-- sub_modified.go --
+package sub
+
+// Added a comment to change the source.
+
+func Sub(a, b int) int {
+ return a - b
+}

Change information

Files:
  • M src/cmd/go/internal/test/test.go
  • A src/cmd/go/testdata/script/test_cache_coverpkg_bug.txt
  • A src/cmd/go/testdata/script/test_cache_coverpkg_no_profile.txt
Change size: L
Delta: 3 files changed, 276 insertions(+), 9 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: release-branch.go1.26
Gerrit-Change-Id: Ice84557789e325330759442689d0e28f871858bb
Gerrit-Change-Number: 764360
Gerrit-PatchSet: 1
Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
Gerrit-CC: Ryan Currah <ry...@currah.ca>
unsatisfied_requirement
satisfied_requirement
open
diffy

Ryan Currah (Gerrit)

unread,
Apr 8, 2026, 11:01:23 PM (2 hours ago) Apr 8
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Ryan Currah has uploaded the change for review

Commit message

[release-branch.go1.25] cmd/go: invalidate test cache when -coverpkg dependencies change


When running tests with -cover and -coverpkg, the resulting coverage
profile includes data from all packages specified in -coverpkg, not
just the test package. Previously, the test cache key did not account
for changes in these out-of-band covered packages, causing stale
coverage profiles to be reused even when source files in covered
packages were modified.

Fix this by hashing the BuildActionIDs of the writeCoverMetaAct's
dependencies (the compile actions for all covered packages) and
incorporating that hash into the coverage profile cache key via
cache.Subkey.

The covMeta hash is now computed directly in tryCacheWithID by
locating the "write coverage meta-data file" action among the run
action's dependencies, keeping all cache logic in one place. When
-coverpkg is used without -coverprofile, a sentinel cache entry is
written so the cache can still detect when covered packages change.

Fixes #78582
Change-Id: Ice84557789e325330759442689d0e28f871858bb
GitHub-Last-Rev: 84aa5376f471704b0ee7be79ab33a1d5bba71c5a
GitHub-Pull-Request: golang/go#74773
Reviewed-by: David Chase <drc...@google.com>
Reviewed-by: Michael Matloob <mat...@google.com>
Reviewed-by: Michael Matloob <mat...@golang.org>

Change diff

diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 6c4a6a5..5ba7a88 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -1371,9 +1371,10 @@

type runCache struct {
disableCache bool // cache should be disabled for this run

- buf *bytes.Buffer
- id1 cache.ActionID
- id2 cache.ActionID
+ buf *bytes.Buffer
+ id1 cache.ActionID
+ id2 cache.ActionID
+ covMeta cache.ActionID // Hash of writeCoverMetaAct dependencies, for invalidating coverage profiles
}

func coverProfTempFile(a *work.Action) string {
@@ -1765,6 +1766,33 @@
@@ -1871,7 +1899,7 @@

// Merge cached cover profile data to cover profile.
if testCoverProfile != "" {
// Specifically ignore entry as it will be the same as above.
- cpData, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID))
+ cpData, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID, c.covMeta))
if err != nil {
if cache.DebugTest {
fmt.Fprintf(os.Stderr, "testcache: %s: cached cover profile missing: %v\n", a.Package.ImportPath, err)
@@ -1879,6 +1907,18 @@

return false
}
mergeCoverProfile(cpData)
+ } else if c.covMeta != (cache.ActionID{}) {
+ // If we have a coverage metadata hash but no testCoverProfile, we're collecting
+ // coverage for out-of-band packages. Check if the coverage profile cache is still
+ // valid. If c.covMeta changed (meaning a covered package changed), the coverage
+ // profile cache will miss and we need to re-run the test.
+ _, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID, c.covMeta))
+ if err != nil {
+ if cache.DebugTest {
+ fmt.Fprintf(os.Stderr, "testcache: %s: coverage metadata changed, re-running test: %v\n", a.Package.ImportPath, err)
+ }
+ return false
+ }
}

if len(data) == 0 || data[len(data)-1] != '\n' {
@@ -2069,9 +2109,15 @@

return cache.Subkey(testID, fmt.Sprintf("inputs:%x", testInputsID))
}

-// coverProfileAndInputKey returns the "coverprofile" cache key for the pair (testID, testInputsID).
-func coverProfileAndInputKey(testID, testInputsID cache.ActionID) cache.ActionID {
- return cache.Subkey(testAndInputKey(testID, testInputsID), "coverprofile")
+// coverProfileAndInputKey returns the "coverprofile" cache key.
+// If covMetaID is non-zero, it is included in the hash to ensure coverage profiles are invalidated
+// when the coverage metadata changes (e.g., when source files in covered packages are modified).
+func coverProfileAndInputKey(testID, testInputsID, covMetaID cache.ActionID) cache.ActionID {
+ key := testAndInputKey(testID, testInputsID)
+ if covMetaID != (cache.ActionID{}) {
+ key = cache.Subkey(key, fmt.Sprintf("coverdeps:%x", covMetaID))
+ }
+ return cache.Subkey(key, "coverprofile")
}

func (c *runCache) saveOutput(a *work.Action) {
@@ -2112,7 +2158,11 @@

cache.PutNoVerify(cache.Default(), c.id1, bytes.NewReader(testlog))
cache.PutNoVerify(cache.Default(), testAndInputKey(c.id1, testInputsID), bytes.NewReader(a.TestOutput.Bytes()))
if coverProfile != nil {
- cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID), bytes.NewReader(coverProfile))
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID, c.covMeta), bytes.NewReader(coverProfile))
+ } else if c.covMeta != (cache.ActionID{}) {
+ // Write a sentinel so the else-if branch in tryCacheWithID can verify
+ // that the covMeta hash has not changed since the last run.
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID, c.covMeta), bytes.NewReader(nil))
}
}
if c.id2 != (cache.ActionID{}) {
@@ -2122,7 +2172,10 @@
Gerrit-Branch: release-branch.go1.25
Gerrit-Change-Id: Ice84557789e325330759442689d0e28f871858bb
Gerrit-Change-Number: 764380
Gerrit-PatchSet: 1
Gerrit-Owner: Ryan Currah <ry...@currah.ca>
unsatisfied_requirement
satisfied_requirement
open
diffy

Ryan Currah (Gerrit)

unread,
Apr 8, 2026, 11:04:01 PM (2 hours ago) Apr 8
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Ryan Currah has uploaded the change for review

Commit message

[release-branch.go1.26] cmd/go: invalidate test cache when -coverpkg dependencies change


When running tests with -cover and -coverpkg, the resulting coverage
profile includes data from all packages specified in -coverpkg, not
just the test package. Previously, the test cache key did not account
for changes in these out-of-band covered packages, causing stale
coverage profiles to be reused even when source files in covered
packages were modified.

Fix this by hashing the BuildActionIDs of the writeCoverMetaAct's
dependencies (the compile actions for all covered packages) and
incorporating that hash into the coverage profile cache key via
cache.Subkey.

The covMeta hash is now computed directly in tryCacheWithID by
locating the "write coverage meta-data file" action among the run
action's dependencies, keeping all cache logic in one place. When
-coverpkg is used without -coverprofile, a sentinel cache entry is
written so the cache can still detect when covered packages change.

Fixes #78583
GitHub-Last-Rev: 84aa5376f471704b0ee7be79ab33a1d5bba71c5a
GitHub-Pull-Request: golang/go#74773
Reviewed-by: David Chase <drc...@google.com>
Reviewed-by: Michael Matloob <mat...@google.com>
Reviewed-by: Michael Matloob <mat...@golang.org>
Change-Id: Ibcf353c215dfa8804dfa948e3ba2ea6b4ae0806f

Change diff

diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 9309aa6..1eb7905 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -1405,9 +1405,10 @@

type runCache struct {
disableCache bool // cache should be disabled for this run

- buf *bytes.Buffer
- id1 cache.ActionID
- id2 cache.ActionID
+ buf *bytes.Buffer
+ id1 cache.ActionID
+ id2 cache.ActionID
+ covMeta cache.ActionID // Hash of writeCoverMetaAct dependencies, for invalidating coverage profiles
}

func coverProfTempFile(a *work.Action) string {
@@ -1800,6 +1801,33 @@
@@ -1906,7 +1934,7 @@

// Merge cached cover profile data to cover profile.
if testCoverProfile != "" {
// Specifically ignore entry as it will be the same as above.
- cpData, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID))
+ cpData, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID, c.covMeta))
if err != nil {
if cache.DebugTest {
fmt.Fprintf(os.Stderr, "testcache: %s: cached cover profile missing: %v\n", a.Package.ImportPath, err)
@@ -1914,6 +1942,18 @@

return false
}
mergeCoverProfile(cpData)
+ } else if c.covMeta != (cache.ActionID{}) {
+ // If we have a coverage metadata hash but no testCoverProfile, we're collecting
+ // coverage for out-of-band packages. Check if the coverage profile cache is still
+ // valid. If c.covMeta changed (meaning a covered package changed), the coverage
+ // profile cache will miss and we need to re-run the test.
+ _, _, err := cache.GetFile(cache.Default(), coverProfileAndInputKey(testID, testInputsID, c.covMeta))
+ if err != nil {
+ if cache.DebugTest {
+ fmt.Fprintf(os.Stderr, "testcache: %s: coverage metadata changed, re-running test: %v\n", a.Package.ImportPath, err)
+ }
+ return false
+ }
}

if len(data) == 0 || data[len(data)-1] != '\n' {
@@ -2104,9 +2144,15 @@

return cache.Subkey(testID, fmt.Sprintf("inputs:%x", testInputsID))
}

-// coverProfileAndInputKey returns the "coverprofile" cache key for the pair (testID, testInputsID).
-func coverProfileAndInputKey(testID, testInputsID cache.ActionID) cache.ActionID {
- return cache.Subkey(testAndInputKey(testID, testInputsID), "coverprofile")
+// coverProfileAndInputKey returns the "coverprofile" cache key.
+// If covMetaID is non-zero, it is included in the hash to ensure coverage profiles are invalidated
+// when the coverage metadata changes (e.g., when source files in covered packages are modified).
+func coverProfileAndInputKey(testID, testInputsID, covMetaID cache.ActionID) cache.ActionID {
+ key := testAndInputKey(testID, testInputsID)
+ if covMetaID != (cache.ActionID{}) {
+ key = cache.Subkey(key, fmt.Sprintf("coverdeps:%x", covMetaID))
+ }
+ return cache.Subkey(key, "coverprofile")
}

func (c *runCache) saveOutput(a *work.Action) {
@@ -2147,7 +2193,11 @@

cache.PutNoVerify(cache.Default(), c.id1, bytes.NewReader(testlog))
cache.PutNoVerify(cache.Default(), testAndInputKey(c.id1, testInputsID), bytes.NewReader(a.TestOutput.Bytes()))
if coverProfile != nil {
- cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID), bytes.NewReader(coverProfile))
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID, c.covMeta), bytes.NewReader(coverProfile))
+ } else if c.covMeta != (cache.ActionID{}) {
+ // Write a sentinel so the else-if branch in tryCacheWithID can verify
+ // that the covMeta hash has not changed since the last run.
+ cache.PutNoVerify(cache.Default(), coverProfileAndInputKey(c.id1, testInputsID, c.covMeta), bytes.NewReader(nil))
}
}
if c.id2 != (cache.ActionID{}) {
@@ -2157,7 +2207,10 @@
Gerrit-Branch: release-branch.go1.26
Gerrit-Change-Id: Ibcf353c215dfa8804dfa948e3ba2ea6b4ae0806f
Gerrit-Change-Number: 764381
unsatisfied_requirement
satisfied_requirement
open
diffy

Ryan Currah (Gerrit)

unread,
Apr 8, 2026, 11:09:38 PM (1 hour ago) Apr 8
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Ryan Currah abandoned this change.

View Change

Abandoned

Ryan Currah abandoned this change

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: abandon
unsatisfied_requirement
satisfied_requirement
open
diffy

Ryan Currah (Gerrit)

unread,
Apr 8, 2026, 11:24:49 PM (1 hour ago) Apr 8
to Gerrit Bot, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Ryan Currah added 1 comment

Patchset-level comments
File-level comment, Patchset 1 (Latest):
Ryan Currah . resolved

Please abandon this CL. It was created automatically from my GitHub PR and I am unable to abandon it myself. It is blocking me from submitting the proper cherry-pick CL for this backport. The replacement CL will be submitted once this is abandoned. Related backport issue: #78583.

Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: release-branch.go1.26
Gerrit-Change-Id: Ice84557789e325330759442689d0e28f871858bb
Gerrit-Change-Number: 764360
Gerrit-PatchSet: 1
Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
Gerrit-CC: Ryan Currah <ry...@currah.ca>
Gerrit-Comment-Date: Thu, 09 Apr 2026 03:24:45 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages