# 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:4.2,4.35' cover2.out # This should fail if the stale coverage line exists (the bug is present) ! grep 'proj/sub/sub.go:4.2,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 }