rsc.io/go@v0.0.0-20150416155037-e040fd465409/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 "fmt" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "regexp" 14 "runtime" 15 "strings" 16 "sync" 17 "testing" 18 "text/template" 19 ) 20 21 func testEnv(cmd *exec.Cmd) *exec.Cmd { 22 if cmd.Env != nil { 23 panic("environment already set") 24 } 25 for _, env := range os.Environ() { 26 // Exclude GODEBUG from the environment to prevent its output 27 // from breaking tests that are trying to parse other command output. 28 if strings.HasPrefix(env, "GODEBUG=") { 29 continue 30 } 31 // Exclude GOTRACEBACK for the same reason. 32 if strings.HasPrefix(env, "GOTRACEBACK=") { 33 continue 34 } 35 cmd.Env = append(cmd.Env, env) 36 } 37 return cmd 38 } 39 40 func executeTest(t *testing.T, templ string, data interface{}, extra ...string) string { 41 switch runtime.GOOS { 42 case "android", "nacl": 43 t.Skipf("skipping on %s", runtime.GOOS) 44 case "darwin": 45 switch runtime.GOARCH { 46 case "arm", "arm64": 47 t.Skipf("skipping on %s/%s, no fork", runtime.GOOS, runtime.GOARCH) 48 } 49 } 50 51 checkStaleRuntime(t) 52 53 st := template.Must(template.New("crashSource").Parse(templ)) 54 55 dir, err := ioutil.TempDir("", "go-build") 56 if err != nil { 57 t.Fatalf("failed to create temp directory: %v", err) 58 } 59 defer os.RemoveAll(dir) 60 61 src := filepath.Join(dir, "main.go") 62 f, err := os.Create(src) 63 if err != nil { 64 t.Fatalf("failed to create file: %v", err) 65 } 66 err = st.Execute(f, data) 67 if err != nil { 68 f.Close() 69 t.Fatalf("failed to execute template: %v", err) 70 } 71 if err := f.Close(); err != nil { 72 t.Fatalf("failed to close file: %v", err) 73 } 74 75 for i := 0; i < len(extra); i += 2 { 76 fname := extra[i] 77 contents := extra[i+1] 78 if d, _ := filepath.Split(fname); d != "" { 79 if err := os.Mkdir(filepath.Join(dir, d), 0755); err != nil { 80 t.Fatal(err) 81 } 82 } 83 if err := ioutil.WriteFile(filepath.Join(dir, fname), []byte(contents), 0666); err != nil { 84 t.Fatal(err) 85 } 86 } 87 88 cmd := exec.Command("go", "build", "-o", "a.exe") 89 cmd.Dir = dir 90 out, err := testEnv(cmd).CombinedOutput() 91 if err != nil { 92 t.Fatalf("building source: %v\n%s", err, out) 93 } 94 95 got, _ := testEnv(exec.Command(filepath.Join(dir, "a.exe"))).CombinedOutput() 96 return string(got) 97 } 98 99 var ( 100 staleRuntimeOnce sync.Once // guards init of staleRuntimeErr 101 staleRuntimeErr error 102 ) 103 104 func checkStaleRuntime(t *testing.T) { 105 staleRuntimeOnce.Do(func() { 106 // 'go run' uses the installed copy of runtime.a, which may be out of date. 107 out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput() 108 if err != nil { 109 staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out)) 110 return 111 } 112 if string(out) != "false\n" { 113 staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.") 114 } 115 }) 116 if staleRuntimeErr != nil { 117 t.Fatal(staleRuntimeErr) 118 } 119 } 120 121 func testCrashHandler(t *testing.T, cgo bool) { 122 type crashTest struct { 123 Cgo bool 124 } 125 output := executeTest(t, crashSource, &crashTest{Cgo: cgo}) 126 want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" 127 if output != want { 128 t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want) 129 } 130 } 131 132 func TestCrashHandler(t *testing.T) { 133 testCrashHandler(t, false) 134 } 135 136 func testDeadlock(t *testing.T, source string) { 137 output := executeTest(t, source, nil) 138 want := "fatal error: all goroutines are asleep - deadlock!\n" 139 if !strings.HasPrefix(output, want) { 140 t.Fatalf("output does not start with %q:\n%s", want, output) 141 } 142 } 143 144 func TestSimpleDeadlock(t *testing.T) { 145 testDeadlock(t, simpleDeadlockSource) 146 } 147 148 func TestInitDeadlock(t *testing.T) { 149 testDeadlock(t, initDeadlockSource) 150 } 151 152 func TestLockedDeadlock(t *testing.T) { 153 testDeadlock(t, lockedDeadlockSource) 154 } 155 156 func TestLockedDeadlock2(t *testing.T) { 157 testDeadlock(t, lockedDeadlockSource2) 158 } 159 160 func TestGoexitDeadlock(t *testing.T) { 161 output := executeTest(t, goexitDeadlockSource, nil) 162 want := "no goroutines (main called runtime.Goexit) - deadlock!" 163 if !strings.Contains(output, want) { 164 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 165 } 166 } 167 168 func TestStackOverflow(t *testing.T) { 169 output := executeTest(t, stackOverflowSource, nil) 170 want := "runtime: goroutine stack exceeds 4194304-byte limit\nfatal error: stack overflow" 171 if !strings.HasPrefix(output, want) { 172 t.Fatalf("output does not start with %q:\n%s", want, output) 173 } 174 } 175 176 func TestThreadExhaustion(t *testing.T) { 177 output := executeTest(t, threadExhaustionSource, nil) 178 want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion" 179 if !strings.HasPrefix(output, want) { 180 t.Fatalf("output does not start with %q:\n%s", want, output) 181 } 182 } 183 184 func TestRecursivePanic(t *testing.T) { 185 output := executeTest(t, recursivePanicSource, nil) 186 want := `wrap: bad 187 panic: again 188 189 ` 190 if !strings.HasPrefix(output, want) { 191 t.Fatalf("output does not start with %q:\n%s", want, output) 192 } 193 194 } 195 196 func TestGoexitCrash(t *testing.T) { 197 output := executeTest(t, goexitExitSource, nil) 198 want := "no goroutines (main called runtime.Goexit) - deadlock!" 199 if !strings.Contains(output, want) { 200 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 201 } 202 } 203 204 func TestGoexitDefer(t *testing.T) { 205 c := make(chan struct{}) 206 go func() { 207 defer func() { 208 r := recover() 209 if r != nil { 210 t.Errorf("non-nil recover during Goexit") 211 } 212 c <- struct{}{} 213 }() 214 runtime.Goexit() 215 }() 216 // Note: if the defer fails to run, we will get a deadlock here 217 <-c 218 } 219 220 func TestGoNil(t *testing.T) { 221 output := executeTest(t, goNilSource, nil) 222 want := "go of nil func value" 223 if !strings.Contains(output, want) { 224 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 225 } 226 } 227 228 func TestMainGoroutineId(t *testing.T) { 229 output := executeTest(t, mainGoroutineIdSource, nil) 230 want := "panic: test\n\ngoroutine 1 [running]:\n" 231 if !strings.HasPrefix(output, want) { 232 t.Fatalf("output does not start with %q:\n%s", want, output) 233 } 234 } 235 236 func TestNoHelperGoroutines(t *testing.T) { 237 output := executeTest(t, noHelperGoroutinesSource, nil) 238 matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1) 239 if len(matches) != 1 || matches[0][0] != "goroutine 1 [" { 240 t.Fatalf("want to see only goroutine 1, see:\n%s", output) 241 } 242 } 243 244 func TestBreakpoint(t *testing.T) { 245 output := executeTest(t, breakpointSource, nil) 246 want := "runtime.Breakpoint()" 247 if !strings.Contains(output, want) { 248 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 249 } 250 } 251 252 const crashSource = ` 253 package main 254 255 import ( 256 "fmt" 257 "runtime" 258 ) 259 260 {{if .Cgo}} 261 import "C" 262 {{end}} 263 264 func test(name string) { 265 defer func() { 266 if x := recover(); x != nil { 267 fmt.Printf(" recovered") 268 } 269 fmt.Printf(" done\n") 270 }() 271 fmt.Printf("%s:", name) 272 var s *string 273 _ = *s 274 fmt.Print("SHOULD NOT BE HERE") 275 } 276 277 func testInNewThread(name string) { 278 c := make(chan bool) 279 go func() { 280 runtime.LockOSThread() 281 test(name) 282 c <- true 283 }() 284 <-c 285 } 286 287 func main() { 288 runtime.LockOSThread() 289 test("main") 290 testInNewThread("new-thread") 291 testInNewThread("second-new-thread") 292 test("main-again") 293 } 294 ` 295 296 const simpleDeadlockSource = ` 297 package main 298 func main() { 299 select {} 300 } 301 ` 302 303 const initDeadlockSource = ` 304 package main 305 func init() { 306 select {} 307 } 308 func main() { 309 } 310 ` 311 312 const lockedDeadlockSource = ` 313 package main 314 import "runtime" 315 func main() { 316 runtime.LockOSThread() 317 select {} 318 } 319 ` 320 321 const lockedDeadlockSource2 = ` 322 package main 323 import ( 324 "runtime" 325 "time" 326 ) 327 func main() { 328 go func() { 329 runtime.LockOSThread() 330 select {} 331 }() 332 time.Sleep(time.Millisecond) 333 select {} 334 } 335 ` 336 337 const goexitDeadlockSource = ` 338 package main 339 import ( 340 "runtime" 341 ) 342 343 func F() { 344 for i := 0; i < 10; i++ { 345 } 346 } 347 348 func main() { 349 go F() 350 go F() 351 runtime.Goexit() 352 } 353 ` 354 355 const stackOverflowSource = ` 356 package main 357 358 import "runtime/debug" 359 360 func main() { 361 debug.SetMaxStack(4<<20) 362 f(make([]byte, 10)) 363 } 364 365 func f(x []byte) byte { 366 var buf [64<<10]byte 367 return x[0] + f(buf[:]) 368 } 369 ` 370 371 const threadExhaustionSource = ` 372 package main 373 374 import ( 375 "runtime" 376 "runtime/debug" 377 ) 378 379 func main() { 380 debug.SetMaxThreads(10) 381 c := make(chan int) 382 for i := 0; i < 100; i++ { 383 go func() { 384 runtime.LockOSThread() 385 c <- 0 386 select{} 387 }() 388 <-c 389 } 390 } 391 ` 392 393 const recursivePanicSource = ` 394 package main 395 396 import ( 397 "fmt" 398 ) 399 400 func main() { 401 func() { 402 defer func() { 403 fmt.Println(recover()) 404 }() 405 var x [8192]byte 406 func(x [8192]byte) { 407 defer func() { 408 if err := recover(); err != nil { 409 panic("wrap: " + err.(string)) 410 } 411 }() 412 panic("bad") 413 }(x) 414 }() 415 panic("again") 416 } 417 ` 418 419 const goexitExitSource = ` 420 package main 421 422 import ( 423 "runtime" 424 "time" 425 ) 426 427 func main() { 428 go func() { 429 time.Sleep(time.Millisecond) 430 }() 431 i := 0 432 runtime.SetFinalizer(&i, func(p *int) {}) 433 runtime.GC() 434 runtime.Goexit() 435 } 436 ` 437 438 const goNilSource = ` 439 package main 440 441 func main() { 442 defer func() { 443 recover() 444 }() 445 var f func() 446 go f() 447 select{} 448 } 449 ` 450 451 const mainGoroutineIdSource = ` 452 package main 453 func main() { 454 panic("test") 455 } 456 ` 457 458 const noHelperGoroutinesSource = ` 459 package main 460 import ( 461 "runtime" 462 "time" 463 ) 464 func init() { 465 i := 0 466 runtime.SetFinalizer(&i, func(p *int) {}) 467 time.AfterFunc(time.Hour, func() {}) 468 panic("oops") 469 } 470 func main() { 471 } 472 ` 473 474 const breakpointSource = ` 475 package main 476 import "runtime" 477 func main() { 478 runtime.Breakpoint() 479 } 480 ` 481 482 func TestGoexitInPanic(t *testing.T) { 483 // see issue 8774: this code used to trigger an infinite recursion 484 output := executeTest(t, goexitInPanicSource, nil) 485 want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" 486 if !strings.HasPrefix(output, want) { 487 t.Fatalf("output does not start with %q:\n%s", want, output) 488 } 489 } 490 491 const goexitInPanicSource = ` 492 package main 493 import "runtime" 494 func main() { 495 go func() { 496 defer func() { 497 runtime.Goexit() 498 }() 499 panic("hello") 500 }() 501 runtime.Goexit() 502 } 503 ` 504 505 func TestPanicAfterGoexit(t *testing.T) { 506 // an uncaught panic should still work after goexit 507 output := executeTest(t, panicAfterGoexitSource, nil) 508 want := "panic: hello" 509 if !strings.HasPrefix(output, want) { 510 t.Fatalf("output does not start with %q:\n%s", want, output) 511 } 512 } 513 514 const panicAfterGoexitSource = ` 515 package main 516 import "runtime" 517 func main() { 518 defer func() { 519 panic("hello") 520 }() 521 runtime.Goexit() 522 } 523 ` 524 525 func TestRecoveredPanicAfterGoexit(t *testing.T) { 526 output := executeTest(t, recoveredPanicAfterGoexitSource, nil) 527 want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" 528 if !strings.HasPrefix(output, want) { 529 t.Fatalf("output does not start with %q:\n%s", want, output) 530 } 531 } 532 533 const recoveredPanicAfterGoexitSource = ` 534 package main 535 import "runtime" 536 func main() { 537 defer func() { 538 defer func() { 539 r := recover() 540 if r == nil { 541 panic("bad recover") 542 } 543 }() 544 panic("hello") 545 }() 546 runtime.Goexit() 547 } 548 ` 549 550 func TestRecoverBeforePanicAfterGoexit(t *testing.T) { 551 // 1. defer a function that recovers 552 // 2. defer a function that panics 553 // 3. call goexit 554 // Goexit should run the #2 defer. Its panic 555 // should be caught by the #1 defer, and execution 556 // should resume in the caller. Like the Goexit 557 // never happened! 558 defer func() { 559 r := recover() 560 if r == nil { 561 panic("bad recover") 562 } 563 }() 564 defer func() { 565 panic("hello") 566 }() 567 runtime.Goexit() 568 } 569 570 func TestNetpollDeadlock(t *testing.T) { 571 output := executeTest(t, netpollDeadlockSource, nil) 572 want := "done\n" 573 if !strings.HasSuffix(output, want) { 574 t.Fatalf("output does not start with %q:\n%s", want, output) 575 } 576 } 577 578 const netpollDeadlockSource = ` 579 package main 580 import ( 581 "fmt" 582 "net" 583 ) 584 func init() { 585 fmt.Println("dialing") 586 c, err := net.Dial("tcp", "localhost:14356") 587 if err == nil { 588 c.Close() 589 } else { 590 fmt.Println("error: ", err) 591 } 592 } 593 func main() { 594 fmt.Println("done") 595 } 596 `