Commit 283558e4 authored by Russ Cox's avatar Russ Cox

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 <rsc@golang.org>
Reviewed-by: default avatarDavid Crawshaw <crawshaw@golang.org>
parent 36ef06cc
...@@ -2338,7 +2338,6 @@ func checkCoverage(tg *testgoData, data string) { ...@@ -2338,7 +2338,6 @@ func checkCoverage(tg *testgoData, data string) {
if regexp.MustCompile(`[^0-9]0\.0%`).MatchString(data) { if regexp.MustCompile(`[^0-9]0\.0%`).MatchString(data) {
tg.t.Error("some coverage results are 0.0%") tg.t.Error("some coverage results are 0.0%")
} }
tg.t.Log(data)
} }
func TestCoverageRuns(t *testing.T) { func TestCoverageRuns(t *testing.T) {
...@@ -2355,6 +2354,7 @@ func TestCoverageRuns(t *testing.T) { ...@@ -2355,6 +2354,7 @@ func TestCoverageRuns(t *testing.T) {
} }
// Check that coverage analysis uses set mode. // Check that coverage analysis uses set mode.
// Also check that coverage profiles merge correctly.
func TestCoverageUsesSetMode(t *testing.T) { func TestCoverageUsesSetMode(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("don't build libraries for coverage in short mode") t.Skip("don't build libraries for coverage in short mode")
...@@ -2362,7 +2362,7 @@ func TestCoverageUsesSetMode(t *testing.T) { ...@@ -2362,7 +2362,7 @@ func TestCoverageUsesSetMode(t *testing.T) {
tg := testgo(t) tg := testgo(t)
defer tg.cleanup() defer tg.cleanup()
tg.creatingTemp("testdata/cover.out") tg.creatingTemp("testdata/cover.out")
tg.run("test", "-short", "-cover", "encoding/binary", "-coverprofile=testdata/cover.out") tg.run("test", "-short", "-cover", "encoding/binary", "errors", "-coverprofile=testdata/cover.out")
data := tg.getStdout() + tg.getStderr() data := tg.getStdout() + tg.getStderr()
if out, err := ioutil.ReadFile("testdata/cover.out"); err != nil { if out, err := ioutil.ReadFile("testdata/cover.out"); err != nil {
t.Error(err) t.Error(err)
...@@ -2370,6 +2370,15 @@ func TestCoverageUsesSetMode(t *testing.T) { ...@@ -2370,6 +2370,15 @@ func TestCoverageUsesSetMode(t *testing.T) {
if !bytes.Contains(out, []byte("mode: set")) { if !bytes.Contains(out, []byte("mode: set")) {
t.Error("missing mode: set") t.Error("missing mode: set")
} }
if !bytes.Contains(out, []byte("errors.go")) {
t.Error("missing errors.go")
}
if !bytes.Contains(out, []byte("binary.go")) {
t.Error("missing binary.go")
}
if bytes.Count(out, []byte("mode: set")) != 1 {
t.Error("too many mode: set")
}
} }
checkCoverage(tg, data) checkCoverage(tg, data)
} }
......
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package test
import (
"cmd/go/internal/base"
"fmt"
"io"
"os"
"sync"
)
var coverMerge struct {
f *os.File
sync.Mutex // for f.Write
}
// initCoverProfile initializes the test coverage profile.
// It must be run before any calls to mergeCoverProfile or closeCoverProfile.
// Using this function clears the profile in case it existed from a previous run,
// or in case it doesn't exist and the test is going to fail to create it (or not run).
func initCoverProfile() {
if testCoverProfile == "" {
return
}
// No mutex - caller's responsibility to call with no racing goroutines.
f, err := os.Create(testCoverProfile)
if err != nil {
base.Fatalf("%v", err)
}
_, err = fmt.Fprintf(f, "mode: %s\n", testCoverMode)
if err != nil {
base.Fatalf("%v", err)
}
coverMerge.f = f
}
// mergeCoverProfile merges file into the profile stored in testCoverProfile.
// It prints any errors it encounters to ew.
func mergeCoverProfile(ew io.Writer, file string) {
if coverMerge.f == nil {
return
}
coverMerge.Lock()
defer coverMerge.Unlock()
expect := fmt.Sprintf("mode: %s\n", testCoverMode)
buf := make([]byte, len(expect))
r, err := os.Open(file)
if err != nil {
// Test did not create profile, which is OK.
return
}
defer r.Close()
n, err := io.ReadFull(r, buf)
if n == 0 {
return
}
if err != nil || string(buf) != expect {
fmt.Fprintf(ew, "error: test wrote malformed coverage profile.\n")
return
}
_, err = io.Copy(coverMerge.f, r)
if err != nil {
fmt.Fprintf(ew, "error: saving coverage profile: %v\n", err)
}
}
func closeCoverProfile() {
if coverMerge.f == nil {
return
}
if err := coverMerge.f.Close(); err != nil {
base.Errorf("closing coverage profile: %v", err)
}
}
...@@ -451,24 +451,25 @@ See the documentation of the testing package for more information. ...@@ -451,24 +451,25 @@ See the documentation of the testing package for more information.
} }
var ( var (
testC bool // -c flag testC bool // -c flag
testCover bool // -cover flag testCover bool // -cover flag
testCoverMode string // -covermode flag testCoverMode string // -covermode flag
testCoverPaths []string // -coverpkg flag testCoverPaths []string // -coverpkg flag
testCoverPkgs []*load.Package // -coverpkg flag testCoverPkgs []*load.Package // -coverpkg flag
testO string // -o flag testCoverProfile string // -coverprofile flag
testProfile bool // some profiling flag testO string // -o flag
testNeedBinary bool // profile needs to keep binary around testProfile string // profiling flag that limits test to one package
testJSON bool // -json flag testNeedBinary bool // profile needs to keep binary around
testV bool // -v flag testJSON bool // -json flag
testTimeout string // -timeout flag testV bool // -v flag
testArgs []string testTimeout string // -timeout flag
testBench bool testArgs []string
testList bool testBench bool
testShowPass bool // show passing output testList bool
testVetList string // -vet flag testShowPass bool // show passing output
pkgArgs []string testVetList string // -vet flag
pkgs []*load.Package pkgArgs []string
pkgs []*load.Package
testKillTimeout = 10 * time.Minute testKillTimeout = 10 * time.Minute
) )
...@@ -525,9 +526,11 @@ func runTest(cmd *base.Command, args []string) { ...@@ -525,9 +526,11 @@ func runTest(cmd *base.Command, args []string) {
if testO != "" && len(pkgs) != 1 { if testO != "" && len(pkgs) != 1 {
base.Fatalf("cannot use -o flag with multiple packages") base.Fatalf("cannot use -o flag with multiple packages")
} }
if testProfile && len(pkgs) != 1 { if testProfile != "" && len(pkgs) != 1 {
base.Fatalf("cannot use test profile flag with multiple packages") base.Fatalf("cannot use %s flag with multiple packages", testProfile)
} }
initCoverProfile()
defer closeCoverProfile()
// If a test timeout was given and is parseable, set our kill timeout // If a test timeout was given and is parseable, set our kill timeout
// to that timeout plus one minute. This is a backup alarm in case // 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 ...@@ -1039,6 +1042,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
Package: p, Package: p,
IgnoreFail: true, IgnoreFail: true,
TryCache: c.tryCache, TryCache: c.tryCache,
Objdir: testDir,
} }
if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 { if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
addTestVet(b, ptest, runAction, installAction) addTestVet(b, ptest, runAction, installAction)
...@@ -1220,6 +1224,15 @@ func (c *runCache) builderRunTest(b *work.Builder, a *work.Action) error { ...@@ -1220,6 +1224,15 @@ func (c *runCache) builderRunTest(b *work.Builder, a *work.Action) error {
return nil return nil
} }
if testCoverProfile != "" {
// Write coverage to temporary profile, for merging later.
for i, arg := range args {
if strings.HasPrefix(arg, "-test.coverprofile=") {
args[i] = "-test.coverprofile=" + a.Objdir + "_cover_.out"
}
}
}
cmd := exec.Command(args[0], args[1:]...) cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = a.Package.Dir cmd.Dir = a.Package.Dir
cmd.Env = base.EnvForDir(cmd.Dir, cfg.OrigEnv) cmd.Env = base.EnvForDir(cmd.Dir, cfg.OrigEnv)
...@@ -1318,6 +1331,9 @@ func (c *runCache) builderRunTest(b *work.Builder, a *work.Action) error { ...@@ -1318,6 +1331,9 @@ func (c *runCache) builderRunTest(b *work.Builder, a *work.Action) error {
out := buf.Bytes() out := buf.Bytes()
a.TestOutput = &buf a.TestOutput = &buf
t := fmt.Sprintf("%.3fs", time.Since(t0).Seconds()) t := fmt.Sprintf("%.3fs", time.Since(t0).Seconds())
mergeCoverProfile(cmd.Stdout, a.Objdir+"_cover_.out")
if err == nil { if err == nil {
norun := "" norun := ""
if !testShowPass { if !testShowPass {
......
...@@ -156,10 +156,10 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -156,10 +156,10 @@ func testFlags(args []string) (packageNames, passToTest []string) {
case "timeout": case "timeout":
testTimeout = value testTimeout = value
case "blockprofile", "cpuprofile", "memprofile", "mutexprofile": case "blockprofile", "cpuprofile", "memprofile", "mutexprofile":
testProfile = true testProfile = "-" + f.Name
testNeedBinary = true testNeedBinary = true
case "trace": case "trace":
testProfile = true testProfile = "-trace"
case "coverpkg": case "coverpkg":
testCover = true testCover = true
if value == "" { if value == "" {
...@@ -169,7 +169,7 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -169,7 +169,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
} }
case "coverprofile": case "coverprofile":
testCover = true testCover = true
testProfile = true testCoverProfile = value
case "covermode": case "covermode":
switch value { switch value {
case "set", "count", "atomic": case "set", "count", "atomic":
...@@ -219,7 +219,7 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -219,7 +219,7 @@ func testFlags(args []string) (packageNames, passToTest []string) {
} }
// Tell the test what directory we're running in, so it can write the profiles there. // Tell the test what directory we're running in, so it can write the profiles there.
if testProfile && outputDir == "" { if testProfile != "" && outputDir == "" {
dir, err := os.Getwd() dir, err := os.Getwd()
if err != nil { if err != nil {
base.Fatalf("error from os.Getwd: %s", err) base.Fatalf("error from os.Getwd: %s", err)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment