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