github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/runtime/coverage/emitdata_test.go (about)

     1  // Copyright 2022 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 coverage
     6  
     7  import (
     8  	"fmt"
     9  	"internal/coverage"
    10  	"internal/goexperiment"
    11  	"internal/platform"
    12  	"internal/testenv"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  // Set to true for debugging (linux only).
    22  const fixedTestDir = false
    23  
    24  func TestCoverageApis(t *testing.T) {
    25  	if testing.Short() {
    26  		t.Skipf("skipping test: too long for short mode")
    27  	}
    28  	if !goexperiment.CoverageRedesign {
    29  		t.Skipf("skipping new coverage tests (experiment not enabled)")
    30  	}
    31  	testenv.MustHaveGoBuild(t)
    32  	dir := t.TempDir()
    33  	if fixedTestDir {
    34  		dir = "/tmp/qqqzzz"
    35  		os.RemoveAll(dir)
    36  		mkdir(t, dir)
    37  	}
    38  
    39  	// Build harness.
    40  	bdir := mkdir(t, filepath.Join(dir, "build"))
    41  	hargs := []string{"-cover", "-coverpkg=all"}
    42  	if testing.CoverMode() != "" {
    43  		hargs = append(hargs, "-covermode="+testing.CoverMode())
    44  	}
    45  	harnessPath := buildHarness(t, bdir, hargs)
    46  
    47  	t.Logf("harness path is %s", harnessPath)
    48  
    49  	// Sub-tests for each API we want to inspect, plus
    50  	// extras for error testing.
    51  	t.Run("emitToDir", func(t *testing.T) {
    52  		t.Parallel()
    53  		testEmitToDir(t, harnessPath, dir)
    54  	})
    55  	t.Run("emitToWriter", func(t *testing.T) {
    56  		t.Parallel()
    57  		testEmitToWriter(t, harnessPath, dir)
    58  	})
    59  	t.Run("emitToNonexistentDir", func(t *testing.T) {
    60  		t.Parallel()
    61  		testEmitToNonexistentDir(t, harnessPath, dir)
    62  	})
    63  	t.Run("emitToNilWriter", func(t *testing.T) {
    64  		t.Parallel()
    65  		testEmitToNilWriter(t, harnessPath, dir)
    66  	})
    67  	t.Run("emitToFailingWriter", func(t *testing.T) {
    68  		t.Parallel()
    69  		testEmitToFailingWriter(t, harnessPath, dir)
    70  	})
    71  	t.Run("emitWithCounterClear", func(t *testing.T) {
    72  		t.Parallel()
    73  		testEmitWithCounterClear(t, harnessPath, dir)
    74  	})
    75  
    76  }
    77  
    78  // upmergeCoverData helps improve coverage data for this package
    79  // itself. If this test itself is being invoked with "-cover", then
    80  // what we'd like is for package coverage data (that is, coverage for
    81  // routines in "runtime/coverage") to be incorporated into the test
    82  // run from the "harness.exe" runs we've just done. We can accomplish
    83  // this by doing a merge from the harness gocoverdir's to the test
    84  // gocoverdir.
    85  func upmergeCoverData(t *testing.T, gocoverdir string) {
    86  	if testing.CoverMode() == "" {
    87  		return
    88  	}
    89  	testGoCoverDir := os.Getenv("GOCOVERDIR")
    90  	if testGoCoverDir == "" {
    91  		return
    92  	}
    93  	args := []string{"tool", "covdata", "merge", "-pkg=runtime/coverage",
    94  		"-o", testGoCoverDir, "-i", gocoverdir}
    95  	t.Logf("up-merge of covdata from %s to %s", gocoverdir, testGoCoverDir)
    96  	t.Logf("executing: go %+v", args)
    97  	cmd := exec.Command(testenv.GoToolPath(t), args...)
    98  	if b, err := cmd.CombinedOutput(); err != nil {
    99  		t.Fatalf("covdata merge failed (%v): %s", err, b)
   100  	}
   101  }
   102  
   103  // buildHarness builds the helper program "harness.exe".
   104  func buildHarness(t *testing.T, dir string, opts []string) string {
   105  	harnessPath := filepath.Join(dir, "harness.exe")
   106  	harnessSrc := filepath.Join("testdata", "harness.go")
   107  	args := []string{"build", "-o", harnessPath}
   108  	args = append(args, opts...)
   109  	args = append(args, harnessSrc)
   110  	//t.Logf("harness build: go %+v\n", args)
   111  	cmd := exec.Command(testenv.GoToolPath(t), args...)
   112  	if b, err := cmd.CombinedOutput(); err != nil {
   113  		t.Fatalf("build failed (%v): %s", err, b)
   114  	}
   115  	return harnessPath
   116  }
   117  
   118  func mkdir(t *testing.T, d string) string {
   119  	t.Helper()
   120  	if err := os.Mkdir(d, 0777); err != nil {
   121  		t.Fatalf("mkdir failed: %v", err)
   122  	}
   123  	return d
   124  }
   125  
   126  // updateGoCoverDir updates the specified environment 'env' to set
   127  // GOCOVERDIR to 'gcd' (if setGoCoverDir is TRUE) or removes
   128  // GOCOVERDIR from the environment (if setGoCoverDir is false).
   129  func updateGoCoverDir(env []string, gcd string, setGoCoverDir bool) []string {
   130  	rv := []string{}
   131  	found := false
   132  	for _, v := range env {
   133  		if strings.HasPrefix(v, "GOCOVERDIR=") {
   134  			if !setGoCoverDir {
   135  				continue
   136  			}
   137  			v = "GOCOVERDIR=" + gcd
   138  			found = true
   139  		}
   140  		rv = append(rv, v)
   141  	}
   142  	if !found && setGoCoverDir {
   143  		rv = append(rv, "GOCOVERDIR="+gcd)
   144  	}
   145  	return rv
   146  }
   147  
   148  func runHarness(t *testing.T, harnessPath string, tp string, setGoCoverDir bool, rdir, edir string) (string, error) {
   149  	t.Logf("running: %s -tp %s -o %s with rdir=%s and GOCOVERDIR=%v", harnessPath, tp, edir, rdir, setGoCoverDir)
   150  	cmd := exec.Command(harnessPath, "-tp", tp, "-o", edir)
   151  	cmd.Dir = rdir
   152  	cmd.Env = updateGoCoverDir(os.Environ(), rdir, setGoCoverDir)
   153  	b, err := cmd.CombinedOutput()
   154  	//t.Logf("harness run output: %s\n", string(b))
   155  	return string(b), err
   156  }
   157  
   158  func testForSpecificFunctions(t *testing.T, dir string, want []string, avoid []string) string {
   159  	args := []string{"tool", "covdata", "debugdump",
   160  		"-live", "-pkg=main", "-i=" + dir}
   161  	t.Logf("running: go %v\n", args)
   162  	cmd := exec.Command(testenv.GoToolPath(t), args...)
   163  	b, err := cmd.CombinedOutput()
   164  	if err != nil {
   165  		t.Fatalf("'go tool covdata failed (%v): %s", err, b)
   166  	}
   167  	output := string(b)
   168  	rval := ""
   169  	for _, f := range want {
   170  		wf := "Func: " + f
   171  		if strings.Contains(output, wf) {
   172  			continue
   173  		}
   174  		rval += fmt.Sprintf("error: output should contain %q but does not\n", wf)
   175  	}
   176  	for _, f := range avoid {
   177  		wf := "Func: " + f
   178  		if strings.Contains(output, wf) {
   179  			rval += fmt.Sprintf("error: output should not contain %q but does\n", wf)
   180  		}
   181  	}
   182  	return rval
   183  }
   184  
   185  func withAndWithoutRunner(f func(setit bool, tag string)) {
   186  	// Run 'f' with and without GOCOVERDIR set.
   187  	for i := 0; i < 2; i++ {
   188  		tag := "x"
   189  		setGoCoverDir := true
   190  		if i == 0 {
   191  			setGoCoverDir = false
   192  			tag = "y"
   193  		}
   194  		f(setGoCoverDir, tag)
   195  	}
   196  }
   197  
   198  func mktestdirs(t *testing.T, tag, tp, dir string) (string, string) {
   199  	t.Helper()
   200  	rdir := mkdir(t, filepath.Join(dir, tp+"-rdir-"+tag))
   201  	edir := mkdir(t, filepath.Join(dir, tp+"-edir-"+tag))
   202  	return rdir, edir
   203  }
   204  
   205  func testEmitToDir(t *testing.T, harnessPath string, dir string) {
   206  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   207  		tp := "emitToDir"
   208  		rdir, edir := mktestdirs(t, tag, tp, dir)
   209  		output, err := runHarness(t, harnessPath, tp,
   210  			setGoCoverDir, rdir, edir)
   211  		if err != nil {
   212  			t.Logf("%s", output)
   213  			t.Fatalf("running 'harness -tp emitDir': %v", err)
   214  		}
   215  
   216  		// Just check to make sure meta-data file and counter data file were
   217  		// written. Another alternative would be to run "go tool covdata"
   218  		// or equivalent, but for now, this is what we've got.
   219  		dents, err := os.ReadDir(edir)
   220  		if err != nil {
   221  			t.Fatalf("os.ReadDir(%s) failed: %v", edir, err)
   222  		}
   223  		mfc := 0
   224  		cdc := 0
   225  		for _, e := range dents {
   226  			if e.IsDir() {
   227  				continue
   228  			}
   229  			if strings.HasPrefix(e.Name(), coverage.MetaFilePref) {
   230  				mfc++
   231  			} else if strings.HasPrefix(e.Name(), coverage.CounterFilePref) {
   232  				cdc++
   233  			}
   234  		}
   235  		wantmf := 1
   236  		wantcf := 1
   237  		if mfc != wantmf {
   238  			t.Errorf("EmitToDir: want %d meta-data files, got %d\n", wantmf, mfc)
   239  		}
   240  		if cdc != wantcf {
   241  			t.Errorf("EmitToDir: want %d counter-data files, got %d\n", wantcf, cdc)
   242  		}
   243  		upmergeCoverData(t, edir)
   244  		upmergeCoverData(t, rdir)
   245  	})
   246  }
   247  
   248  func testEmitToWriter(t *testing.T, harnessPath string, dir string) {
   249  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   250  		tp := "emitToWriter"
   251  		rdir, edir := mktestdirs(t, tag, tp, dir)
   252  		output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
   253  		if err != nil {
   254  			t.Logf("%s", output)
   255  			t.Fatalf("running 'harness -tp %s': %v", tp, err)
   256  		}
   257  		want := []string{"main", tp}
   258  		avoid := []string{"final"}
   259  		if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
   260  			t.Errorf("coverage data from %q output match failed: %s", tp, msg)
   261  		}
   262  		upmergeCoverData(t, edir)
   263  		upmergeCoverData(t, rdir)
   264  	})
   265  }
   266  
   267  func testEmitToNonexistentDir(t *testing.T, harnessPath string, dir string) {
   268  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   269  		tp := "emitToNonexistentDir"
   270  		rdir, edir := mktestdirs(t, tag, tp, dir)
   271  		output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
   272  		if err != nil {
   273  			t.Logf("%s", output)
   274  			t.Fatalf("running 'harness -tp %s': %v", tp, err)
   275  		}
   276  		upmergeCoverData(t, edir)
   277  		upmergeCoverData(t, rdir)
   278  	})
   279  }
   280  
   281  func testEmitToUnwritableDir(t *testing.T, harnessPath string, dir string) {
   282  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   283  
   284  		tp := "emitToUnwritableDir"
   285  		rdir, edir := mktestdirs(t, tag, tp, dir)
   286  
   287  		// Make edir unwritable.
   288  		if err := os.Chmod(edir, 0555); err != nil {
   289  			t.Fatalf("chmod failed: %v", err)
   290  		}
   291  		defer os.Chmod(edir, 0777)
   292  
   293  		output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
   294  		if err != nil {
   295  			t.Logf("%s", output)
   296  			t.Fatalf("running 'harness -tp %s': %v", tp, err)
   297  		}
   298  		upmergeCoverData(t, edir)
   299  		upmergeCoverData(t, rdir)
   300  	})
   301  }
   302  
   303  func testEmitToNilWriter(t *testing.T, harnessPath string, dir string) {
   304  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   305  		tp := "emitToNilWriter"
   306  		rdir, edir := mktestdirs(t, tag, tp, dir)
   307  		output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
   308  		if err != nil {
   309  			t.Logf("%s", output)
   310  			t.Fatalf("running 'harness -tp %s': %v", tp, err)
   311  		}
   312  		upmergeCoverData(t, edir)
   313  		upmergeCoverData(t, rdir)
   314  	})
   315  }
   316  
   317  func testEmitToFailingWriter(t *testing.T, harnessPath string, dir string) {
   318  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   319  		tp := "emitToFailingWriter"
   320  		rdir, edir := mktestdirs(t, tag, tp, dir)
   321  		output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
   322  		if err != nil {
   323  			t.Logf("%s", output)
   324  			t.Fatalf("running 'harness -tp %s': %v", tp, err)
   325  		}
   326  		upmergeCoverData(t, edir)
   327  		upmergeCoverData(t, rdir)
   328  	})
   329  }
   330  
   331  func testEmitWithCounterClear(t *testing.T, harnessPath string, dir string) {
   332  	// Ensure that we have two versions of the harness: one built with
   333  	// -covermode=atomic and one built with -covermode=set (we need
   334  	// both modes to test all of the functionality).
   335  	var nonatomicHarnessPath, atomicHarnessPath string
   336  	if testing.CoverMode() != "atomic" {
   337  		nonatomicHarnessPath = harnessPath
   338  		bdir2 := mkdir(t, filepath.Join(dir, "build2"))
   339  		hargs := []string{"-covermode=atomic", "-coverpkg=all"}
   340  		atomicHarnessPath = buildHarness(t, bdir2, hargs)
   341  	} else {
   342  		atomicHarnessPath = harnessPath
   343  		mode := "set"
   344  		if testing.CoverMode() != "" && testing.CoverMode() != "atomic" {
   345  			mode = testing.CoverMode()
   346  		}
   347  		// Build a special nonatomic covermode version of the harness
   348  		// (we need both modes to test all of the functionality).
   349  		bdir2 := mkdir(t, filepath.Join(dir, "build2"))
   350  		hargs := []string{"-covermode=" + mode, "-coverpkg=all"}
   351  		nonatomicHarnessPath = buildHarness(t, bdir2, hargs)
   352  	}
   353  
   354  	withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
   355  		// First a run with the nonatomic harness path, which we
   356  		// expect to fail.
   357  		tp := "emitWithCounterClear"
   358  		rdir1, edir1 := mktestdirs(t, tag, tp+"1", dir)
   359  		output, err := runHarness(t, nonatomicHarnessPath, tp,
   360  			setGoCoverDir, rdir1, edir1)
   361  		if err == nil {
   362  			t.Logf("%s", output)
   363  			t.Fatalf("running '%s -tp %s': unexpected success",
   364  				nonatomicHarnessPath, tp)
   365  		}
   366  
   367  		// Next a run with the atomic harness path, which we
   368  		// expect to succeed.
   369  		rdir2, edir2 := mktestdirs(t, tag, tp+"2", dir)
   370  		output, err = runHarness(t, atomicHarnessPath, tp,
   371  			setGoCoverDir, rdir2, edir2)
   372  		if err != nil {
   373  			t.Logf("%s", output)
   374  			t.Fatalf("running 'harness -tp %s': %v", tp, err)
   375  		}
   376  		want := []string{tp, "postClear"}
   377  		avoid := []string{"preClear", "main", "final"}
   378  		if msg := testForSpecificFunctions(t, edir2, want, avoid); msg != "" {
   379  			t.Logf("%s", output)
   380  			t.Errorf("coverage data from %q output match failed: %s", tp, msg)
   381  		}
   382  
   383  		if testing.CoverMode() == "atomic" {
   384  			upmergeCoverData(t, edir2)
   385  			upmergeCoverData(t, rdir2)
   386  		} else {
   387  			upmergeCoverData(t, edir1)
   388  			upmergeCoverData(t, rdir1)
   389  		}
   390  	})
   391  }
   392  
   393  func TestApisOnNocoverBinary(t *testing.T) {
   394  	if testing.Short() {
   395  		t.Skipf("skipping test: too long for short mode")
   396  	}
   397  	testenv.MustHaveGoBuild(t)
   398  	dir := t.TempDir()
   399  
   400  	// Build harness with no -cover.
   401  	bdir := mkdir(t, filepath.Join(dir, "nocover"))
   402  	edir := mkdir(t, filepath.Join(dir, "emitDirNo"))
   403  	harnessPath := buildHarness(t, bdir, nil)
   404  	output, err := runHarness(t, harnessPath, "emitToDir", false, edir, edir)
   405  	if err == nil {
   406  		t.Fatalf("expected error on TestApisOnNocoverBinary harness run")
   407  	}
   408  	const want = "not built with -cover"
   409  	if !strings.Contains(output, want) {
   410  		t.Errorf("error output does not contain %q: %s", want, output)
   411  	}
   412  }
   413  
   414  func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
   415  	if testing.Short() {
   416  		t.Skipf("skipping test: too long for short mode")
   417  	}
   418  	if !goexperiment.CoverageRedesign {
   419  		t.Skipf("skipping new coverage tests (experiment not enabled)")
   420  	}
   421  
   422  	// This test requires "go test -race -cover", meaning that we need
   423  	// go build, go run, and "-race" support.
   424  	testenv.MustHaveGoRun(t)
   425  	if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) ||
   426  		!testenv.HasCGO() {
   427  		t.Skip("skipped due to lack of race detector support / CGO")
   428  	}
   429  
   430  	// This will run a program with -cover and -race where we have a
   431  	// goroutine still running (and updating counters) at the point where
   432  	// the test runtime is trying to write out counter data.
   433  	cmd := exec.Command(testenv.GoToolPath(t), "test", "-cover", "-race")
   434  	cmd.Dir = filepath.Join("testdata", "issue56006")
   435  	b, err := cmd.CombinedOutput()
   436  	if err != nil {
   437  		t.Fatalf("go test -cover -race failed: %v", err)
   438  	}
   439  
   440  	// Don't want to see any data races in output.
   441  	avoid := []string{"DATA RACE"}
   442  	for _, no := range avoid {
   443  		if strings.Contains(string(b), no) {
   444  			t.Logf("%s\n", string(b))
   445  			t.Fatalf("found %s in test output, not permitted", no)
   446  		}
   447  	}
   448  }