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