]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/cover: run tests in parallel, don't change source directory
authorIan Lance Taylor <iant@golang.org>
Thu, 6 Dec 2018 01:24:41 +0000 (17:24 -0800)
committerIan Lance Taylor <iant@golang.org>
Thu, 6 Dec 2018 22:06:55 +0000 (22:06 +0000)
This speeds up the cmd/cover testsuite by about 40% on my laptop.

Updates #26473
Updates #28386

Change-Id: I853b1b3b8c98dc89440f7b7bf5c0ade1d3d66802
Reviewed-on: https://go-review.googlesource.com/c/152817
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/cmd/cover/cover_test.go

index 8eb7124aad85b69c33195315e897567cb1f94899..a374dc4e9bb51720bd439248a55e546aa966c4a8 100644 (file)
@@ -19,43 +19,103 @@ import (
        "path/filepath"
        "regexp"
        "strings"
+       "sync"
        "testing"
 )
 
 const (
        // Data directory, also the package directory for the test.
        testdata = "testdata"
-
-       // Binaries we compile.
-       testcover = "./testcover.exe"
 )
 
 var (
-       // Files we use.
+       // Input files.
        testMain     = filepath.Join(testdata, "main.go")
        testTest     = filepath.Join(testdata, "test.go")
-       coverInput   = filepath.Join(testdata, "test_line.go")
-       coverOutput  = filepath.Join(testdata, "test_cover.go")
        coverProfile = filepath.Join(testdata, "profile.cov")
 
        // The HTML test files are in a separate directory
        // so they are a complete package.
-       htmlProfile = filepath.Join(testdata, "html", "html.cov")
-       htmlHTML    = filepath.Join(testdata, "html", "html.html")
-       htmlGolden  = filepath.Join(testdata, "html", "html.golden")
+       htmlGolden = filepath.Join(testdata, "html", "html.golden")
+
+       // Temporary files.
+       tmpTestMain string
+       coverInput  string
+       coverOutput string
+       htmlProfile string
+       htmlHTML    string
+)
+
+var (
+       // testTempDir is a temporary directory created in TestMain.
+       testTempDir string
+
+       // testcover is a newly built version of the cover program.
+       testcover string
+
+       // testcoverErr records an error building testcover.
+       testcoverErr error
+
+       // testcoverOnce is used to build testcover once.
+       testcoverOnce sync.Once
 )
 
 var debug = flag.Bool("debug", false, "keep rewritten files for debugging")
 
+// We use TestMain to set up a temporary directory and remove it when
+// the tests are done.
+func TestMain(m *testing.M) {
+       dir, err := ioutil.TempDir("", "gotestcover")
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+
+       testTempDir = dir
+
+       tmpTestMain = filepath.Join(dir, "main.go")
+       coverInput = filepath.Join(dir, "test_line.go")
+       coverOutput = filepath.Join(dir, "test_cover.go")
+       htmlProfile = filepath.Join(dir, "html.cov")
+       htmlHTML = filepath.Join(dir, "html.html")
+
+       status := m.Run()
+
+       if !*debug {
+               os.RemoveAll(dir)
+       }
+
+       os.Exit(status)
+}
+
+// buildCover builds a version of the cover program for testing.
+// This ensures that "go test cmd/cover" tests the current cmd/cover.
+func buildCover(t *testing.T) {
+       t.Helper()
+       testenv.MustHaveGoBuild(t)
+       testcoverOnce.Do(func() {
+               testcover = filepath.Join(testTempDir, "testcover.exe")
+               t.Logf("running [go build -o %s]", testcover)
+               out, err := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover).CombinedOutput()
+               t.Logf("%s", out)
+               testcoverErr = err
+       })
+       if testcoverErr != nil {
+               t.Fatal("failed to build testcover program:", testcoverErr)
+       }
+}
+
 // Run this shell script, but do it in Go so it can be run by "go test".
 //
 //     replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
