Skip to content

Commit 283558e

Browse files
committed
cmd/go: allow -coverprofile with multiple packages being tested
It's easy to merge the coverage profiles from the multiple executed tests, so do that. Also ensures that at least an empty coverage profile is always written. Fixes #6909. Fixes #18909. Change-Id: I28b88e1fb0fb773c8f57e956b18904dc388cdd82 Reviewed-on: https://go-review.googlesource.com/76875 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: David Crawshaw <[email protected]>
1 parent 36ef06c commit 283558e

File tree

4 files changed

+131
-26
lines changed

4 files changed

+131
-26
lines changed

src/cmd/go/go_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,7 +2338,6 @@ func checkCoverage(tg *testgoData, data string) {
23382338
if regexp.MustCompile(`[^0-9]0\.0%`).MatchString(data) {
23392339
tg.t.Error("some coverage results are 0.0%")
23402340
}
2341-
tg.t.Log(data)
23422341
}
23432342

23442343
func TestCoverageRuns(t *testing.T) {
@@ -2355,21 +2354,31 @@ func TestCoverageRuns(t *testing.T) {
23552354
}
23562355

23572356
// Check that coverage analysis uses set mode.
2357+
// Also check that coverage profiles merge correctly.
23582358
func TestCoverageUsesSetMode(t *testing.T) {
23592359
if testing.Short() {
23602360
t.Skip("don't build libraries for coverage in short mode")
23612361
}
23622362
tg := testgo(t)
23632363
defer tg.cleanup()
23642364
tg.creatingTemp("testdata/cover.out")
2365-
tg.run("test", "-short", "-cover", "encoding/binary", "-coverprofile=testdata/cover.out")
2365+
tg.run("test", "-short", "-cover", "encoding/binary", "errors", "-coverprofile=testdata/cover.out")
23662366
data := tg.getStdout() + tg.getStderr()
23672367
if out, err := ioutil.ReadFile("testdata/cover.out"); err != nil {
23682368
t.Error(err)
23692369
} else {
23702370
if !bytes.Contains(out, []byte("mode: set")) {
23712371
t.Error("missing mode: set")
23722372
}
2373+
if !bytes.Contains(out, []byte("errors.go")) {
2374+
t.Error("missing errors.go")
2375+
}
2376+
if !bytes.Contains(out, []byte("binary.go")) {
2377+
t.Error("missing binary.go")
2378+
}
2379+
if bytes.Count(out, []byte("mode: set")) != 1 {
2380+
t.Error("too many mode: set")
2381+
}
23732382
}
23742383
checkCoverage(tg, data)
23752384
}

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package test
6+
7+
import (
8+
"cmd/go/internal/base"
9+
"fmt"
10+
"io"
11+
"os"
12+
"sync"
13+
)
14+
15+
var coverMerge struct {
16+
f *os.File
17+
sync.Mutex // for f.Write
18+
}
19+
20+
// initCoverProfile initializes the test coverage profile.
21+
// It must be run before any calls to mergeCoverProfile or closeCoverProfile.
22+
// Using this function clears the profile in case it existed from a previous run,
23+
// or in case it doesn't exist and the test is going to fail to create it (or not run).
24+
func initCoverProfile() {
25+
if testCoverProfile == "" {
26+
return
27+
}
28+
29+
// No mutex - caller's responsibility to call with no racing goroutines.
30+
f, err := os.Create(testCoverProfile)
31+
if err != nil {
32+
base.Fatalf("%v", err)
33+
}
34+
_, err = fmt.Fprintf(f, "mode: %s\n", testCoverMode)
35+
if err != nil {
36+
base.Fatalf("%v", err)
37+
}
38+
coverMerge.f = f
39+
}
40+
41+
// mergeCoverProfile merges file into the profile stored in testCoverProfile.
42+
// It prints any errors it encounters to ew.
43+
func mergeCoverProfile(ew io.Writer, file string) {
44+
if coverMerge.f == nil {
45+
return
46+
}
47+
coverMerge.Lock()
48+
defer coverMerge.Unlock()
49+
50+
expect := fmt.Sprintf("mode: %s\n", testCoverMode)
51+
buf := make([]byte, len(expect))
52+
r, err := os.Open(file)
53+
if err != nil {
54+
// Test did not create profile, which is OK.
55+
return
56+
}
57+
defer r.Close()
58+
59+
n, err := io.ReadFull(r, buf)
60+
if n == 0 {
61+
return
62+
}
63+
if err != nil || string(buf) != expect {
64+
fmt.Fprintf(ew, "error: test wrote malformed coverage profile.\n")
65+
return
66+
}
67+
_, err = io.Copy(coverMerge.f, r)
68+
if err != nil {
69+
fmt.Fprintf(ew, "error: saving coverage profile: %v\n", err)
70+
}
71+
}
72+
73+
func closeCoverProfile() {
74+
if coverMerge.f == nil {
75+
return
76+
}
77+
if err := coverMerge.f.Close(); err != nil {
78+
base.Errorf("closing coverage profile: %v", err)
79+
}
80+
}

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

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -451,24 +451,25 @@ See the documentation of the testing package for more information.
451451
}
452452

