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