-//     go build -o ./testcover
-//     ./testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
+//     go build -o testcover
+//     testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
 //     go run ./testdata/main.go ./testdata/test.go
 //
 func TestCover(t *testing.T) {
-       testenv.MustHaveGoBuild(t)
+       t.Parallel()
+       testenv.MustHaveGoRun(t)
+       buildCover(t)
 
        // Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
        file, err := ioutil.ReadFile(testTest)
@@ -81,29 +141,22 @@ func TestCover(t *testing.T) {
                t.Fatal(err)
        }
 
-       // defer removal of test_line.go
-       if !*debug {
-               defer os.Remove(coverInput)
-       }
-
-       // go build -o testcover
-       cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
+       // testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
+       cmd := exec.Command(testcover, "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
        run(cmd, t)
 
-       // defer removal of testcover
-       defer os.Remove(testcover)
-
-       // ./testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
-       cmd = exec.Command(testcover, "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
-       run(cmd, t)
-
-       // defer removal of ./testdata/test_cover.go
-       if !*debug {
-               defer os.Remove(coverOutput)
+       // Copy testmain to testTempDir, so that it is in the same directory
+       // as coverOutput.
+       b, err := ioutil.ReadFile(testMain)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if err := ioutil.WriteFile(tmpTestMain, b, 0444); err != nil {
+               t.Fatal(err)
        }
 
        // go run ./testdata/main.go ./testdata/test.go
-       cmd = exec.Command(testenv.GoToolPath(t), "run", testMain, coverOutput)
+       cmd = exec.Command(testenv.GoToolPath(t), "run", tmpTestMain, coverOutput)
        run(cmd, t)
 
        file, err = ioutil.ReadFile(coverOutput)
@@ -131,6 +184,9 @@ func TestCover(t *testing.T) {
 // above those declarations, even if they are not part of the block of
 // documentation comments.
 func TestDirectives(t *testing.T) {
+       t.Parallel()
+       buildCover(t)
+
        // Read the source file and find all the directives. We'll keep
        // track of whether each one has been seen in the output.
        testDirectives := filepath.Join(testdata, "directives.go")
@@ -140,8 +196,8 @@ func TestDirectives(t *testing.T) {
        }
        sourceDirectives := findDirectives(source)
 
-       // go tool cover -mode=atomic ./testdata/directives.go
-       cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-mode=atomic", testDirectives)
+       // testcover -mode=atomic ./testdata/directives.go
+       cmd := exec.Command(testcover, "-mode=atomic", testDirectives)
        cmd.Stderr = os.Stderr
        output, err := cmd.Output()
        if err != nil {
@@ -247,8 +303,10 @@ func findDirectives(source []byte) []directiveInfo {
 // Makes sure that `cover -func=profile.cov` reports accurate coverage.
 // Issue #20515.
 func TestCoverFunc(t *testing.T) {
-       // go tool cover -func ./testdata/profile.cov
-       cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func", coverProfile)
+       t.Parallel()
+       buildCover(t)
+       // testcover -func ./testdata/profile.cov
+       cmd := exec.Command(testcover, "-func", coverProfile)
        out, err := cmd.Output()
        if err != nil {
                if ee, ok := err.(*exec.ExitError); ok {
@@ -266,19 +324,14 @@ func TestCoverFunc(t *testing.T) {
 // Check that cover produces correct HTML.
 // Issue #25767.
 func TestCoverHTML(t *testing.T) {
-       testenv.MustHaveGoBuild(t)
-       if !*debug {
-               defer os.Remove(testcover)
-               defer os.Remove(htmlProfile)
-               defer os.Remove(htmlHTML)
-       }
-       // go build -o testcover
-       cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
-       run(cmd, t)
+       t.Parallel()
+       testenv.MustHaveGoRun(t)
+       buildCover(t)
+
        // go test -coverprofile testdata/html/html.cov cmd/cover/testdata/html
-       cmd = exec.Command(testenv.GoToolPath(t), "test", "-coverprofile", htmlProfile, "cmd/cover/testdata/html")
+       cmd := exec.Command(testenv.GoToolPath(t), "test", "-coverprofile", htmlProfile, "cmd/cover/testdata/html")
        run(cmd, t)
-       // ./testcover -html testdata/html/html.cov -o testdata/html/html.html
+       // testcover -html testdata/html/html.cov -o testdata/html/html.html
        cmd = exec.Command(testcover, "-html", htmlProfile, "-o", htmlHTML)
        run(cmd, t)
 
@@ -303,6 +356,9 @@ func TestCoverHTML(t *testing.T) {
                        in = false
                }
        }
+       if scan.Err() != nil {
+               t.Error(scan.Err())
+       }
        golden, err := ioutil.ReadFile(htmlGolden)
        if err != nil {
                t.Fatalf("reading golden file: %v", err)
@@ -331,6 +387,7 @@ func TestCoverHTML(t *testing.T) {
 
 func run(c *exec.Cmd, t *testing.T) {
        t.Helper()
+       t.Log("running", c.Args)
        c.Stdout = os.Stdout
        c.Stderr = os.Stderr
        err := c.Run()