github.com/likebike/go--@v0.0.0-20190911215757-0bd925d16e96/go/src/runtime/crash_test.go (about) 1 // Copyright 2012 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 runtime_test 6 7 import ( 8 "bytes" 9 "flag" 10 "fmt" 11 "internal/testenv" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "strconv" 19 "strings" 20 "sync" 21 "testing" 22 "time" 23 ) 24 25 var toRemove []string 26 27 func TestMain(m *testing.M) { 28 status := m.Run() 29 for _, file := range toRemove { 30 os.RemoveAll(file) 31 } 32 os.Exit(status) 33 } 34 35 var testprog struct { 36 sync.Mutex 37 dir string 38 target map[string]buildexe 39 } 40 41 type buildexe struct { 42 exe string 43 err error 44 } 45 46 func runTestProg(t *testing.T, binary, name string, env ...string) string { 47 if *flagQuick { 48 t.Skip("-quick") 49 } 50 51 testenv.MustHaveGoBuild(t) 52 53 exe, err := buildTestProg(t, binary) 54 if err != nil { 55 t.Fatal(err) 56 } 57 58 cmd := testenv.CleanCmdEnv(exec.Command(exe, name)) 59 cmd.Env = append(cmd.Env, env...) 60 if testing.Short() { 61 cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1") 62 } 63 var b bytes.Buffer 64 cmd.Stdout = &b 65 cmd.Stderr = &b 66 if err := cmd.Start(); err != nil { 67 t.Fatalf("starting %s %s: %v", binary, name, err) 68 } 69 70 // If the process doesn't complete within 1 minute, 71 // assume it is hanging and kill it to get a stack trace. 72 p := cmd.Process 73 done := make(chan bool) 74 go func() { 75 scale := 1 76 // This GOARCH/GOOS test is copied from cmd/dist/test.go. 77 // TODO(iant): Have cmd/dist update the environment variable. 78 if runtime.GOARCH == "arm" || runtime.GOOS == "windows" { 79 scale = 2 80 } 81 if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { 82 if sc, err := strconv.Atoi(s); err == nil { 83 scale = sc 84 } 85 } 86 87 select { 88 case <-done: 89 case <-time.After(time.Duration(scale) * time.Minute): 90 p.Signal(sigquit) 91 } 92 }() 93 94 if err := cmd.Wait(); err != nil { 95 t.Logf("%s %s exit status: %v", binary, name, err) 96 } 97 close(done) 98 99 return b.String() 100 } 101 102 func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) { 103 if *flagQuick { 104 t.Skip("-quick") 105 } 106 107 checkStaleRuntime(t) 108 109 testprog.Lock() 110 defer testprog.Unlock() 111 if testprog.dir == "" { 112 dir, err := ioutil.TempDir("", "go-build") 113 if err != nil { 114 t.Fatalf("failed to create temp directory: %v", err) 115 } 116 testprog.dir = dir 117 toRemove = append(toRemove, dir) 118 } 119 120 if testprog.target == nil { 121 testprog.target = make(map[string]buildexe) 122 } 123 name := binary 124 if len(flags) > 0 { 125 name += "_" + strings.Join(flags, "_") 126 } 127 target, ok := testprog.target[name] 128 if ok { 129 return target.exe, target.err 130 } 131 132 exe := filepath.Join(testprog.dir, name+".exe") 133 cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...) 134 cmd.Dir = "testdata/" + binary 135 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 136 if err != nil { 137 target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out) 138 testprog.target[name] = target 139 return "", target.err 140 } 141 target.exe = exe 142 testprog.target[name] = target 143 return exe, nil 144 } 145 146 var ( 147 staleRuntimeOnce sync.Once // guards init of staleRuntimeErr 148 staleRuntimeErr error 149 ) 150 151 func checkStaleRuntime(t *testing.T) { 152 staleRuntimeOnce.Do(func() { 153 // 'go run' uses the installed copy of runtime.a, which may be out of date. 154 out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.Stale}}", "runtime")).CombinedOutput() 155 if err != nil { 156 staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out)) 157 return 158 } 159 if string(out) != "false\n" { 160 t.Logf("go list -f {{.Stale}} runtime:\n%s", out) 161 out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.StaleReason}}", "runtime")).CombinedOutput() 162 if err != nil { 163 t.Logf("go list -f {{.StaleReason}} failed: %v", err) 164 } 165 t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out) 166 staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.") 167 } 168 }) 169 if staleRuntimeErr != nil { 170 t.Fatal(staleRuntimeErr) 171 } 172 } 173 174 func testCrashHandler(t *testing.T, cgo bool) { 175 type crashTest struct { 176 Cgo bool 177 } 178 var output string 179 if cgo { 180 output = runTestProg(t, "testprogcgo", "Crash") 181 } else { 182 output = runTestProg(t, "testprog", "Crash") 183 } 184 want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" 185 if output != want { 186 t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want) 187 } 188 } 189 190 func TestCrashHandler(t *testing.T) { 191 testCrashHandler(t, false) 192 } 193 194 func testDeadlock(t *testing.T, name string) { 195 output := runTestProg(t, "testprog", name) 196 want := "fatal error: all goroutines are asleep - deadlock!\n" 197 if !strings.HasPrefix(output, want) { 198 t.Fatalf("output does not start with %q:\n%s", want, output) 199 } 200 } 201 202 func TestSimpleDeadlock(t *testing.T) { 203 testDeadlock(t, "SimpleDeadlock") 204 } 205 206 func TestInitDeadlock(t *testing.T) { 207 testDeadlock(t, "InitDeadlock") 208 } 209 210 func TestLockedDeadlock(t *testing.T) { 211 testDeadlock(t, "LockedDeadlock") 212 } 213 214 func TestLockedDeadlock2(t *testing.T) { 215 testDeadlock(t, "LockedDeadlock2") 216 } 217 218 func TestGoexitDeadlock(t *testing.T) { 219 output := runTestProg(t, "testprog", "GoexitDeadlock") 220 want := "no goroutines (main called runtime.Goexit) - deadlock!" 221 if !strings.Contains(output, want) { 222 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 223 } 224 } 225 226 func TestStackOverflow(t *testing.T) { 227 output := runTestProg(t, "testprog", "StackOverflow") 228 want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow" 229 if !strings.HasPrefix(output, want) { 230 t.Fatalf("output does not start with %q:\n%s", want, output) 231 } 232 } 233 234 func TestThreadExhaustion(t *testing.T) { 235 output := runTestProg(t, "testprog", "ThreadExhaustion") 236 want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion" 237 if !strings.HasPrefix(output, want) { 238 t.Fatalf("output does not start with %q:\n%s", want, output) 239 } 240 } 241 242 func TestRecursivePanic(t *testing.T) { 243 output := runTestProg(t, "testprog", "RecursivePanic") 244 want := `wrap: bad 245 panic: again 246 247 ` 248 if !strings.HasPrefix(output, want) { 249 t.Fatalf("output does not start with %q:\n%s", want, output) 250 } 251 252 } 253 254 func TestGoexitCrash(t *testing.T) { 255 output := runTestProg(t, "testprog", "GoexitExit") 256 want := "no goroutines (main called runtime.Goexit) - deadlock!" 257 if !strings.Contains(output, want) { 258 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 259 } 260 } 261 262 func TestGoexitDefer(t *testing.T) { 263 c := make(chan struct{}) 264 go func() { 265 defer func() { 266 r := recover() 267 if r != nil { 268 t.Errorf("non-nil recover during Goexit") 269 } 270 c <- struct{}{} 271 }() 272 runtime.Goexit() 273 }() 274 // Note: if the defer fails to run, we will get a deadlock here 275 <-c 276 } 277 278 func TestGoNil(t *testing.T) { 279 output := runTestProg(t, "testprog", "GoNil") 280 want := "go of nil func value" 281 if !strings.Contains(output, want) { 282 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 283 } 284 } 285 286 func TestMainGoroutineID(t *testing.T) { 287 output := runTestProg(t, "testprog", "MainGoroutineID") 288 want := "panic: test\n\ngoroutine 1 [running]:\n" 289 if !strings.HasPrefix(output, want) { 290 t.Fatalf("output does not start with %q:\n%s", want, output) 291 } 292 } 293 294 func TestNoHelperGoroutines(t *testing.T) { 295 output := runTestProg(t, "testprog", "NoHelperGoroutines") 296 matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1) 297 if len(matches) != 1 || matches[0][0] != "goroutine 1 [" { 298 t.Fatalf("want to see only goroutine 1, see:\n%s", output) 299 } 300 } 301 302 func TestBreakpoint(t *testing.T) { 303 output := runTestProg(t, "testprog", "Breakpoint") 304 // If runtime.Breakpoint() is inlined, then the stack trace prints 305 // "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()". 306 want := "runtime.Breakpoint(" 307 if !strings.Contains(output, want) { 308 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 309 } 310 } 311 312 func TestGoexitInPanic(t *testing.T) { 313 // see issue 8774: this code used to trigger an infinite recursion 314 output := runTestProg(t, "testprog", "GoexitInPanic") 315 want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" 316 if !strings.HasPrefix(output, want) { 317 t.Fatalf("output does not start with %q:\n%s", want, output) 318 } 319 } 320 321 // Issue 14965: Runtime panics should be of type runtime.Error 322 func TestRuntimePanicWithRuntimeError(t *testing.T) { 323 testCases := [...]func(){ 324 0: func() { 325 var m map[uint64]bool 326 m[1234] = true 327 }, 328 1: func() { 329 ch := make(chan struct{}) 330 close(ch) 331 close(ch) 332 }, 333 2: func() { 334 var ch = make(chan struct{}) 335 close(ch) 336 ch <- struct{}{} 337 }, 338 3: func() { 339 var s = make([]int, 2) 340 _ = s[2] 341 }, 342 4: func() { 343 n := -1 344 _ = make(chan bool, n) 345 }, 346 5: func() { 347 close((chan bool)(nil)) 348 }, 349 } 350 351 for i, fn := range testCases { 352 got := panicValue(fn) 353 if _, ok := got.(runtime.Error); !ok { 354 t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got) 355 } 356 } 357 } 358 359 func panicValue(fn func()) (recovered interface{}) { 360 defer func() { 361 recovered = recover() 362 }() 363 fn() 364 return 365 } 366 367 func TestPanicAfterGoexit(t *testing.T) { 368 // an uncaught panic should still work after goexit 369 output := runTestProg(t, "testprog", "PanicAfterGoexit") 370 want := "panic: hello" 371 if !strings.HasPrefix(output, want) { 372 t.Fatalf("output does not start with %q:\n%s", want, output) 373 } 374 } 375 376 func TestRecoveredPanicAfterGoexit(t *testing.T) { 377 output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit") 378 want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" 379 if !strings.HasPrefix(output, want) { 380 t.Fatalf("output does not start with %q:\n%s", want, output) 381 } 382 } 383 384 func TestRecoverBeforePanicAfterGoexit(t *testing.T) { 385 // 1. defer a function that recovers 386 // 2. defer a function that panics 387 // 3. call goexit 388 // Goexit should run the #2 defer. Its panic 389 // should be caught by the #1 defer, and execution 390 // should resume in the caller. Like the Goexit 391 // never happened! 392 defer func() { 393 r := recover() 394 if r == nil { 395 panic("bad recover") 396 } 397 }() 398 defer func() { 399 panic("hello") 400 }() 401 runtime.Goexit() 402 } 403 404 func TestNetpollDeadlock(t *testing.T) { 405 t.Parallel() 406 output := runTestProg(t, "testprognet", "NetpollDeadlock") 407 want := "done\n" 408 if !strings.HasSuffix(output, want) { 409 t.Fatalf("output does not start with %q:\n%s", want, output) 410 } 411 } 412 413 func TestPanicTraceback(t *testing.T) { 414 t.Parallel() 415 output := runTestProg(t, "testprog", "PanicTraceback") 416 want := "panic: hello" 417 if !strings.HasPrefix(output, want) { 418 t.Fatalf("output does not start with %q:\n%s", want, output) 419 } 420 421 // Check functions in the traceback. 422 fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"} 423 for _, fn := range fns { 424 re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`) 425 idx := re.FindStringIndex(output) 426 if idx == nil { 427 t.Fatalf("expected %q function in traceback:\n%s", fn, output) 428 } 429 output = output[idx[1]:] 430 } 431 } 432 433 func testPanicDeadlock(t *testing.T, name string, want string) { 434 // test issue 14432 435 output := runTestProg(t, "testprog", name) 436 if !strings.HasPrefix(output, want) { 437 t.Fatalf("output does not start with %q:\n%s", want, output) 438 } 439 } 440 441 func TestPanicDeadlockGosched(t *testing.T) { 442 testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n") 443 } 444 445 func TestPanicDeadlockSyscall(t *testing.T) { 446 testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n") 447 } 448 449 func TestPanicLoop(t *testing.T) { 450 output := runTestProg(t, "testprog", "PanicLoop") 451 if want := "panic while printing panic value"; !strings.Contains(output, want) { 452 t.Errorf("output does not contain %q:\n%s", want, output) 453 } 454 } 455 456 func TestMemPprof(t *testing.T) { 457 testenv.MustHaveGoRun(t) 458 459 exe, err := buildTestProg(t, "testprog") 460 if err != nil { 461 t.Fatal(err) 462 } 463 464 got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput() 465 if err != nil { 466 t.Fatal(err) 467 } 468 fn := strings.TrimSpace(string(got)) 469 defer os.Remove(fn) 470 471 for try := 0; try < 2; try++ { 472 cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top")) 473 // Check that pprof works both with and without explicit executable on command line. 474 if try == 0 { 475 cmd.Args = append(cmd.Args, exe, fn) 476 } else { 477 cmd.Args = append(cmd.Args, fn) 478 } 479 found := false 480 for i, e := range cmd.Env { 481 if strings.HasPrefix(e, "PPROF_TMPDIR=") { 482 cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir() 483 found = true 484 break 485 } 486 } 487 if !found { 488 cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir()) 489 } 490 491 top, err := cmd.CombinedOutput() 492 t.Logf("%s:\n%s", cmd.Args, top) 493 if err != nil { 494 t.Error(err) 495 } else if !bytes.Contains(top, []byte("MemProf")) { 496 t.Error("missing MemProf in pprof output") 497 } 498 } 499 } 500 501 var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests") 502 503 func TestConcurrentMapWrites(t *testing.T) { 504 if !*concurrentMapTest { 505 t.Skip("skipping without -run_concurrent_map_tests") 506 } 507 testenv.MustHaveGoRun(t) 508 output := runTestProg(t, "testprog", "concurrentMapWrites") 509 want := "fatal error: concurrent map writes" 510 if !strings.HasPrefix(output, want) { 511 t.Fatalf("output does not start with %q:\n%s", want, output) 512 } 513 } 514 func TestConcurrentMapReadWrite(t *testing.T) { 515 if !*concurrentMapTest { 516 t.Skip("skipping without -run_concurrent_map_tests") 517 } 518 testenv.MustHaveGoRun(t) 519 output := runTestProg(t, "testprog", "concurrentMapReadWrite") 520 want := "fatal error: concurrent map read and map write" 521 if !strings.HasPrefix(output, want) { 522 t.Fatalf("output does not start with %q:\n%s", want, output) 523 } 524 } 525 func TestConcurrentMapIterateWrite(t *testing.T) { 526 if !*concurrentMapTest { 527 t.Skip("skipping without -run_concurrent_map_tests") 528 } 529 testenv.MustHaveGoRun(t) 530 output := runTestProg(t, "testprog", "concurrentMapIterateWrite") 531 want := "fatal error: concurrent map iteration and map write" 532 if !strings.HasPrefix(output, want) { 533 t.Fatalf("output does not start with %q:\n%s", want, output) 534 } 535 } 536 537 type point struct { 538 x, y *int 539 } 540 541 func (p *point) negate() { 542 *p.x = *p.x * -1 543 *p.y = *p.y * -1 544 } 545 546 // Test for issue #10152. 547 func TestPanicInlined(t *testing.T) { 548 defer func() { 549 r := recover() 550 if r == nil { 551 t.Fatalf("recover failed") 552 } 553 buf := make([]byte, 2048) 554 n := runtime.Stack(buf, false) 555 buf = buf[:n] 556 if !bytes.Contains(buf, []byte("(*point).negate(")) { 557 t.Fatalf("expecting stack trace to contain call to (*point).negate()") 558 } 559 }() 560 561 pt := new(point) 562 pt.negate() 563 } 564 565 // Test for issues #3934 and #20018. 566 // We want to delay exiting until a panic print is complete. 567 func TestPanicRace(t *testing.T) { 568 testenv.MustHaveGoRun(t) 569 570 exe, err := buildTestProg(t, "testprog") 571 if err != nil { 572 t.Fatal(err) 573 } 574 575 // The test is intentionally racy, and in my testing does not 576 // produce the expected output about 0.05% of the time. 577 // So run the program in a loop and only fail the test if we 578 // get the wrong output ten times in a row. 579 const tries = 10 580 retry: 581 for i := 0; i < tries; i++ { 582 got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput() 583 if err == nil { 584 t.Logf("try %d: program exited successfully, should have failed", i+1) 585 continue 586 } 587 588 if i > 0 { 589 t.Logf("try %d:\n", i+1) 590 } 591 t.Logf("%s\n", got) 592 593 wants := []string{ 594 "panic: crash", 595 "PanicRace", 596 "created by ", 597 } 598 for _, want := range wants { 599 if !bytes.Contains(got, []byte(want)) { 600 t.Logf("did not find expected string %q", want) 601 continue retry 602 } 603 } 604 605 // Test generated expected output. 606 return 607 } 608 t.Errorf("test ran %d times without producing expected output", tries) 609 } 610 611 func TestBadTraceback(t *testing.T) { 612 output := runTestProg(t, "testprog", "BadTraceback") 613 for _, want := range []string{ 614 "runtime: unexpected return pc", 615 "called from 0xbad", 616 "00000bad", // Smashed LR in hex dump 617 "<main.badLR", // Symbolization in hex dump (badLR1 or badLR2) 618 } { 619 if !strings.Contains(output, want) { 620 t.Errorf("output does not contain %q:\n%s", want, output) 621 } 622 } 623 }