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  `