453453
var (
454-
testC bool // -c flag
455-
testCover bool // -cover flag
456-
testCoverMode string // -covermode flag
457-
testCoverPaths []string // -coverpkg flag
458-
testCoverPkgs []*load.Package // -coverpkg flag
459-
testO string // -o flag
460-
testProfile bool // some profiling flag
461-
testNeedBinary bool // profile needs to keep binary around
462-
testJSON bool // -json flag
463-
testV bool // -v flag
464-
testTimeout string // -timeout flag
465-
testArgs []string
466-
testBench bool
467-
testList bool
468-
testShowPass bool // show passing output
469-
testVetList string // -vet flag
470-
pkgArgs []string
471-
pkgs []*load.Package
454+
testC bool // -c flag
455+
testCover bool // -cover flag
456+
testCoverMode string // -covermode flag
457+
testCoverPaths []string // -coverpkg flag
458+
testCoverPkgs []*load.Package // -coverpkg flag
459+
testCoverProfile string // -coverprofile flag
460+
testO string // -o flag
461+
testProfile string // profiling flag that limits test to one package
462+
testNeedBinary bool // profile needs to keep binary around
463+
testJSON bool // -json flag
464+
testV bool // -v flag
465+
testTimeout string // -timeout flag
466+
testArgs []string
467+
testBench bool
468+
testList bool
469+
testShowPass bool // show passing output
470+
testVetList string // -vet flag
471+
pkgArgs []string
472+
pkgs []*load.Package
472473

473474
testKillTimeout = 10 * time.Minute
474475
)
@@ -525,9 +526,11 @@ func runTest(cmd *base.Command, args []string) {
525526
if testO != "" && len(pkgs) != 1 {
526527
base.Fatalf("cannot use -o flag with multiple packages")
527528
}
528-
if testProfile && len(pkgs) != 1 {
529-
base.Fatalf("cannot use test profile flag with multiple packages")
529+
if testProfile != "" && len(pkgs) != 1 {
530+
base.Fatalf("cannot use %s flag with multiple packages", testProfile)
530531
}
532+
initCoverProfile()
533+
defer closeCoverProfile()
531534

532535
// If a test timeout was given and is parseable, set our kill timeout
533536
// to that timeout plus one minute. This is a backup alarm in case
@@ -1039,6 +1042,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
10391042
Package: p,
10401043
IgnoreFail: true,
10411044
TryCache: c.tryCache,
1045+
Objdir: testDir,
10421046
}
10431047
if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
10441048
addTestVet(b, ptest, runAction, installAction)
@@ -1220,6 +1224,15 @@ func (c *runCache) builderRunTest(b *work.Builder, a *work.Action) error {
12201224
return nil
12211225
}
12221226

1227+
if testCoverProfile != "" {
1228+
// Write coverage to temporary profile, for merging later.
1229+
for i, arg := range args {
1230+
if strings.HasPrefix(arg, "-test.coverprofile=") {
1231+
args[i] = "-test.coverprofile=" + a.Objdir + "_cover_.out"
1232+
}
1233+
}
1234+
}
1235+
12231236
cmd := exec.Command(args[0], args[1:]...)
12241237
cmd.Dir = a.Package.Dir
12251238
cmd.Env = base.EnvForDir(cmd.Dir, cfg.OrigEnv)
@@ -1318,6 +1331,9 @@ func (c *runCache) builderRunTest(b *work.Builder, a *work.Action) error {
13181331
out := buf.Bytes()
13191332
a.TestOutput = &buf
13201333
t := fmt.Sprintf("%.3fs", time.Since(t0).Seconds())
1334+
1335+
mergeCoverProfile(cmd.Stdout, a.Objdir+"_cover_.out")
1336+
13211337
if err == nil {
13221338
norun := ""
13231339
if !testShowPass {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ func testFlags(args []string) (packageNames, passToTest []string) {
156156
case "timeout":
157157
testTimeout = value
158158
case "blockprofile", "cpuprofile", "memprofile", "mutexprofile":
159-
testProfile = true
159+
testProfile = "-" + f.Name
160160
testNeedBinary = true
161161
case "trace":
162-
testProfile = true
162+
testProfile = "-trace"
163163
case "coverpkg":
164164
testCover = true
165165
if value == "" {
@@ -169,7 +169,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
169169
}
170170
case "coverprofile":
171171
testCover = true
172-
testProfile = true
172+
testCoverProfile = value
173173
case "covermode":
174174
switch value {
175175
case "set", "count", "atomic":
@@ -219,7 +219,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
219219
}
220220

221221
// Tell the test what directory we're running in, so it can write the profiles there.
222-
if testProfile && outputDir == "" {
222+
if testProfile != "" && outputDir == "" {
223223
dir, err := os.Getwd()
224224
if err != nil {
225225
base.Fatalf("error from os.Getwd: %s", err)

0 commit comments

Comments
 (0)