summaryrefslogtreecommitdiffstats
path: root/src/runtime/coverage/emitdata_test.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/runtime/coverage/emitdata_test.go550
1 files changed, 550 insertions, 0 deletions
diff --git a/src/runtime/coverage/emitdata_test.go b/src/runtime/coverage/emitdata_test.go
new file mode 100644
index 0000000..3558dd2
--- /dev/null
+++ b/src/runtime/coverage/emitdata_test.go
@@ -0,0 +1,550 @@
+// Copyright 2022 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 coverage
+
+import (
+ "fmt"
+ "internal/coverage"
+ "internal/goexperiment"
+ "internal/platform"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+// Set to true for debugging (linux only).
+const fixedTestDir = false
+
+func TestCoverageApis(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ if !goexperiment.CoverageRedesign {
+ t.Skipf("skipping new coverage tests (experiment not enabled)")
+ }
+ testenv.MustHaveGoBuild(t)
+ dir := t.TempDir()
+ if fixedTestDir {
+ dir = "/tmp/qqqzzz"
+ os.RemoveAll(dir)
+ mkdir(t, dir)
+ }
+
+ // Build harness. We need two copies of the harness, one built
+ // with -covermode=atomic and one built non-atomic.
+ bdir1 := mkdir(t, filepath.Join(dir, "build1"))
+ hargs1 := []string{"-covermode=atomic", "-coverpkg=all"}
+ atomicHarnessPath := buildHarness(t, bdir1, hargs1)
+ nonAtomicMode := testing.CoverMode()
+ if testing.CoverMode() == "atomic" {
+ nonAtomicMode = "set"
+ }
+ bdir2 := mkdir(t, filepath.Join(dir, "build2"))
+ hargs2 := []string{"-coverpkg=all", "-covermode=" + nonAtomicMode}
+ nonAtomicHarnessPath := buildHarness(t, bdir2, hargs2)
+
+ t.Logf("atomic harness path is %s", atomicHarnessPath)
+ t.Logf("non-atomic harness path is %s", nonAtomicHarnessPath)
+
+ // Sub-tests for each API we want to inspect, plus
+ // extras for error testing.
+ t.Run("emitToDir", func(t *testing.T) {
+ t.Parallel()
+ testEmitToDir(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToWriter", func(t *testing.T) {
+ t.Parallel()
+ testEmitToWriter(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToNonexistentDir", func(t *testing.T) {
+ t.Parallel()
+ testEmitToNonexistentDir(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToNilWriter", func(t *testing.T) {
+ t.Parallel()
+ testEmitToNilWriter(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToFailingWriter", func(t *testing.T) {
+ t.Parallel()
+ testEmitToFailingWriter(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitWithCounterClear", func(t *testing.T) {
+ t.Parallel()
+ testEmitWithCounterClear(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToDirNonAtomic", func(t *testing.T) {
+ t.Parallel()
+ testEmitToDirNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
+ })
+ t.Run("emitToWriterNonAtomic", func(t *testing.T) {
+ t.Parallel()
+ testEmitToWriterNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
+ })
+ t.Run("emitWithCounterClearNonAtomic", func(t *testing.T) {
+ t.Parallel()
+ testEmitWithCounterClearNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
+ })
+}
+
+// upmergeCoverData helps improve coverage data for this package
+// itself. If this test itself is being invoked with "-cover", then
+// what we'd like is for package coverage data (that is, coverage for
+// routines in "runtime/coverage") to be incorporated into the test
+// run from the "harness.exe" runs we've just done. We can accomplish
+// this by doing a merge from the harness gocoverdir's to the test
+// gocoverdir.
+func upmergeCoverData(t *testing.T, gocoverdir string, mode string) {
+ if testing.CoverMode() != mode {
+ return
+ }
+ testGoCoverDir := os.Getenv("GOCOVERDIR")
+ if testGoCoverDir == "" {
+ return
+ }
+ args := []string{"tool", "covdata", "merge", "-pkg=runtime/coverage",
+ "-o", testGoCoverDir, "-i", gocoverdir}
+ t.Logf("up-merge of covdata from %s to %s", gocoverdir, testGoCoverDir)
+ t.Logf("executing: go %+v", args)
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("covdata merge failed (%v): %s", err, b)
+ }
+}
+
+// buildHarness builds the helper program "harness.exe".
+func buildHarness(t *testing.T, dir string, opts []string) string {
+ harnessPath := filepath.Join(dir, "harness.exe")
+ harnessSrc := filepath.Join("testdata", "harness.go")
+ args := []string{"build", "-o", harnessPath}
+ args = append(args, opts...)
+ args = append(args, harnessSrc)
+ //t.Logf("harness build: go %+v\n", args)
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("build failed (%v): %s", err, b)
+ }
+ return harnessPath
+}
+
+func mkdir(t *testing.T, d string) string {
+ t.Helper()
+ if err := os.Mkdir(d, 0777); err != nil {
+ t.Fatalf("mkdir failed: %v", err)
+ }
+ return d
+}
+
+// updateGoCoverDir updates the specified environment 'env' to set
+// GOCOVERDIR to 'gcd' (if setGoCoverDir is TRUE) or removes
+// GOCOVERDIR from the environment (if setGoCoverDir is false).
+func updateGoCoverDir(env []string, gcd string, setGoCoverDir bool) []string {
+ rv := []string{}
+ found := false
+ for _, v := range env {
+ if strings.HasPrefix(v, "GOCOVERDIR=") {
+ if !setGoCoverDir {
+ continue
+ }
+ v = "GOCOVERDIR=" + gcd
+ found = true
+ }
+ rv = append(rv, v)
+ }
+ if !found && setGoCoverDir {
+ rv = append(rv, "GOCOVERDIR="+gcd)
+ }
+ return rv
+}
+
+func runHarness(t *testing.T, harnessPath string, tp string, setGoCoverDir bool, rdir, edir string) (string, error) {
+ t.Logf("running: %s -tp %s -o %s with rdir=%s and GOCOVERDIR=%v", harnessPath, tp, edir, rdir, setGoCoverDir)
+ cmd := exec.Command(harnessPath, "-tp", tp, "-o", edir)
+ cmd.Dir = rdir
+ cmd.Env = updateGoCoverDir(os.Environ(), rdir, setGoCoverDir)
+ b, err := cmd.CombinedOutput()
+ //t.Logf("harness run output: %s\n", string(b))
+ return string(b), err
+}
+
+func testForSpecificFunctions(t *testing.T, dir string, want []string, avoid []string) string {
+ args := []string{"tool", "covdata", "debugdump",
+ "-live", "-pkg=command-line-arguments", "-i=" + dir}
+ t.Logf("running: go %v\n", args)
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("'go tool covdata failed (%v): %s", err, b)
+ }
+ output := string(b)
+ rval := ""
+ for _, f := range want {
+ wf := "Func: " + f + "\n"
+ if strings.Contains(output, wf) {
+ continue
+ }
+ rval += fmt.Sprintf("error: output should contain %q but does not\n", wf)
+ }
+ for _, f := range avoid {
+ wf := "Func: " + f + "\n"
+ if strings.Contains(output, wf) {
+ rval += fmt.Sprintf("error: output should not contain %q but does\n", wf)
+ }
+ }
+ if rval != "" {
+ t.Logf("=-= begin output:\n" + output + "\n=-= end output\n")
+ }
+ return rval
+}
+
+func withAndWithoutRunner(f func(setit bool, tag string)) {
+ // Run 'f' with and without GOCOVERDIR set.
+ for i := 0; i < 2; i++ {
+ tag := "x"
+ setGoCoverDir := true
+ if i == 0 {
+ setGoCoverDir = false
+ tag = "y"
+ }
+ f(setGoCoverDir, tag)
+ }
+}
+
+func mktestdirs(t *testing.T, tag, tp, dir string) (string, string) {
+ t.Helper()
+ rdir := mkdir(t, filepath.Join(dir, tp+"-rdir-"+tag))
+ edir := mkdir(t, filepath.Join(dir, tp+"-edir-"+tag))
+ return rdir, edir
+}
+
+func testEmitToDir(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToDir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp emitDir': %v", err)
+ }
+
+ // Just check to make sure meta-data file and counter data file were
+ // written. Another alternative would be to run "go tool covdata"
+ // or equivalent, but for now, this is what we've got.
+ dents, err := os.ReadDir(edir)
+ if err != nil {
+ t.Fatalf("os.ReadDir(%s) failed: %v", edir, err)
+ }
+ mfc := 0
+ cdc := 0
+ for _, e := range dents {
+ if e.IsDir() {
+ continue
+ }
+ if strings.HasPrefix(e.Name(), coverage.MetaFilePref) {
+ mfc++
+ } else if strings.HasPrefix(e.Name(), coverage.CounterFilePref) {
+ cdc++
+ }
+ }
+ wantmf := 1
+ wantcf := 1
+ if mfc != wantmf {
+ t.Errorf("EmitToDir: want %d meta-data files, got %d\n", wantmf, mfc)
+ }
+ if cdc != wantcf {
+ t.Errorf("EmitToDir: want %d counter-data files, got %d\n", wantcf, cdc)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToWriter(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToWriter"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ want := []string{"main", tp}
+ avoid := []string{"final"}
+ if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
+ t.Errorf("coverage data from %q output match failed: %s", tp, msg)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToNonexistentDir(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToNonexistentDir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToUnwritableDir(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+
+ tp := "emitToUnwritableDir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+
+ // Make edir unwritable.
+ if err := os.Chmod(edir, 0555); err != nil {
+ t.Fatalf("chmod failed: %v", err)
+ }
+ defer os.Chmod(edir, 0777)
+
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToNilWriter(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToNilWriter"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToFailingWriter(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToFailingWriter"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitWithCounterClear(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitWithCounterClear"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ want := []string{tp, "postClear"}
+ avoid := []string{"preClear", "main", "final"}
+ if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
+ t.Logf("%s", output)
+ t.Errorf("coverage data from %q output match failed: %s", tp, msg)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToDirNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
+ tp := "emitToDir"
+ tag := "nonatomdir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ true, rdir, edir)
+
+ // We expect an error here.
+ if err == nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
+ }
+
+ got := strings.TrimSpace(string(output))
+ want := "WriteCountersDir invoked for program built"
+ if !strings.Contains(got, want) {
+ t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
+ tp, got, want)
+ }
+ upmergeCoverData(t, edir, naMode)
+ upmergeCoverData(t, rdir, naMode)
+}
+
+func testEmitToWriterNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
+ tp := "emitToWriter"
+ tag := "nonatomw"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ true, rdir, edir)
+
+ // We expect an error here.
+ if err == nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
+ }
+
+ got := strings.TrimSpace(string(output))
+ want := "WriteCounters invoked for program built"
+ if !strings.Contains(got, want) {
+ t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
+ tp, got, want)
+ }
+
+ upmergeCoverData(t, edir, naMode)
+ upmergeCoverData(t, rdir, naMode)
+}
+
+func testEmitWithCounterClearNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
+ tp := "emitWithCounterClear"
+ tag := "cclear"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ true, rdir, edir)
+
+ // We expect an error here.
+ if err == nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s' nonatomic: did not get expected error", tp)
+ }
+
+ got := strings.TrimSpace(string(output))
+ want := "ClearCounters invoked for program built"
+ if !strings.Contains(got, want) {
+ t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
+ tp, got, want)
+ }
+
+ upmergeCoverData(t, edir, naMode)
+ upmergeCoverData(t, rdir, naMode)
+}
+
+func TestApisOnNocoverBinary(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ testenv.MustHaveGoBuild(t)
+ dir := t.TempDir()
+
+ // Build harness with no -cover.
+ bdir := mkdir(t, filepath.Join(dir, "nocover"))
+ edir := mkdir(t, filepath.Join(dir, "emitDirNo"))
+ harnessPath := buildHarness(t, bdir, nil)
+ output, err := runHarness(t, harnessPath, "emitToDir", false, edir, edir)
+ if err == nil {
+ t.Fatalf("expected error on TestApisOnNocoverBinary harness run")
+ }
+ const want = "not built with -cover"
+ if !strings.Contains(output, want) {
+ t.Errorf("error output does not contain %q: %s", want, output)
+ }
+}
+
+func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ if !goexperiment.CoverageRedesign {
+ t.Skipf("skipping new coverage tests (experiment not enabled)")
+ }
+
+ // This test requires "go test -race -cover", meaning that we need
+ // go build, go run, and "-race" support.
+ testenv.MustHaveGoRun(t)
+ if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) ||
+ !testenv.HasCGO() {
+ t.Skip("skipped due to lack of race detector support / CGO")
+ }
+
+ // This will run a program with -cover and -race where we have a
+ // goroutine still running (and updating counters) at the point where
+ // the test runtime is trying to write out counter data.
+ cmd := exec.Command(testenv.GoToolPath(t), "test", "-cover", "-race")
+ cmd.Dir = filepath.Join("testdata", "issue56006")
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go test -cover -race failed: %v", err)
+ }
+
+ // Don't want to see any data races in output.
+ avoid := []string{"DATA RACE"}
+ for _, no := range avoid {
+ if strings.Contains(string(b), no) {
+ t.Logf("%s\n", string(b))
+ t.Fatalf("found %s in test output, not permitted", no)
+ }
+ }
+}
+
+func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ testenv.MustHaveGoRun(t)
+
+ tmpdir := t.TempDir()
+ ppath := filepath.Join(tmpdir, "foo.cov")
+
+ cmd := exec.Command(testenv.GoToolPath(t), "test", "-coverpkg=all", "-coverprofile="+ppath)
+ cmd.Dir = filepath.Join("testdata", "issue59563")
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go test -cover failed: %v", err)
+ }
+
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath)
+ b, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool cover -func failed: %v", err)
+ }
+
+ lines := strings.Split(string(b), "\n")
+ nfound := 0
+ bad := false
+ for _, line := range lines {
+ f := strings.Fields(line)
+ if len(f) == 0 {
+ continue
+ }
+ // We're only interested in the specific function "large" for
+ // the testcase being built. See the #59563 for details on why
+ // size matters.
+ if !(strings.HasPrefix(f[0], "runtime/coverage/testdata/issue59563/repro.go") && strings.Contains(line, "large")) {
+ continue
+ }
+ nfound++
+ want := "100.0%"
+ if f[len(f)-1] != want {
+ t.Errorf("wanted %s got: %q\n", want, line)
+ bad = true
+ }
+ }
+ if nfound != 1 {
+ t.Errorf("wanted 1 found, got %d\n", nfound)
+ bad = true
+ }
+ if bad {
+ t.Logf("func output:\n%s\n", string(b))
+ }
+}