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