github.com/4ad/go@v0.0.0-20161219182952-69a12818b605/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  	"bytes"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  )
    23  
    24  var toRemove []string
    25  
    26  func TestMain(m *testing.M) {
    27  	status := m.Run()
    28  	for _, file := range toRemove {
    29  		os.RemoveAll(file)
    30  	}
    31  	os.Exit(status)
    32  }
    33  
    34  func testEnv(cmd *exec.Cmd) *exec.Cmd {
    35  	if cmd.Env != nil {
    36  		panic("environment already set")
    37  	}
    38  	for _, env := range os.Environ() {
    39  		// Exclude GODEBUG from the environment to prevent its output
    40  		// from breaking tests that are trying to parse other command output.
    41  		if strings.HasPrefix(env, "GODEBUG=") {
    42  			continue
    43  		}
    44  		// Exclude GOTRACEBACK for the same reason.
    45  		if strings.HasPrefix(env, "GOTRACEBACK=") {
    46  			continue
    47  		}
    48  		cmd.Env = append(cmd.Env, env)
    49  	}
    50  	return cmd
    51  }
    52  
    53  var testprog struct {
    54  	sync.Mutex
    55  	dir    string
    56  	target map[string]buildexe
    57  }
    58  
    59  type buildexe struct {
    60  	exe string
    61  	err error
    62  }
    63  
    64  func runTestProg(t *testing.T, binary, name string) string {
    65  	testenv.MustHaveGoBuild(t)
    66  
    67  	exe, err := buildTestProg(t, binary)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	cmd := testEnv(exec.Command(exe, name))
    73  	var b bytes.Buffer
    74  	cmd.Stdout = &b
    75  	cmd.Stderr = &b
    76  	if err := cmd.Start(); err != nil {
    77  		t.Fatalf("starting %s %s: %v", binary, name, err)
    78  	}
    79  
    80  	// If the process doesn't complete within 1 minute,
    81  	// assume it is hanging and kill it to get a stack trace.
    82  	p := cmd.Process
    83  	done := make(chan bool)
    84  	go func() {
    85  		scale := 1
    86  		// This GOARCH/GOOS test is copied from cmd/dist/test.go.
    87  		// TODO(iant): Have cmd/dist update the environment variable.
    88  		if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
    89  			scale = 2
    90  		}
    91  		if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
    92  			if sc, err := strconv.Atoi(s); err == nil {
    93  				scale = sc
    94  			}
    95  		}
    96  
    97  		select {
    98  		case <-done:
    99  		case <-time.After(time.Duration(scale) * time.Minute):
   100  			p.Signal(sigquit)
   101  		}
   102  	}()
   103  
   104  	if err := cmd.Wait(); err != nil {
   105  		t.Logf("%s %s exit status: %v", binary, name, err)
   106  	}
   107  	close(done)
   108  
   109  	return b.String()
   110  }
   111  
   112  func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
   113  	checkStaleRuntime(t)
   114  
   115  	testprog.Lock()
   116  	defer testprog.Unlock()
   117  	if testprog.dir == "" {
   118  		dir, err := ioutil.TempDir("", "go-build")
   119  		if err != nil {
   120  			t.Fatalf("failed to create temp directory: %v", err)
   121  		}
   122  		testprog.dir = dir
   123  		toRemove = append(toRemove, dir)
   124  	}
   125  
   126  	if testprog.target == nil {
   127  		testprog.target = make(map[string]buildexe)
   128  	}
   129  	name := binary
   130  	if len(flags) > 0 {
   131  		name += "_" + strings.Join(flags, "_")
   132  	}
   133  	target, ok := testprog.target[name]
   134  	if ok {
   135  		return target.exe, target.err
   136  	}
   137  
   138  	exe := filepath.Join(testprog.dir, name+".exe")
   139  	cmd := exec.Command("go", append([]string{"build", "-o", exe}, flags...)...)
   140  	cmd.Dir = "testdata/" + binary
   141  	out, err := testEnv(cmd).CombinedOutput()
   142  	if err != nil {
   143  		exe = ""
   144  		target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
   145  		testprog.target[name] = target
   146  		return "", target.err
   147  	}
   148  	target.exe = exe
   149  	testprog.target[name] = target
   150  	return exe, nil
   151  }
   152  
   153  var (
   154  	staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
   155  	staleRuntimeErr  error
   156  )
   157  
   158  func checkStaleRuntime(t *testing.T) {
   159  	staleRuntimeOnce.Do(func() {
   160  		// 'go run' uses the installed copy of runtime.a, which may be out of date.
   161  		out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput()
   162  		if err != nil {
   163  			staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
   164  			return
   165  		}
   166  		if string(out) != "false\n" {
   167  			staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
   168  		}
   169  	})
   170  	if staleRuntimeErr != nil {
   171  		t.Fatal(staleRuntimeErr)
   172  	}
   173  }
   174  
   175  func testCrashHandler(t *testing.T, cgo bool) {
   176  	type crashTest struct {
   177  		Cgo bool
   178  	}
   179  	var output string
   180  	if cgo {
   181  		output = runTestProg(t, "testprogcgo", "Crash")
   182  	} else {
   183  		output = runTestProg(t, "testprog", "Crash")
   184  	}
   185  	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
   186  	if output != want {
   187  		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
   188  	}
   189  }
   190  
   191  func TestCrashHandler(t *testing.T) {
   192  	testCrashHandler(t, false)
   193  }
   194  
   195  func testDeadlock(t *testing.T, name string) {
   196  	output := runTestProg(t, "testprog", name)
   197  	want := "fatal error: all goroutines are asleep - deadlock!\n"
   198  	if !strings.HasPrefix(output, want) {
   199  		t.Fatalf("output does not start with %q:\n%s", want, output)
   200  	}
   201  }
   202  
   203  func TestSimpleDeadlock(t *testing.T) {
   204  	testDeadlock(t, "SimpleDeadlock")
   205  }
   206  
   207  func TestInitDeadlock(t *testing.T) {
   208  	testDeadlock(t, "InitDeadlock")
   209  }
   210  
   211  func TestLockedDeadlock(t *testing.T) {
   212  	testDeadlock(t, "LockedDeadlock")
   213  }
   214  
   215  func TestLockedDeadlock2(t *testing.T) {
   216  	testDeadlock(t, "LockedDeadlock2")
   217  }
   218  
   219  func TestGoexitDeadlock(t *testing.T) {
   220  	output := runTestProg(t, "testprog", "GoexitDeadlock")
   221  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   222  	if !strings.Contains(output, want) {
   223  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   224  	}
   225  }
   226  
   227  func TestStackOverflow(t *testing.T) {
   228  	output := runTestProg(t, "testprog", "StackOverflow")
   229  	want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
   230  	if !strings.HasPrefix(output, want) {
   231  		t.Fatalf("output does not start with %q:\n%s", want, output)
   232  	}
   233  }
   234  
   235  func TestThreadExhaustion(t *testing.T) {
   236  	output := runTestProg(t, "testprog", "ThreadExhaustion")
   237  	want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
   238  	if !strings.HasPrefix(output, want) {
   239  		t.Fatalf("output does not start with %q:\n%s", want, output)
   240  	}
   241  }
   242  
   243  func TestRecursivePanic(t *testing.T) {
   244  	output := runTestProg(t, "testprog", "RecursivePanic")
   245  	want := `wrap: bad
   246  panic: again
   247  
   248  `
   249  	if !strings.HasPrefix(output, want) {
   250  		t.Fatalf("output does not start with %q:\n%s", want, output)
   251  	}
   252  
   253  }
   254  
   255  func TestGoexitCrash(t *testing.T) {
   256  	output := runTestProg(t, "testprog", "GoexitExit")
   257  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   258  	if !strings.Contains(output, want) {
   259  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   260  	}
   261  }
   262  
   263  func TestGoexitDefer(t *testing.T) {
   264  	c := make(chan struct{})
   265  	go func() {
   266  		defer func() {
   267  			r := recover()
   268  			if r != nil {
   269  				t.Errorf("non-nil recover during Goexit")
   270  			}
   271  			c <- struct{}{}
   272  		}()
   273  		runtime.Goexit()
   274  	}()
   275  	// Note: if the defer fails to run, we will get a deadlock here
   276  	<-c
   277  }
   278  
   279  func TestGoNil(t *testing.T) {
   280  	output := runTestProg(t, "testprog", "GoNil")
   281  	want := "go of nil func value"
   282  	if !strings.Contains(output, want) {
   283  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   284  	}
   285  }
   286  
   287  func TestMainGoroutineID(t *testing.T) {
   288  	output := runTestProg(t, "testprog", "MainGoroutineID")
   289  	want := "panic: test\n\ngoroutine 1 [running]:\n"
   290  	if !strings.HasPrefix(output, want) {
   291  		t.Fatalf("output does not start with %q:\n%s", want, output)
   292  	}
   293  }
   294  
   295  func TestNoHelperGoroutines(t *testing.T) {
   296  	output := runTestProg(t, "testprog", "NoHelperGoroutines")
   297  	matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
   298  	if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
   299  		t.Fatalf("want to see only goroutine 1, see:\n%s", output)
   300  	}
   301  }
   302  
   303  func TestBreakpoint(t *testing.T) {
   304  	output := runTestProg(t, "testprog", "Breakpoint")
   305  	want := "runtime.Breakpoint()"
   306  	if !strings.Contains(output, want) {
   307  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   308  	}
   309  }
   310  
   311  func TestGoexitInPanic(t *testing.T) {
   312  	// see issue 8774: this code used to trigger an infinite recursion
   313  	output := runTestProg(t, "testprog", "GoexitInPanic")
   314  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   315  	if !strings.HasPrefix(output, want) {
   316  		t.Fatalf("output does not start with %q:\n%s", want, output)
   317  	}
   318  }
   319  
   320  // Issue 14965: Runtime panics should be of type runtime.Error
   321  func TestRuntimePanicWithRuntimeError(t *testing.T) {
   322  	testCases := [...]func(){
   323  		0: func() {
   324  			var m map[uint64]bool
   325  			m[1234] = true
   326  		},
   327  		1: func() {
   328  			ch := make(chan struct{})
   329  			close(ch)
   330  			close(ch)
   331  		},
   332  		2: func() {
   333  			var ch = make(chan struct{})
   334  			close(ch)
   335  			ch <- struct{}{}
   336  		},
   337  		3: func() {
   338  			var s = make([]int, 2)
   339  			_ = s[2]
   340  		},
   341  		4: func() {
   342  			n := -1
   343  			_ = make(chan bool, n)
   344  		},
   345  		5: func() {
   346  			close((chan bool)(nil))
   347  		},
   348  	}
   349  
   350  	for i, fn := range testCases {
   351  		got := panicValue(fn)
   352  		if _, ok := got.(runtime.Error); !ok {
   353  			t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
   354  		}
   355  	}
   356  }
   357  
   358  func panicValue(fn func()) (recovered interface{}) {
   359  	defer func() {
   360  		recovered = recover()
   361  	}()
   362  	fn()
   363  	return
   364  }
   365  
   366  func TestPanicAfterGoexit(t *testing.T) {
   367  	// an uncaught panic should still work after goexit
   368  	output := runTestProg(t, "testprog", "PanicAfterGoexit")
   369  	want := "panic: hello"
   370  	if !strings.HasPrefix(output, want) {
   371  		t.Fatalf("output does not start with %q:\n%s", want, output)
   372  	}
   373  }
   374  
   375  func TestRecoveredPanicAfterGoexit(t *testing.T) {
   376  	output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
   377  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   378  	if !strings.HasPrefix(output, want) {
   379  		t.Fatalf("output does not start with %q:\n%s", want, output)
   380  	}
   381  }
   382  
   383  func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
   384  	// 1. defer a function that recovers
   385  	// 2. defer a function that panics
   386  	// 3. call goexit
   387  	// Goexit should run the #2 defer. Its panic
   388  	// should be caught by the #1 defer, and execution
   389  	// should resume in the caller. Like the Goexit
   390  	// never happened!
   391  	defer func() {
   392  		r := recover()
   393  		if r == nil {
   394  			panic("bad recover")
   395  		}
   396  	}()
   397  	defer func() {
   398  		panic("hello")
   399  	}()
   400  	runtime.Goexit()
   401  }
   402  
   403  func TestNetpollDeadlock(t *testing.T) {
   404  	output := runTestProg(t, "testprognet", "NetpollDeadlock")
   405  	want := "done\n"
   406  	if !strings.HasSuffix(output, want) {
   407  		t.Fatalf("output does not start with %q:\n%s", want, output)
   408  	}
   409  }
   410  
   411  func TestPanicTraceback(t *testing.T) {
   412  	output := runTestProg(t, "testprog", "PanicTraceback")
   413  	want := "panic: hello"
   414  	if !strings.HasPrefix(output, want) {
   415  		t.Fatalf("output does not start with %q:\n%s", want, output)
   416  	}
   417  
   418  	// Check functions in the traceback.
   419  	fns := []string{"panic", "main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
   420  	for _, fn := range fns {
   421  		re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
   422  		idx := re.FindStringIndex(output)
   423  		if idx == nil {
   424  			t.Fatalf("expected %q function in traceback:\n%s", fn, output)
   425  		}
   426  		output = output[idx[1]:]
   427  	}
   428  }
   429  
   430  func testPanicDeadlock(t *testing.T, name string, want string) {
   431  	// test issue 14432
   432  	output := runTestProg(t, "testprog", name)
   433  	if !strings.HasPrefix(output, want) {
   434  		t.Fatalf("output does not start with %q:\n%s", want, output)
   435  	}
   436  }
   437  
   438  func TestPanicDeadlockGosched(t *testing.T) {
   439  	testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
   440  }
   441  
   442  func TestPanicDeadlockSyscall(t *testing.T) {
   443  	testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
   444  }
   445  
   446  func TestMemPprof(t *testing.T) {
   447  	testenv.MustHaveGoRun(t)
   448  
   449  	exe, err := buildTestProg(t, "testprog")
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  
   454  	got, err := testEnv(exec.Command(exe, "MemProf")).CombinedOutput()
   455  	if err != nil {
   456  		t.Fatal(err)
   457  	}
   458  	fn := strings.TrimSpace(string(got))
   459  	defer os.Remove(fn)
   460  
   461  	cmd := testEnv(exec.Command("go", "tool", "pprof", "-alloc_space", "-top", exe, fn))
   462  
   463  	found := false
   464  	for i, e := range cmd.Env {
   465  		if strings.HasPrefix(e, "PPROF_TMPDIR=") {
   466  			cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
   467  			found = true
   468  			break
   469  		}
   470  	}
   471  	if !found {
   472  		cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
   473  	}
   474  
   475  	top, err := cmd.CombinedOutput()
   476  	t.Logf("%s", top)
   477  	if err != nil {
   478  		t.Fatal(err)
   479  	}
   480  
   481  	if !bytes.Contains(top, []byte("MemProf")) {
   482  		t.Error("missing MemProf in pprof output")
   483  	}
   484  }