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 }