Skip to content

Commit 36e75f6

Browse files
committed
cmd/go: fix percent covered problems with -coverpkg
This patch fixes some problems with how "go test -cover" was handling tests involving A) multiple package tests and B) multiple packages matched by "-coverpkg". In such scenarios the expectation is that the percent statements covered metric for each package needs to be relative to all statements in all packages matched by the -coverpkg arg (this aspect of the reporting here was broken as part of GOEXPERIMENT=coverageredesign). The new scheme works as follows. If -coverpkg is in effect and is matching multiple packages, and we have multiple test targets, then: - each time a package is built for coverage, capture a meta-data file fragment corresponding to just the meta-data for that package. - create a new "writeCoverMeta" action, and interpose it between the build actions for the covered packages and the run actions. The "writeCoverMeta" action at runtime will emit a file "metafiles.txt" containing a table mapping each covered package (by import path) to its corresponding meta-data file fragment. - pass in the "metafiles.txt" file to each run action, so that when the test finishes running it will have an accurate picture of _all_ covered packages, permitting it to calculate the correct percentage. Concrete example: suppose we have a top level directory with three package subdirs, "a", "b", and "c", and from the top level, a user runs "go test -coverpkg=./... ./...". This will result in (roughly) the following action graph: build("a") build("b") build("c") | | | link("a.test") link("b.test") link("c.test") | | | run("a.test") run("b.test") run("c.test") | | | print print print With the new scheme, the action graph is augmented with a writeCoverMeta action and additional dependence edges to form build("a") build("b") build("c") | \ / | / | | v v | / | | writecovmeta<-|-------------+ | | ||| | | | ||\ | | link("a.test")/\ \ link("b.test") link("c.test") | / \ +-|--------------+ | | / \ | \ | | v v | v | run("a.test") run("b.test") run("c.test") | | | print print print A note on init functions: prior to GOEXPERIMENT=coverageredesign the "-coverpkg=..." flag was implemented by force-importing all packages matched by "-coverpkg" into each instrumented package. This meant that for the example above, when executing "a.test", the init function for package "c" would fire even if package "a" did not ordinarily import package "c". The new implementation does not do this sort of forced importing, meaning that the coverage percentages can be slightly different between 1.21 and 1.19 if there are user-written init funcs. Fixes #58770. Updates #24570. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest Change-Id: I7749ed205dce81b96ad7f74ab98bc1e90e377302 Reviewed-on: https://go-review.googlesource.com/c/go/+/495452 Reviewed-by: Bryan Mills <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent d1cb5c0 commit 36e75f6

File tree

6 files changed

+461
-21
lines changed

6 files changed

+461
-21
lines changed

src/cmd/go/internal/test/test.go

Lines changed: 122 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"context"
1010
"errors"
1111
"fmt"
12+
"internal/coverage"
1213
"internal/platform"
1314
"io"
1415
"io/fs"
@@ -848,6 +849,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
848849
}()
849850

850851
var builds, runs, prints []*work.Action
852+
var writeCoverMetaAct *work.Action
851853

