cmd/cover: run tests in parallel, don't change source directory

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>
diff --git a/src/cmd/cover/cover_test.go b/src/cmd/cover/cover_test.go
index 8eb7124..a374dc4e 100644
--- a/src/cmd/cover/cover_test.go
+++ b/src/cmd/cover/cover_test.go
@@ -19,43 +19,103 @@
 	"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 @@
 		t.Fatal(err)
 	}
 
-	// defer removal of test_line.go
-	if !*debug {
-		defer os.Remove(coverInput)
+	// 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)
+
+	// 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)
 	}
-
-	// go build -o testcover
-	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
-	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)
+	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 @@
 // 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 @@
 	}
 	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 @@
 // 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 @@
 // 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 @@
 			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 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()