github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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. We need two copies of the harness, one built 40 // with -covermode=atomic and one built non-atomic. 41 bdir1 := mkdir(t, filepath.Join(dir, "build1")) 42 hargs1 := []string{"-covermode=atomic", "-coverpkg=all"} 43 atomicHarnessPath := buildHarness(t, bdir1, hargs1) 44 nonAtomicMode := testing.CoverMode() 45 if testing.CoverMode() == "atomic" { 46 nonAtomicMode = "set" 47 } 48 bdir2 := mkdir(t, filepath.Join(dir, "build2")) 49 hargs2 := []string{"-coverpkg=all", "-covermode=" + nonAtomicMode} 50 nonAtomicHarnessPath := buildHarness(t, bdir2, hargs2) 51 52 t.Logf("atomic harness path is %s", atomicHarnessPath) 53 t.Logf("non-atomic harness path is %s", nonAtomicHarnessPath) 54 55 // Sub-tests for each API we want to inspect, plus 56 // extras for error testing. 57 t.Run("emitToDir", func(t *testing.T) { 58 t.Parallel() 59 testEmitToDir(t, atomicHarnessPath, dir) 60 }) 61 t.Run("emitToWriter", func(t *testing.T) { 62 t.Parallel() 63 testEmitToWriter(t, atomicHarnessPath, dir) 64 }) 65 t.Run("emitToNonexistentDir", func(t *testing.T) { 66 t.Parallel() 67 testEmitToNonexistentDir(t, atomicHarnessPath, dir) 68 }) 69 t.Run("emitToNilWriter", func(t *testing.T) { 70 t.Parallel() 71 testEmitToNilWriter(t, atomicHarnessPath, dir) 72 }) 73 t.Run("emitToFailingWriter", func(t *testing.T) { 74 t.Parallel() 75 testEmitToFailingWriter(t, atomicHarnessPath, dir) 76 }) 77 t.Run("emitWithCounterClear", func(t *testing.T) { 78 t.Parallel() 79 testEmitWithCounterClear(t, atomicHarnessPath, dir) 80 }) 81 t.Run("emitToDirNonAtomic", func(t *testing.T) { 82 t.Parallel() 83 testEmitToDirNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir) 84 }) 85 t.Run("emitToWriterNonAtomic", func(t *testing.T) { 86 t.Parallel() 87 testEmitToWriterNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir) 88 }) 89 t.Run("emitWithCounterClearNonAtomic", func(t *testing.T) { 90 t.Parallel() 91 testEmitWithCounterClearNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir) 92 }) 93 } 94 95 // upmergeCoverData helps improve coverage data for this package 96 // itself. If this test itself is being invoked with "-cover", then 97 // what we'd like is for package coverage data (that is, coverage for 98 // routines in "runtime/coverage") to be incorporated into the test 99 // run from the "harness.exe" runs we've just done. We can accomplish 100 // this by doing a merge from the harness gocoverdir's to the test 101 // gocoverdir. 102 func upmergeCoverData(t *testing.T, gocoverdir string, mode string) { 103 if testing.CoverMode() != mode { 104 return 105 } 106 testGoCoverDir := os.Getenv("GOCOVERDIR") 107 if testGoCoverDir == "" { 108 return 109 } 110 args := []string{"tool", "covdata", "merge", "-pkg=runtime/coverage", 111 "-o", testGoCoverDir, "-i", gocoverdir} 112 t.Logf("up-merge of covdata from %s to %s", gocoverdir, testGoCoverDir) 113 t.Logf("executing: go %+v", args) 114 cmd := exec.Command(testenv.GoToolPath(t), args...) 115 if b, err := cmd.CombinedOutput(); err != nil { 116 t.Fatalf("covdata merge failed (%v): %s", err, b) 117 } 118 } 119 120 // buildHarness builds the helper program "harness.exe". 121 func buildHarness(t *testing.T, dir string, opts []string) string { 122 harnessPath := filepath.Join(dir, "harness.exe") 123 harnessSrc := filepath.Join("testdata", "harness.go") 124 args := []string{"build", "-o", harnessPath} 125 args = append(args, opts...) 126 args = append(args, harnessSrc) 127 //t.Logf("harness build: go %+v\n", args) 128 cmd := exec.Command(testenv.GoToolPath(t), args...) 129 if b, err := cmd.CombinedOutput(); err != nil { 130 t.Fatalf("build failed (%v): %s", err, b) 131 } 132 return harnessPath 133 } 134 135 func mkdir(t *testing.T, d string) string { 136 t.Helper() 137 if err := os.Mkdir(d, 0777); err != nil { 138 t.Fatalf("mkdir failed: %v", err) 139 } 140 return d 141 } 142 143 // updateGoCoverDir updates the specified environment 'env' to set 144 // GOCOVERDIR to 'gcd' (if setGoCoverDir is TRUE) or removes 145 // GOCOVERDIR from the environment (if setGoCoverDir is false). 146 func updateGoCoverDir(env []string, gcd string, setGoCoverDir bool) []string { 147 rv := []string{} 148 found := false 149 for _, v := range env { 150 if strings.HasPrefix(v, "GOCOVERDIR=") { 151 if !setGoCoverDir { 152 continue 153 } 154 v = "GOCOVERDIR=" + gcd 155 found = true 156 } 157 rv = append(rv, v) 158 } 159 if !found && setGoCoverDir { 160 rv = append(rv, "GOCOVERDIR="+gcd) 161 } 162 return rv 163 } 164 165 func runHarness(t *testing.T, harnessPath string, tp string, setGoCoverDir bool, rdir, edir string) (string, error) { 166 t.Logf("running: %s -tp %s -o %s with rdir=%s and GOCOVERDIR=%v", harnessPath, tp, edir, rdir, setGoCoverDir) 167 cmd := exec.Command(harnessPath, "-tp", tp, "-o", edir) 168 cmd.Dir = rdir 169 cmd.Env = updateGoCoverDir(os.Environ(), rdir, setGoCoverDir) 170 b, err := cmd.CombinedOutput() 171 //t.Logf("harness run output: %s\n", string(b)) 172 return string(b), err 173 } 174 175 func testForSpecificFunctions(t *testing.T, dir string, want []string, avoid []string) string { 176 args := []string{"tool", "covdata", "debugdump", 177 "-live", "-pkg=command-line-arguments", "-i=" + dir} 178 t.Logf("running: go %v\n", args) 179 cmd := exec.Command(testenv.GoToolPath(t), args...) 180 b, err := cmd.CombinedOutput() 181 if err != nil { 182 t.Fatalf("'go tool covdata failed (%v): %s", err, b) 183 } 184 output := string(b) 185 rval := "" 186 for _, f := range want { 187 wf := "Func: " + f + "\n" 188 if strings.Contains(output, wf) { 189 continue 190 } 191 rval += fmt.Sprintf("error: output should contain %q but does not\n", wf) 192 } 193 for _, f := range avoid { 194 wf := "Func: " + f + "\n" 195 if strings.Contains(output, wf) { 196 rval += fmt.Sprintf("error: output should not contain %q but does\n", wf) 197 } 198 } 199 if rval != "" { 200 t.Logf("=-= begin output:\n" + output + "\n=-= end output\n") 201 } 202 return rval 203 } 204 205 func withAndWithoutRunner(f func(setit bool, tag string)) { 206 // Run 'f' with and without GOCOVERDIR set. 207 for i := 0; i < 2; i++ { 208 tag := "x" 209 setGoCoverDir := true 210 if i == 0 { 211 setGoCoverDir = false 212 tag = "y" 213 } 214 f(setGoCoverDir, tag) 215 } 216 } 217 218 func mktestdirs(t *testing.T, tag, tp, dir string) (string, string) { 219 t.Helper() 220 rdir := mkdir(t, filepath.Join(dir, tp+"-rdir-"+tag)) 221 edir := mkdir(t, filepath.Join(dir, tp+"-edir-"+tag)) 222 return rdir, edir 223 } 224 225 func testEmitToDir(t *testing.T, harnessPath string, dir string) { 226 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 227 tp := "emitToDir" 228 rdir, edir := mktestdirs(t, tag, tp, dir) 229 output, err := runHarness(t, harnessPath, tp, 230 setGoCoverDir, rdir, edir) 231 if err != nil { 232 t.Logf("%s", output) 233 t.Fatalf("running 'harness -tp emitDir': %v", err) 234 } 235 236 // Just check to make sure meta-data file and counter data file were 237 // written. Another alternative would be to run "go tool covdata" 238 // or equivalent, but for now, this is what we've got. 239 dents, err := os.ReadDir(edir) 240 if err != nil { 241 t.Fatalf("os.ReadDir(%s) failed: %v", edir, err) 242 } 243 mfc := 0 244 cdc := 0 245 for _, e := range dents { 246 if e.IsDir() { 247 continue 248 } 249 if strings.HasPrefix(e.Name(), coverage.MetaFilePref) { 250 mfc++ 251 } else if strings.HasPrefix(e.Name(), coverage.CounterFilePref) { 252 cdc++ 253 } 254 } 255 wantmf := 1 256 wantcf := 1 257 if mfc != wantmf { 258 t.Errorf("EmitToDir: want %d meta-data files, got %d\n", wantmf, mfc) 259 } 260 if cdc != wantcf { 261 t.Errorf("EmitToDir: want %d counter-data files, got %d\n", wantcf, cdc) 262 } 263 upmergeCoverData(t, edir, "atomic") 264 upmergeCoverData(t, rdir, "atomic") 265 }) 266 } 267 268 func testEmitToWriter(t *testing.T, harnessPath string, dir string) { 269 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 270 tp := "emitToWriter" 271 rdir, edir := mktestdirs(t, tag, tp, dir) 272 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir) 273 if err != nil { 274 t.Logf("%s", output) 275 t.Fatalf("running 'harness -tp %s': %v", tp, err) 276 } 277 want := []string{"main", tp} 278 avoid := []string{"final"} 279 if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" { 280 t.Errorf("coverage data from %q output match failed: %s", tp, msg) 281 } 282 upmergeCoverData(t, edir, "atomic") 283 upmergeCoverData(t, rdir, "atomic") 284 }) 285 } 286 287 func testEmitToNonexistentDir(t *testing.T, harnessPath string, dir string) { 288 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 289 tp := "emitToNonexistentDir" 290 rdir, edir := mktestdirs(t, tag, tp, dir) 291 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir) 292 if err != nil { 293 t.Logf("%s", output) 294 t.Fatalf("running 'harness -tp %s': %v", tp, err) 295 } 296 upmergeCoverData(t, edir, "atomic") 297 upmergeCoverData(t, rdir, "atomic") 298 }) 299 } 300 301 func testEmitToUnwritableDir(t *testing.T, harnessPath string, dir string) { 302 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 303 304 tp := "emitToUnwritableDir" 305 rdir, edir := mktestdirs(t, tag, tp, dir) 306 307 // Make edir unwritable. 308 if err := os.Chmod(edir, 0555); err != nil { 309 t.Fatalf("chmod failed: %v", err) 310 } 311 defer os.Chmod(edir, 0777) 312 313 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir) 314 if err != nil { 315 t.Logf("%s", output) 316 t.Fatalf("running 'harness -tp %s': %v", tp, err) 317 } 318 upmergeCoverData(t, edir, "atomic") 319 upmergeCoverData(t, rdir, "atomic") 320 }) 321 } 322 323 func testEmitToNilWriter(t *testing.T, harnessPath string, dir string) { 324 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 325 tp := "emitToNilWriter" 326 rdir, edir := mktestdirs(t, tag, tp, dir) 327 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir) 328 if err != nil { 329 t.Logf("%s", output) 330 t.Fatalf("running 'harness -tp %s': %v", tp, err) 331 } 332 upmergeCoverData(t, edir, "atomic") 333 upmergeCoverData(t, rdir, "atomic") 334 }) 335 } 336 337 func testEmitToFailingWriter(t *testing.T, harnessPath string, dir string) { 338 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 339 tp := "emitToFailingWriter" 340 rdir, edir := mktestdirs(t, tag, tp, dir) 341 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir) 342 if err != nil { 343 t.Logf("%s", output) 344 t.Fatalf("running 'harness -tp %s': %v", tp, err) 345 } 346 upmergeCoverData(t, edir, "atomic") 347 upmergeCoverData(t, rdir, "atomic") 348 }) 349 } 350 351 func testEmitWithCounterClear(t *testing.T, harnessPath string, dir string) { 352 withAndWithoutRunner(func(setGoCoverDir bool, tag string) { 353 tp := "emitWithCounterClear" 354 rdir, edir := mktestdirs(t, tag, tp, dir) 355 output, err := runHarness(t, harnessPath, tp, 356 setGoCoverDir, rdir, edir) 357 if err != nil { 358 t.Logf("%s", output) 359 t.Fatalf("running 'harness -tp %s': %v", tp, err) 360 } 361 want := []string{tp, "postClear"} 362 avoid := []string{"preClear", "main", "final"} 363 if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" { 364 t.Logf("%s", output) 365 t.Errorf("coverage data from %q output match failed: %s", tp, msg) 366 } 367 upmergeCoverData(t, edir, "atomic") 368 upmergeCoverData(t, rdir, "atomic") 369 }) 370 } 371 372 func testEmitToDirNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) { 373 tp := "emitToDir" 374 tag := "nonatomdir" 375 rdir, edir := mktestdirs(t, tag, tp, dir) 376 output, err := runHarness(t, harnessPath, tp, 377 true, rdir, edir) 378 379 // We expect an error here. 380 if err == nil { 381 t.Logf("%s", output) 382 t.Fatalf("running 'harness -tp %s': did not get expected error", tp) 383 } 384 385 got := strings.TrimSpace(string(output)) 386 want := "WriteCountersDir invoked for program built" 387 if !strings.Contains(got, want) { 388 t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s", 389 tp, got, want) 390 } 391 upmergeCoverData(t, edir, naMode) 392 upmergeCoverData(t, rdir, naMode) 393 } 394 395 func testEmitToWriterNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) { 396 tp := "emitToWriter" 397 tag := "nonatomw" 398 rdir, edir := mktestdirs(t, tag, tp, dir) 399 output, err := runHarness(t, harnessPath, tp, 400 true, rdir, edir) 401 402 // We expect an error here. 403 if err == nil { 404 t.Logf("%s", output) 405 t.Fatalf("running 'harness -tp %s': did not get expected error", tp) 406 } 407 408 got := strings.TrimSpace(string(output)) 409 want := "WriteCounters invoked for program built" 410 if !strings.Contains(got, want) { 411 t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s", 412 tp, got, want) 413 } 414 415 upmergeCoverData(t, edir, naMode) 416 upmergeCoverData(t, rdir, naMode) 417 } 418 419 func testEmitWithCounterClearNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) { 420 tp := "emitWithCounterClear" 421 tag := "cclear" 422 rdir, edir := mktestdirs(t, tag, tp, dir) 423 output, err := runHarness(t, harnessPath, tp, 424 true, rdir, edir) 425 426 // We expect an error here. 427 if err == nil { 428 t.Logf("%s", output) 429 t.Fatalf("running 'harness -tp %s' nonatomic: did not get expected error", tp) 430 } 431 432 got := strings.TrimSpace(string(output)) 433 want := "ClearCounters invoked for program built" 434 if !strings.Contains(got, want) { 435 t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s", 436 tp, got, want) 437 } 438 439 upmergeCoverData(t, edir, naMode) 440 upmergeCoverData(t, rdir, naMode) 441 } 442 443 func TestApisOnNocoverBinary(t *testing.T) { 444 if testing.Short() { 445 t.Skipf("skipping test: too long for short mode") 446 } 447 testenv.MustHaveGoBuild(t) 448 dir := t.TempDir() 449 450 // Build harness with no -cover. 451 bdir := mkdir(t, filepath.Join(dir, "nocover")) 452 edir := mkdir(t, filepath.Join(dir, "emitDirNo")) 453 harnessPath := buildHarness(t, bdir, nil) 454 output, err := runHarness(t, harnessPath, "emitToDir", false, edir, edir) 455 if err == nil { 456 t.Fatalf("expected error on TestApisOnNocoverBinary harness run") 457 } 458 const want = "not built with -cover" 459 if !strings.Contains(output, want) { 460 t.Errorf("error output does not contain %q: %s", want, output) 461 } 462 } 463 464 func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) { 465 if testing.Short() { 466 t.Skipf("skipping test: too long for short mode") 467 } 468 if !goexperiment.CoverageRedesign { 469 t.Skipf("skipping new coverage tests (experiment not enabled)") 470 } 471 472 // This test requires "go test -race -cover", meaning that we need 473 // go build, go run, and "-race" support. 474 testenv.MustHaveGoRun(t) 475 if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) || 476 !testenv.HasCGO() { 477 t.Skip("skipped due to lack of race detector support / CGO") 478 } 479 480 // This will run a program with -cover and -race where we have a 481 // goroutine still running (and updating counters) at the point where 482 // the test runtime is trying to write out counter data. 483 cmd := exec.Command(testenv.GoToolPath(t), "test", "-cover", "-race") 484 cmd.Dir = filepath.Join("testdata", "issue56006") 485 b, err := cmd.CombinedOutput() 486 if err != nil { 487 t.Fatalf("go test -cover -race failed: %v", err) 488 } 489 490 // Don't want to see any data races in output. 491 avoid := []string{"DATA RACE"} 492 for _, no := range avoid { 493 if strings.Contains(string(b), no) { 494 t.Logf("%s\n", string(b)) 495 t.Fatalf("found %s in test output, not permitted", no) 496 } 497 } 498 } 499 500 func TestIssue59563TruncatedCoverPkgAll(t *testing.T) { 501 if testing.Short() { 502 t.Skipf("skipping test: too long for short mode") 503 } 504 testenv.MustHaveGoRun(t) 505 506 tmpdir := t.TempDir() 507 ppath := filepath.Join(tmpdir, "foo.cov") 508 509 cmd := exec.Command(testenv.GoToolPath(t), "test", "-coverpkg=all", "-coverprofile="+ppath) 510 cmd.Dir = filepath.Join("testdata", "issue59563") 511 b, err := cmd.CombinedOutput() 512 if err != nil { 513 t.Fatalf("go test -cover failed: %v", err) 514 } 515 516 cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath) 517 b, err = cmd.CombinedOutput() 518 if err != nil { 519 t.Fatalf("go tool cover -func failed: %v", err) 520 } 521 522 lines := strings.Split(string(b), "\n") 523 nfound := 0 524 bad := false 525 for _, line := range lines { 526 f := strings.Fields(line) 527 if len(f) == 0 { 528 continue 529 } 530 // We're only interested in the specific function "large" for 531 // the testcase being built. See the #59563 for details on why 532 // size matters. 533 if !(strings.HasPrefix(f[0], "runtime/coverage/testdata/issue59563/repro.go") && strings.Contains(line, "large")) { 534 continue 535 } 536 nfound++ 537 want := "100.0%" 538 if f[len(f)-1] != want { 539 t.Errorf("wanted %s got: %q\n", want, line) 540 bad = true 541 } 542 } 543 if nfound != 1 { 544 t.Errorf("wanted 1 found, got %d\n", nfound) 545 bad = true 546 } 547 if bad { 548 t.Logf("func output:\n%s\n", string(b)) 549 } 550 }