852854
if cfg.BuildCoverPkg != nil {
853855
match := make([]func(*load.Package) bool, len(cfg.BuildCoverPkg))
@@ -859,6 +861,61 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
859861
// patterns.
860862
plist := load.TestPackageList(ctx, pkgOpts, pkgs)
861863
testCoverPkgs = load.SelectCoverPackages(plist, match, "test")
864+
if cfg.Experiment.CoverageRedesign && len(testCoverPkgs) > 0 {
865+
// create a new singleton action that will collect up the
866+
// meta-data files from all of the packages mentioned in
867+
// "-coverpkg" and write them to a summary file. This new
868+
// action will depend on all the build actions for the
869+
// test packages, and all the run actions for these
870+
// packages will depend on it. Motivating example:
871+
// supposed we have a top level directory with three
872+
// package subdirs, "a", "b", and "c", and
873+
// from the top level, a user runs "go test -coverpkg=./... ./...".
874+
// This will result in (roughly) the following action graph:
875+
//
876+
// build("a") build("b") build("c")
877+
// | | |
878+
// link("a.test") link("b.test") link("c.test")
879+
// | | |
880+
// run("a.test") run("b.test") run("c.test")
881+
// | | |
882+
// print print print
883+
//
884+
// When -coverpkg=<pattern> is in effect, we want to
885+
// express the coverage percentage for each package as a
886+
// fraction of *all* the statements that match the
887+
// pattern, hence if "c" doesn't import "a", we need to
888+
// pass as meta-data file for "a" (emitted during the
889+
// package "a" build) to the package "c" run action, so
890+
// that it can be incorporated with "c"'s regular
891+
// metadata. To do this, we add edges from each compile
892+
// action to a "writeCoverMeta" action, then from the
893+
// writeCoverMeta action to each run action. Updated
894+
// graph:
895+
//
896+
// build("a") build("b") build("c")
897+
// | \ / | / |
898+
// | v v | / |
899+
// | writemeta <-|-------------+ |
900+
// | ||| | |
901+
// | ||\ | |
902+
// link("a.test")/\ \ link("b.test") link("c.test")
903+
// | / \ +-|--------------+ |
904+
// | / \ | \ |
905+
// | v v | v |
906+
// run("a.test") run("b.test") run("c.test")
907+
// | | |
908+
// print print print
909+
//
910+
writeCoverMetaAct = &work.Action{
911+
Mode: "write coverage meta-data file",
912+
Actor: work.ActorFunc(work.WriteCoverMetaFilesFile),
913+
Objdir: b.NewObjdir(),
914+
}
915+
for _, p := range testCoverPkgs {
916+
p.Internal.Cover.GenMeta = true
917+
}
918+
}
862919
}
863920

864921
// Inform the compiler that it should instrument the binary at
@@ -915,8 +972,11 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
915972
// design). Do this here (as opposed to in builderTest) so
916973
// as to handle the case where we're testing multiple
917974
// packages and one of the earlier packages imports a
918-
// later package.
975+
// later package. Note that if -coverpkg is in effect
976+
// p.Internal.Cover.GenMeta will wind up being set for
977+
// all matching packages.
919978
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 &&
979+
cfg.BuildCoverPkg == nil &&
920980
cfg.Experiment.CoverageRedesign {
921981
p.Internal.Cover.GenMeta = true
922982
}
@@ -925,7 +985,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
925985

