github.com/aloncn/graphics-go@v0.0.1/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 "internal/testenv" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 "strings" 17 "sync" 18 "testing" 19 ) 20 21 var toRemove []string 22 23 func TestMain(m *testing.M) { 24 status := m.Run() 25 for _, file := range toRemove { 26 os.RemoveAll(file) 27 } 28 os.Exit(status) 29 } 30 31 func testEnv(cmd *exec.Cmd) *exec.Cmd { 32 if cmd.Env != nil { 33 panic("environment already set") 34 } 35 for _, env := range os.Environ() { 36 // Exclude GODEBUG from the environment to prevent its output 37 // from breaking tests that are trying to parse other command output. 38 if strings.HasPrefix(env, "GODEBUG=") { 39 continue 40 } 41 // Exclude GOTRACEBACK for the same reason. 42 if strings.HasPrefix(env, "GOTRACEBACK=") { 43 continue 44 } 45 cmd.Env = append(cmd.Env, env) 46 } 47 return cmd 48 } 49 50 var testprog struct { 51 sync.Mutex 52 dir string 53 target map[string]buildexe 54 } 55 56 type buildexe struct { 57 exe string 58 err error 59 } 60 61 func runTestProg(t *testing.T, binary, name string) string { 62 testenv.MustHaveGoBuild(t) 63 64 exe, err := buildTestProg(t, binary) 65 if err != nil { 66 t.Fatal(err) 67 } 68 got, _ := testEnv(exec.Command(exe, name)).CombinedOutput() 69 return string(got) 70 } 71 72 func buildTestProg(t *testing.T, binary string) (string, error) { 73 checkStaleRuntime(t) 74 75 testprog.Lock() 76 defer testprog.Unlock() 77 if testprog.dir == "" { 78 dir, err := ioutil.TempDir("", "go-build") 79 if err != nil { 80 t.Fatalf("failed to create temp directory: %v", err) 81 } 82 testprog.dir = dir 83 toRemove = append(toRemove, dir) 84 } 85 86 if testprog.target == nil { 87 testprog.target = make(map[string]buildexe) 88 } 89 target, ok := testprog.target[binary] 90 if ok { 91 return target.exe, target.err 92 } 93 94 exe := filepath.Join(testprog.dir, binary+".exe") 95 cmd := exec.Command("go", "build", "-o", exe) 96 cmd.Dir = "testdata/" + binary 97 out, err := testEnv(cmd).CombinedOutput() 98 if err != nil { 99 exe = "" 100 target.err = fmt.Errorf("building %s: %v\n%s", binary, err, out) 101 testprog.target[binary] = target 102 return "", target.err 103 } 104 target.exe = exe 105 testprog.target[binary] = target 106 return exe, nil 107 } 108 109 var ( 110 staleRuntimeOnce sync.Once // guards init of staleRuntimeErr 111 staleRuntimeErr error 112 ) 113 114 func checkStaleRuntime(t *testing.T) { 115 staleRuntimeOnce.Do(func() { 116 // 'go run' uses the installed copy of runtime.a, which may be out of date. 117 out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput() 118 if err != nil { 119 staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out)) 120 return 121 } 122 if string(out) != "false\n" { 123 staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.") 124 } 125 }) 126 if staleRuntimeErr != nil { 127 t.Fatal(staleRuntimeErr) 128 } 129 } 130 131 func testCrashHandler(t *testing.T, cgo bool) { 132 type crashTest struct { 133 Cgo bool 134 } 135 var output string 136 if cgo { 137 output = runTestProg(t, "testprogcgo", "Crash") 138 } else { 139 output = runTestProg(t, "testprog", "Crash") 140 } 141 want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" 142 if output != want { 143 t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want) 144 } 145 } 146 147 func TestCrashHandler(t *testing.T) { 148 testCrashHandler(t, false) 149 } 150 151 func testDeadlock(t *testing.T, name string) { 152 output := runTestProg(t, "testprog", name) 153 want := "fatal error: all goroutines are asleep - deadlock!\n" 154 if !strings.HasPrefix(output, want) { 155 t.Fatalf("output does not start with %q:\n%s", want, output) 156 } 157 } 158 159 func TestSimpleDeadlock(t *testing.T) { 160 testDeadlock(t, "SimpleDeadlock") 161 } 162 163 func TestInitDeadlock(t *testing.T) { 164 testDeadlock(t, "InitDeadlock") 165 } 166 167 func TestLockedDeadlock(t *testing.T) { 168 testDeadlock(t, "LockedDeadlock") 169 } 170 171 func TestLockedDeadlock2(t *testing.T) { 172 testDeadlock(t, "LockedDeadlock2") 173 } 174 175 func TestGoexitDeadlock(t *testing.T) { 176 output := runTestProg(t, "testprog", "GoexitDeadlock") 177 want := "no goroutines (main called runtime.Goexit) - deadlock!" 178 if !strings.Contains(output, want) { 179 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 180 } 181 } 182 183 func TestStackOverflow(t *testing.T) { 184 output := runTestProg(t, "testprog", "StackOverflow") 185 want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow" 186 if !strings.HasPrefix(output, want) { 187 t.Fatalf("output does not start with %q:\n%s", want, output) 188 } 189 } 190 191 func TestThreadExhaustion(t *testing.T) { 192 output := runTestProg(t, "testprog", "ThreadExhaustion") 193 want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion" 194 if !strings.HasPrefix(output, want) { 195 t.Fatalf("output does not start with %q:\n%s", want, output) 196 } 197 } 198 199 func TestRecursivePanic(t *testing.T) { 200 output := runTestProg(t, "testprog", "RecursivePanic") 201 want := `wrap: bad 202 panic: again 203 204 ` 205 if !strings.HasPrefix(output, want) { 206 t.Fatalf("output does not start with %q:\n%s", want, output) 207 } 208 209 } 210 211 func TestGoexitCrash(t *testing.T) { 212 output := runTestProg(t, "testprog", "GoexitExit") 213 want := "no goroutines (main called runtime.Goexit) - deadlock!" 214 if !strings.Contains(output, want) { 215 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 216 } 217 } 218 219 func TestGoexitDefer(t *testing.T) { 220 c := make(chan struct{}) 221 go func() { 222 defer func() { 223 r := recover() 224 if r != nil { 225 t.Errorf("non-nil recover during Goexit") 226 } 227 c <- struct{}{} 228 }() 229 runtime.Goexit() 230 }() 231 // Note: if the defer fails to run, we will get a deadlock here 232 <-c 233 } 234 235 func TestGoNil(t *testing.T) { 236 output := runTestProg(t, "testprog", "GoNil") 237 want := "go of nil func value" 238 if !strings.Contains(output, want) { 239 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 240 } 241 } 242 243 func TestMainGoroutineID(t *testing.T) { 244 output := runTestProg(t, "testprog", "MainGoroutineID") 245 want := "panic: test\n\ngoroutine 1 [running]:\n" 246 if !strings.HasPrefix(output, want) { 247 t.Fatalf("output does not start with %q:\n%s", want, output) 248 } 249 } 250 251 func TestNoHelperGoroutines(t *testing.T) { 252 output := runTestProg(t, "testprog", "NoHelperGoroutines") 253 matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1) 254 if len(matches) != 1 || matches[0][0] != "goroutine 1 [" { 255 t.Fatalf("want to see only goroutine 1, see:\n%s", output) 256 } 257 } 258 259 func TestBreakpoint(t *testing.T) { 260 output := runTestProg(t, "testprog", "Breakpoint") 261 want := "runtime.Breakpoint()" 262 if !strings.Contains(output, want) { 263 t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) 264 } 265 } 266 267 func TestGoexitInPanic(t *testing.T) { 268 // see issue 8774: this code used to trigger an infinite recursion 269 output := runTestProg(t, "testprog", "GoexitInPanic") 270 want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" 271 if !strings.HasPrefix(output, want) { 272 t.Fatalf("output does not start with %q:\n%s", want, output) 273 } 274 } 275 276 func TestPanicAfterGoexit(t *testing.T) { 277 // an uncaught panic should still work after goexit 278 output := runTestProg(t, "testprog", "PanicAfterGoexit") 279 want := "panic: hello" 280 if !strings.HasPrefix(output, want) { 281 t.Fatalf("output does not start with %q:\n%s", want, output) 282 } 283 } 284 285 func TestRecoveredPanicAfterGoexit(t *testing.T) { 286 output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit") 287 want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" 288 if !strings.HasPrefix(output, want) { 289 t.Fatalf("output does not start with %q:\n%s", want, output) 290 } 291 } 292 293 func TestRecoverBeforePanicAfterGoexit(t *testing.T) { 294 // 1. defer a function that recovers 295 // 2. defer a function that panics 296 // 3. call goexit 297 // Goexit should run the #2 defer. Its panic 298 // should be caught by the #1 defer, and execution 299 // should resume in the caller. Like the Goexit 300 // never happened! 301 defer func() { 302 r := recover() 303 if r == nil { 304 panic("bad recover") 305 } 306 }() 307 defer func() { 308 panic("hello") 309 }() 310 runtime.Goexit() 311 } 312 313 func TestNetpollDeadlock(t *testing.T) { 314 output := runTestProg(t, "testprognet", "NetpollDeadlock") 315 want := "done\n" 316 if !strings.HasSuffix(output, want) { 317 t.Fatalf("output does not start with %q:\n%s", want, output) 318 } 319 } 320 321 func TestPanicTraceback(t *testing.T) { 322 output := runTestProg(t, "testprog", "PanicTraceback") 323 want := "panic: hello" 324 if !strings.HasPrefix(output, want) { 325 t.Fatalf("output does not start with %q:\n%s", want, output) 326 } 327 328 // Check functions in the traceback. 329 fns := []string{"panic", "main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"} 330 for _, fn := range fns { 331 re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`) 332 idx := re.FindStringIndex(output) 333 if idx == nil { 334 t.Fatalf("expected %q function in traceback:\n%s", fn, output) 335 } 336 output = output[idx[1]:] 337 } 338 }