Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 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" |
Russ Cox | 0c2a727 | 2014-05-20 12:10:19 -0400 | [diff] [blame] | 12 | "runtime" |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 13 | "strings" |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 14 | "testing" |
| 15 | "text/template" |
| 16 | ) |
| 17 | |
Dmitriy Vyukov | 4b536a5 | 2013-06-28 18:37:06 +0400 | [diff] [blame] | 18 | // testEnv excludes GODEBUG from the environment |
Albert Strasheim | 4235fa8 | 2013-04-07 11:37:37 -0700 | [diff] [blame] | 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() { |
Dmitriy Vyukov | 4b536a5 | 2013-06-28 18:37:06 +0400 | [diff] [blame] | 26 | if strings.HasPrefix(env, "GODEBUG=") { |
Albert Strasheim | 4235fa8 | 2013-04-07 11:37:37 -0700 | [diff] [blame] | 27 | continue |
| 28 | } |
| 29 | cmd.Env = append(cmd.Env, env) |
| 30 | } |
| 31 | return cmd |
| 32 | } |
| 33 | |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 34 | func executeTest(t *testing.T, templ string, data interface{}) string { |
David Crawshaw | 1648df6 | 2014-07-08 14:47:52 -0400 | [diff] [blame] | 35 | switch runtime.GOOS { |
| 36 | case "android", "nacl": |
| 37 | t.Skipf("skipping on %s", runtime.GOOS) |
Russ Cox | 0c2a727 | 2014-05-20 12:10:19 -0400 | [diff] [blame] | 38 | } |
| 39 | |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 40 | checkStaleRuntime(t) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 41 | |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 42 | st := template.Must(template.New("crashSource").Parse(templ)) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 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 { |
Dmitriy Vyukov | 2791ef0 | 2013-08-12 22:04:10 +0400 | [diff] [blame] | 53 | t.Fatalf("failed to create file: %v", err) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 54 | } |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 55 | err = st.Execute(f, data) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 56 | if err != nil { |
| 57 | f.Close() |
| 58 | t.Fatalf("failed to execute template: %v", err) |
| 59 | } |
Dmitriy Vyukov | 2791ef0 | 2013-08-12 22:04:10 +0400 | [diff] [blame] | 60 | if err := f.Close(); err != nil { |
| 61 | t.Fatalf("failed to close file: %v", err) |
| 62 | } |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 63 | |
Albert Strasheim | 4235fa8 | 2013-04-07 11:37:37 -0700 | [diff] [blame] | 64 | got, _ := testEnv(exec.Command("go", "run", src)).CombinedOutput() |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 65 | return string(got) |
| 66 | } |
| 67 | |
| 68 | func checkStaleRuntime(t *testing.T) { |
| 69 | // 'go run' uses the installed copy of runtime.a, which may be out of date. |
Albert Strasheim | 4235fa8 | 2013-04-07 11:37:37 -0700 | [diff] [blame] | 70 | out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput() |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 71 | if err != nil { |
| 72 | t.Fatalf("failed to execute 'go list': %v\n%v", err, string(out)) |
| 73 | } |
| 74 | if string(out) != "false\n" { |
| 75 | t.Fatalf("Stale runtime.a. Run 'go install runtime'.") |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | func testCrashHandler(t *testing.T, cgo bool) { |
| 80 | type crashTest struct { |
| 81 | Cgo bool |
| 82 | } |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 83 | output := executeTest(t, crashSource, &crashTest{Cgo: cgo}) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 84 | want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 85 | if output != want { |
| 86 | t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 87 | } |
| 88 | } |
| 89 | |
| 90 | func TestCrashHandler(t *testing.T) { |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 91 | testCrashHandler(t, false) |
| 92 | } |
| 93 | |
| 94 | func testDeadlock(t *testing.T, source string) { |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 95 | output := executeTest(t, source, nil) |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 96 | want := "fatal error: all goroutines are asleep - deadlock!\n" |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 97 | if !strings.HasPrefix(output, want) { |
| 98 | t.Fatalf("output does not start with %q:\n%s", want, output) |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 99 | } |
| 100 | } |
| 101 | |
| 102 | func TestSimpleDeadlock(t *testing.T) { |
| 103 | testDeadlock(t, simpleDeadlockSource) |
| 104 | } |
| 105 | |
| 106 | func TestInitDeadlock(t *testing.T) { |
| 107 | testDeadlock(t, initDeadlockSource) |
| 108 | } |
| 109 | |
| 110 | func TestLockedDeadlock(t *testing.T) { |
| 111 | testDeadlock(t, lockedDeadlockSource) |
| 112 | } |
| 113 | |
| 114 | func TestLockedDeadlock2(t *testing.T) { |
| 115 | testDeadlock(t, lockedDeadlockSource2) |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 116 | } |
| 117 | |
Dmitriy Vyukov | 2fe840f | 2013-03-05 09:40:17 +0200 | [diff] [blame] | 118 | func TestGoexitDeadlock(t *testing.T) { |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 119 | output := executeTest(t, goexitDeadlockSource, nil) |
Russ Cox | ade6bc6 | 2014-04-16 13:12:18 -0400 | [diff] [blame] | 120 | want := "no goroutines (main called runtime.Goexit) - deadlock!" |
| 121 | if !strings.Contains(output, want) { |
| 122 | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 123 | } |
| 124 | } |
| 125 | |
| 126 | func TestStackOverflow(t *testing.T) { |
| 127 | output := executeTest(t, stackOverflowSource, nil) |
| 128 | want := "runtime: goroutine stack exceeds 4194304-byte limit\nfatal error: stack overflow" |
| 129 | if !strings.HasPrefix(output, want) { |
| 130 | t.Fatalf("output does not start with %q:\n%s", want, output) |
Dmitriy Vyukov | 2fe840f | 2013-03-05 09:40:17 +0200 | [diff] [blame] | 131 | } |
| 132 | } |
| 133 | |
Russ Cox | 665feee | 2013-08-16 22:25:26 -0400 | [diff] [blame] | 134 | func TestThreadExhaustion(t *testing.T) { |
| 135 | output := executeTest(t, threadExhaustionSource, nil) |
| 136 | want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion" |
| 137 | if !strings.HasPrefix(output, want) { |
| 138 | t.Fatalf("output does not start with %q:\n%s", want, output) |
| 139 | } |
| 140 | } |
| 141 | |
Dmitriy Vyukov | f946a7c | 2014-03-07 20:50:30 +0400 | [diff] [blame] | 142 | func TestRecursivePanic(t *testing.T) { |
| 143 | output := executeTest(t, recursivePanicSource, nil) |
| 144 | want := `wrap: bad |
| 145 | panic: again |
| 146 | |
| 147 | ` |
| 148 | if !strings.HasPrefix(output, want) { |
| 149 | t.Fatalf("output does not start with %q:\n%s", want, output) |
| 150 | } |
| 151 | |
| 152 | } |
| 153 | |
Russ Cox | ade6bc6 | 2014-04-16 13:12:18 -0400 | [diff] [blame] | 154 | func TestGoexitCrash(t *testing.T) { |
Dmitriy Vyukov | 55e0f36 | 2014-04-15 19:48:17 +0400 | [diff] [blame] | 155 | output := executeTest(t, goexitExitSource, nil) |
Russ Cox | ade6bc6 | 2014-04-16 13:12:18 -0400 | [diff] [blame] | 156 | want := "no goroutines (main called runtime.Goexit) - deadlock!" |
| 157 | if !strings.Contains(output, want) { |
| 158 | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) |
Dmitriy Vyukov | 55e0f36 | 2014-04-15 19:48:17 +0400 | [diff] [blame] | 159 | } |
Dmitriy Vyukov | 55e0f36 | 2014-04-15 19:48:17 +0400 | [diff] [blame] | 160 | } |
| 161 | |
Dmitriy Vyukov | b5caa02 | 2014-05-28 00:00:01 -0400 | [diff] [blame] | 162 | func TestGoNil(t *testing.T) { |
| 163 | output := executeTest(t, goNilSource, nil) |
| 164 | want := "go of nil func value" |
| 165 | if !strings.Contains(output, want) { |
| 166 | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) |
| 167 | } |
| 168 | } |
| 169 | |
Dmitriy Vyukov | aa76377 | 2014-07-16 12:19:33 +0400 | [diff] [blame] | 170 | func TestMainGoroutineId(t *testing.T) { |
| 171 | output := executeTest(t, mainGoroutineIdSource, nil) |
| 172 | want := "panic: test\n\ngoroutine 1 [running]:\n" |
| 173 | if !strings.HasPrefix(output, want) { |
| 174 | t.Fatalf("output does not start with %q:\n%s", want, output) |
| 175 | } |
| 176 | } |
| 177 | |
Russ Cox | 1d550b8 | 2014-09-11 12:08:30 -0400 | [diff] [blame^] | 178 | func TestBreakpoint(t *testing.T) { |
| 179 | output := executeTest(t, breakpointSource, nil) |
| 180 | want := "runtime.Breakpoint()" |
| 181 | if !strings.Contains(output, want) { |
| 182 | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) |
| 183 | } |
| 184 | } |
| 185 | |
Alex Brainman | afe0e97 | 2012-05-30 15:10:54 +1000 | [diff] [blame] | 186 | const crashSource = ` |
| 187 | package main |
| 188 | |
| 189 | import ( |
| 190 | "fmt" |
| 191 | "runtime" |
| 192 | ) |
| 193 | |
| 194 | {{if .Cgo}} |
| 195 | import "C" |
| 196 | {{end}} |
| 197 | |
| 198 | func test(name string) { |
| 199 | defer func() { |
| 200 | if x := recover(); x != nil { |
| 201 | fmt.Printf(" recovered") |
| 202 | } |
| 203 | fmt.Printf(" done\n") |
| 204 | }() |
| 205 | fmt.Printf("%s:", name) |
| 206 | var s *string |
| 207 | _ = *s |
| 208 | fmt.Print("SHOULD NOT BE HERE") |
| 209 | } |
| 210 | |
| 211 | func testInNewThread(name string) { |
| 212 | c := make(chan bool) |
| 213 | go func() { |
| 214 | runtime.LockOSThread() |
| 215 | test(name) |
| 216 | c <- true |
| 217 | }() |
| 218 | <-c |
| 219 | } |
| 220 | |
| 221 | func main() { |
| 222 | runtime.LockOSThread() |
| 223 | test("main") |
| 224 | testInNewThread("new-thread") |
| 225 | testInNewThread("second-new-thread") |
| 226 | test("main-again") |
| 227 | } |
| 228 | ` |
Dmitriy Vyukov | 06a488f | 2013-02-20 12:15:02 +0400 | [diff] [blame] | 229 | |
| 230 | const simpleDeadlockSource = ` |
| 231 | package main |
| 232 | func main() { |
| 233 | select {} |
| 234 | } |
| 235 | ` |
| 236 | |
| 237 | const initDeadlockSource = ` |
| 238 | package main |
| 239 | func init() { |
| 240 | select {} |
| 241 | } |
| 242 | func main() { |
| 243 | } |
| 244 | ` |
| 245 | |
| 246 | const lockedDeadlockSource = ` |
| 247 | package main |
| 248 | import "runtime" |
| 249 | func main() { |
| 250 | runtime.LockOSThread() |
| 251 | select {} |
| 252 | } |
| 253 | ` |
| 254 | |
| 255 | const lockedDeadlockSource2 = ` |
| 256 | package main |
| 257 | import ( |
| 258 | "runtime" |
| 259 | "time" |
| 260 | ) |
| 261 | func main() { |
| 262 | go func() { |
| 263 | runtime.LockOSThread() |
| 264 | select {} |
| 265 | }() |
| 266 | time.Sleep(time.Millisecond) |
| 267 | select {} |
| 268 | } |
| 269 | ` |
Dmitriy Vyukov | 2fe840f | 2013-03-05 09:40:17 +0200 | [diff] [blame] | 270 | |
| 271 | const goexitDeadlockSource = ` |
| 272 | package main |
| 273 | import ( |
| 274 | "runtime" |
| 275 | ) |
| 276 | |
| 277 | func F() { |
| 278 | for i := 0; i < 10; i++ { |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | func main() { |
| 283 | go F() |
| 284 | go F() |
| 285 | runtime.Goexit() |
| 286 | } |
| 287 | ` |
Russ Cox | 757e0de | 2013-08-15 22:34:06 -0400 | [diff] [blame] | 288 | |
| 289 | const stackOverflowSource = ` |
| 290 | package main |
| 291 | |
| 292 | import "runtime/debug" |
| 293 | |
| 294 | func main() { |
| 295 | debug.SetMaxStack(4<<20) |
| 296 | f(make([]byte, 10)) |
| 297 | } |
| 298 | |
| 299 | func f(x []byte) byte { |
| 300 | var buf [64<<10]byte |
| 301 | return x[0] + f(buf[:]) |
| 302 | } |
| 303 | ` |
Russ Cox | 665feee | 2013-08-16 22:25:26 -0400 | [diff] [blame] | 304 | |
| 305 | const threadExhaustionSource = ` |
| 306 | package main |
| 307 | |
| 308 | import ( |
| 309 | "runtime" |
| 310 | "runtime/debug" |
| 311 | ) |
| 312 | |
| 313 | func main() { |
| 314 | debug.SetMaxThreads(10) |
| 315 | c := make(chan int) |
| 316 | for i := 0; i < 100; i++ { |
| 317 | go func() { |
| 318 | runtime.LockOSThread() |
| 319 | c <- 0 |
| 320 | select{} |
| 321 | }() |
| 322 | <-c |
| 323 | } |
| 324 | } |
| 325 | ` |
Dmitriy Vyukov | f946a7c | 2014-03-07 20:50:30 +0400 | [diff] [blame] | 326 | |
| 327 | const recursivePanicSource = ` |
| 328 | package main |
| 329 | |
| 330 | import ( |
| 331 | "fmt" |
| 332 | ) |
| 333 | |
| 334 | func main() { |
| 335 | func() { |
| 336 | defer func() { |
| 337 | fmt.Println(recover()) |
| 338 | }() |
| 339 | var x [8192]byte |
| 340 | func(x [8192]byte) { |
| 341 | defer func() { |
| 342 | if err := recover(); err != nil { |
| 343 | panic("wrap: " + err.(string)) |
| 344 | } |
| 345 | }() |
| 346 | panic("bad") |
| 347 | }(x) |
| 348 | }() |
| 349 | panic("again") |
| 350 | } |
| 351 | ` |
Dmitriy Vyukov | 55e0f36 | 2014-04-15 19:48:17 +0400 | [diff] [blame] | 352 | |
| 353 | const goexitExitSource = ` |
| 354 | package main |
| 355 | |
| 356 | import ( |
| 357 | "runtime" |
| 358 | "time" |
| 359 | ) |
| 360 | |
| 361 | func main() { |
| 362 | go func() { |
| 363 | time.Sleep(time.Millisecond) |
| 364 | }() |
| 365 | i := 0 |
| 366 | runtime.SetFinalizer(&i, func(p *int) {}) |
| 367 | runtime.GC() |
| 368 | runtime.Goexit() |
| 369 | } |
| 370 | ` |
Dmitriy Vyukov | b5caa02 | 2014-05-28 00:00:01 -0400 | [diff] [blame] | 371 | |
| 372 | const goNilSource = ` |
| 373 | package main |
| 374 | |
| 375 | func main() { |
| 376 | defer func() { |
| 377 | recover() |
| 378 | }() |
| 379 | var f func() |
| 380 | go f() |
| 381 | select{} |
| 382 | } |
| 383 | ` |
Dmitriy Vyukov | aa76377 | 2014-07-16 12:19:33 +0400 | [diff] [blame] | 384 | |
| 385 | const mainGoroutineIdSource = ` |
| 386 | package main |
| 387 | func main() { |
| 388 | panic("test") |
| 389 | } |
| 390 | ` |
Russ Cox | 1d550b8 | 2014-09-11 12:08:30 -0400 | [diff] [blame^] | 391 | |
| 392 | const breakpointSource = ` |
| 393 | package main |
| 394 | import "runtime" |
| 395 | func main() { |
| 396 | runtime.Breakpoint() |
| 397 | } |
| 398 | ` |