926986
// Prepare build + run + print actions for all packages being tested.
927987
for _, p := range pkgs {
928-
buildTest, runTest, printTest, err := builderTest(b, ctx, pkgOpts, p, allImports[p])
988+
buildTest, runTest, printTest, err := builderTest(b, ctx, pkgOpts, p, allImports[p], writeCoverMetaAct)
929989
if err != nil {
930990
str := err.Error()
931991
str = strings.TrimPrefix(str, "\n")
@@ -987,13 +1047,12 @@ var windowsBadWords = []string{
9871047
"update",
9881048
}
9891049

990-
func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, p *load.Package, imported bool) (buildAction, runAction, printAction *work.Action, err error) {
1050+
func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts, p *load.Package, imported bool, writeCoverMetaAct *work.Action) (buildAction, runAction, printAction *work.Action, err error) {
9911051
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
9921052
if cfg.BuildCover && cfg.Experiment.CoverageRedesign {
993-
if !p.Internal.Cover.GenMeta {
994-
panic("internal error: Cover.GenMeta should already be set")
1053+
if p.Internal.Cover.GenMeta {
1054+
p.Internal.Cover.Mode = cfg.BuildCoverMode
9951055
}
996-
p.Internal.Cover.Mode = cfg.BuildCoverMode
9971056
}
9981057
build := b.CompileAction(work.ModeBuild, work.ModeBuild, p)
9991058
run := &work.Action{
@@ -1003,6 +1062,23 @@ func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts,
10031062
Package: p,
10041063
IgnoreFail: true, // run (prepare output) even if build failed
10051064
}
1065+
if writeCoverMetaAct != nil {
1066+
// There is no real "run" for this package (since there
1067+
// are no tests), but if coverage is turned on, we can
1068+
// collect coverage data for the code in the package by
1069+
// asking cmd/cover for a static meta-data file as part of
1070+
// the package build. This static meta-data file is then
1071+
// consumed by a pseudo-action (writeCoverMetaAct) that
1072+
// adds it to a summary file, then this summary file is
1073+
// consumed by the various "run test" actions. Below we
1074+
// add a dependence edge between the build action and the
1075+
// "write meta files" pseudo-action, and then another dep
1076+
// from writeCoverMetaAct to the run action. See the
1077+
// comment in runTest() at the definition of
1078+
// writeCoverMetaAct for more details.
1079+
run.Deps = append(run.Deps, writeCoverMetaAct)
1080+
writeCoverMetaAct.Deps = append(writeCoverMetaAct.Deps, build)
1081+
}
10061082
addTestVet(b, p, run, nil)
10071083
print := &work.Action{
10081084
Mode: "test print",
@@ -1140,22 +1216,42 @@ func builderTest(b *work.Builder, ctx context.Context, pkgOpts load.PackageOpts,
11401216
runAction = installAction // make sure runAction != nil even if not running test
11411217
}
11421218
}
1219+
11431220
var vetRunAction *work.Action
11441221
if testC {
11451222
printAction = &work.Action{Mode: "test print (nop)", Package: p, Deps: []*work.Action{runAction}} // nop
11461223
vetRunAction = printAction
11471224
} else {
11481225
// run test
1149-
r := new(runTestActor)
1226+
rta := &runTestActor{
1227+
writeCoverMetaAct: writeCoverMetaAct,
1228+
}
11501229
runAction = &work.Action{
11511230
Mode: "test run",
1152-
Actor: r,
1231+
Actor: rta,
11531232
Deps: []*work.Action{buildAction},
11541233
Package: p,
11551234
IgnoreFail: true, // run (prepare output) even if build failed
1156-
TryCache: r.c.tryCache,
1157-
Objdir: testDir,
1235+
TryCache: rta.c.tryCache,
1236+
}
1237+
if writeCoverMetaAct != nil {
1238+
// If writeCoverMetaAct != nil, this indicates that our
1239+
// "go test -coverpkg" run actions will need to read the
1240+
// meta-files summary file written by writeCoverMetaAct,
1241+
// so add a dependence edge from writeCoverMetaAct to the
1242+
// run action.
1243+
runAction.Deps = append(runAction.Deps, writeCoverMetaAct)
1244+
if !p.IsTestOnly() {
1245+
// Package p is not test only, meaning that the build
1246+
// action for p may generate a static meta-data file.
1247+
// Add a dependence edge from p to writeCoverMetaAct,
1248+
// which needs to know the name of that meta-data
1249+
// file.
1250+
compileAction := b.CompileAction(work.ModeBuild, work.ModeBuild, p)
1251+
writeCoverMetaAct.Deps = append(writeCoverMetaAct.Deps, compileAction)
1252+
}
11581253
}
1254+
runAction.Objdir = testDir
11591255
vetRunAction = runAction
11601256
cleanAction = &work.Action{
11611257
Mode: "test clean",
@@ -1217,6 +1313,12 @@ var tooManyFuzzTestsToFuzz = []byte("\ntesting: warning: -fuzz matches more than
12171313
type runTestActor struct {
12181314
c runCache
12191315

1316+
// writeCoverMetaAct points to the pseudo-action for collecting
1317+
// coverage meta-data files for selected -cover test runs. See the
1318+
// comment in runTest at the definition of writeCoverMetaAct for
1319+
// more details.
1320+
writeCoverMetaAct *work.Action
1321+
12201322
// sequencing of json start messages, to preserve test order
12211323
prev <-chan struct{} // wait to start until prev is closed
12221324
next chan<- struct{} // close next once the next test can start.
@@ -1391,6 +1493,16 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
13911493
base.Fatalf("failed to create temporary dir: %v", err)
13921494
}
13931495
coverdirArg = append(coverdirArg, "-test.gocoverdir="+gcd)
1496+
if r.writeCoverMetaAct != nil {
1497+
// Copy the meta-files file over into the test's coverdir
1498+
// directory so that the coverage runtime support will be
1499+
// able to find it.
1500+
src := r.writeCoverMetaAct.Objdir + coverage.MetaFilesFileName
1501+
dst := filepath.Join(gcd, coverage.MetaFilesFileName)
1502+
if err := b.CopyFile(dst, src, 0666, false); err != nil {
1503+
return err
1504+
}
1505+
}
13941506
// Even though we are passing the -test.gocoverdir option to
13951507
// the test binary, also set GOCOVERDIR as well. This is
13961508
// intended to help with tests that run "go build" to build

src/cmd/go/internal/work/cover.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import (
1010
"cmd/go/internal/base"
1111
"cmd/go/internal/cfg"
1212
"cmd/go/internal/str"
13+
"context"
14+
"encoding/json"
1315
"fmt"
16+
"internal/coverage"
1417
"internal/coverage/covcmd"
1518
"io"
1619
"os"
@@ -93,3 +96,57 @@ func WriteCoverageProfile(b *Builder, runAct *Action, mf, outf string, w io.Writ
9396
_, werr := w.Write(output)
9497
return werr
9598
}
99+
100+
// WriteCoverMetaFilesFile writes out a summary file ("meta-files
101+
// file") as part of the action function for the "writeCoverMeta"
102+
// pseudo action employed during "go test -coverpkg" runs where there
103+
// are multiple tests and multiple packages covered. It builds up a
104+
// table mapping package import path to meta-data file fragment and
105+
// writes it out to a file where it can be read by the various test
106+
// run actions. Note that this function has to be called A) after the
107+
// build actions are complete for all packages being tested, and B)
108+
// before any of the "run test" actions for those packages happen.
109+
// This requirement is enforced by adding making this action ("a")
110+
// dependent on all test package build actions, and making all test
111+
// run actions dependent on this action.
112+
func WriteCoverMetaFilesFile(b *Builder, ctx context.Context, a *Action) error {
113+
// Build the metafilecollection object.
114+
var collection coverage.MetaFileCollection
115+
for i := range a.Deps {
116+
dep := a.Deps[i]
117+
if dep.Mode != "build" {
118+
panic("unexpected mode " + dep.Mode)
119+
}
120+
metaFilesFile := dep.Objdir + covcmd.MetaFileForPackage(dep.Package.ImportPath)
121+
// Check to make sure the meta-data file fragment exists
122+
// and has content (may be empty if package has no functions).
123+
if fi, err := os.Stat(metaFilesFile); err != nil {
124+
continue
125+
} else if fi.Size() == 0 {
126+
continue
127+
}
128+
collection.ImportPaths = append(collection.ImportPaths, dep.Package.ImportPath)
129+
collection.MetaFileFragments = append(collection.MetaFileFragments, metaFilesFile)
130+
}
131+
132+
// Serialize it.
133+
data, err := json.Marshal(collection)
134+
if err != nil {
135+
return fmt.Errorf("marshal MetaFileCollection: %v", err)
136+
}
137+
data = append(data, '\n') // makes -x output more readable
138+
139+
// Create the directory for this action's objdir and
140+
// then write out the serialized collection
141+
// to a file in the directory.
142+
if err := b.Mkdir(a.Objdir); err != nil {
143+
return err
144+
}
145+
mfpath := a.Objdir + coverage.MetaFilesFileName
146+
if err := b.writeFile(mfpath, data); err != nil {
147+
return fmt.Errorf("writing metafiles file: %v", err)
148+
}
149+
150+
// We're done.
151+
return nil
152+
}

src/cmd/go/internal/work/exec.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ OverlayLoop:
619619
from := mkAbs(p.Dir, fs[i])
620620
opath, _ := fsys.OverlayPath(from)
621621
dst := objdir + filepath.Base(fs[i])
622-
if err := b.copyFile(dst, opath, 0666, false); err != nil {
622+
if err := b.CopyFile(dst, opath, 0666, false); err != nil {
623623
return err
624624
}
625625
a.nonGoOverlay[from] = dst
@@ -894,17 +894,17 @@ OverlayLoop:
894894
switch {
895895
case strings.HasSuffix(name, _goos_goarch):
896896
targ := file[:len(name)-len(_goos_goarch)] + "_GOOS_GOARCH." + ext
897-
if err := b.copyFile(objdir+targ, filepath.Join(p.Dir, file), 0666, true); err != nil {
897+
if err := b.CopyFile(objdir+targ, filepath.Join(p.Dir, file), 0666, true); err != nil {
898898
return err
899899
}
900900
case strings.HasSuffix(name, _goarch):
901901
targ := file[:len(name)-len(_goarch)] + "_GOARCH." + ext
902-
if err := b.copyFile(objdir+targ, filepath.Join(p.Dir, file), 0666, true); err != nil {
902+
if err := b.CopyFile(objdir+targ, filepath.Join(p.Dir, file), 0666, true); err != nil {
903903
return err
904904
}
905905
case strings.HasSuffix(name, _goos):
906906
targ := file[:len(name)-len(_goos)] + "_GOOS." + ext
907-
if err := b.copyFile(objdir+targ, filepath.Join(p.Dir, file), 0666, true); err != nil {
907+
if err := b.CopyFile(objdir+targ, filepath.Join(p.Dir, file), 0666, true); err != nil {
908908
return err
909909
}
910910
}
@@ -1029,7 +1029,7 @@ func (b *Builder) loadCachedObjdirFile(a *Action, c cache.Cache, name string) er
10291029
if err != nil {
10301030
return err
10311031
}
1032-
return b.copyFile(a.Objdir+name, cached, 0666, true)
1032+
return b.CopyFile(a.Objdir+name, cached, 0666, true)
10331033
}
10341034

10351035
func (b *Builder) cacheCgoHdr(a *Action) {
@@ -1884,23 +1884,23 @@ func (b *Builder) moveOrCopyFile(dst, src string, perm fs.FileMode, force bool)
18841884

18851885
// If the source is in the build cache, we need to copy it.
18861886
if strings.HasPrefix(src, cache.DefaultDir()) {
1887-
return b.copyFile(dst, src, perm, force)
1887+
return b.CopyFile(dst, src, perm, force)
18881888
}
18891889

18901890
// On Windows, always copy the file, so that we respect the NTFS
18911891
// permissions of the parent folder. https://golang.org/issue/22343.
18921892
// What matters here is not cfg.Goos (the system we are building
18931893
// for) but runtime.GOOS (the system we are building on).
18941894
if runtime.GOOS == "windows" {
1895-
return b.copyFile(dst, src, perm, force)
1895+
return b.CopyFile(dst, src, perm, force)
18961896
}
18971897

18981898
// If the destination directory has the group sticky bit set,
18991899
// we have to copy the file to retain the correct permissions.
19001900
// https://golang.org/issue/18878
19011901
if fi, err := os.Stat(filepath.Dir(dst)); err == nil {
19021902
if fi.IsDir() && (fi.Mode()&fs.ModeSetgid) != 0 {
1903-
return b.copyFile(dst, src, perm, force)
1903+
return b.CopyFile(dst, src, perm, force)
19041904
}
19051905
}
19061906

@@ -1930,11 +1930,11 @@ func (b *Builder) moveOrCopyFile(dst, src string, perm fs.FileMode, force bool)
19301930
}
19311931
}
19321932

1933-
return b.copyFile(dst, src, perm, force)
1933+
return b.CopyFile(dst, src, perm, force)
19341934
}
19351935

19361936
// copyFile is like 'cp src dst'.
1937-
func (b *Builder) copyFile(dst, src string, perm fs.FileMode, force bool) error {
1937+
func (b *Builder) CopyFile(dst, src string, perm fs.FileMode, force bool) error {
19381938
if cfg.BuildN || cfg.BuildX {
19391939
b.Showcmd("", "cp %s %s", src, dst)
19401940
if cfg.BuildN {

src/cmd/go/internal/work/gccgo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func (tools gccgoToolchain) link(b *Builder, root *Action, out, importcfg string
299299
readAndRemoveCgoFlags := func(archive string) (string, error) {
300300
newID++
301301
newArchive := root.Objdir + fmt.Sprintf("_pkg%d_.a", newID)
302-
if err := b.copyFile(newArchive, archive, 0666, false); err != nil {
302+
if err := b.CopyFile(newArchive, archive, 0666, false); err != nil {
303303
return "", err
304304
}
305305
if cfg.BuildN || cfg.BuildX {

0 commit comments

Comments
 (0)