github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/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  		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(testenv.GoToolPath(t), "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  			t.Logf("go list -f {{.Stale}} runtime:\n%s", out)
   168  			out, err := testEnv(exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.StaleReason}}", "runtime")).CombinedOutput()
   169  			if err != nil {
   170  				t.Logf("go list -f {{.StaleReason}} failed: %v", err)
   171  			}
   172  			t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out)
   173  			staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
   174  		}
   175  	})
   176  	if staleRuntimeErr != nil {
   177  		t.Fatal(staleRuntimeErr)
   178  	}
   179  }
   180  
   181  func testCrashHandler(t *testing.T, cgo bool) {
   182  	type crashTest struct {
   183  		Cgo bool
   184  	}
   185  	var output string
   186  	if cgo {
   187  		output = runTestProg(t, "testprogcgo", "Crash")
   188  	} else {
   189  		output = runTestProg(t, "testprog", "Crash")
   190  	}
   191  	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
   192  	if output != want {
   193  		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
   194  	}
   195  }
   196  
   197  func TestCrashHandler(t *testing.T) {
   198  	testCrashHandler(t, false)
   199  }
   200  
   201  func testDeadlock(t *testing.T, name string) {
   202  	output := runTestProg(t, "testprog", name)
   203  	want := "fatal error: all goroutines are asleep - deadlock!\n"
   204  	if !strings.HasPrefix(output, want) {
   205  		t.Fatalf("output does not start with %q:\n%s", want, output)
   206  	}
   207  }
   208  
   209  func TestSimpleDeadlock(t *testing.T) {
   210  	testDeadlock(t, "SimpleDeadlock")
   211  }
   212  
   213  func TestInitDeadlock(t *testing.T) {
   214  	testDeadlock(t, "InitDeadlock")
   215  }
   216  
   217  func TestLockedDeadlock(t *testing.T) {
   218  	testDeadlock(t, "LockedDeadlock")
   219  }
   220  
   221  func TestLockedDeadlock2(t *testing.T) {
   222  	testDeadlock(t, "LockedDeadlock2")
   223  }
   224  
   225  func TestGoexitDeadlock(t *testing.T) {
   226  	output := runTestProg(t, "testprog", "GoexitDeadlock")
   227  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   228  	if !strings.Contains(output, want) {
   229  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   230  	}
   231  }
   232  
   233  func TestStackOverflow(t *testing.T) {
   234  	output := runTestProg(t, "testprog", "StackOverflow")
   235  	want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
   236  	if !strings.HasPrefix(output, want) {
   237  		t.Fatalf("output does not start with %q:\n%s", want, output)
   238  	}
   239  }
   240  
   241  func TestThreadExhaustion(t *testing.T) {
   242  	output := runTestProg(t, "testprog", "ThreadExhaustion")
   243  	want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
   244  	if !strings.HasPrefix(output, want) {
   245  		t.Fatalf("output does not start with %q:\n%s", want, output)
   246  	}
   247  }
   248  
   249  func TestRecursivePanic(t *testing.T) {
   250  	output := runTestProg(t, "testprog", "RecursivePanic")
   251  	want := `wrap: bad
   252  panic: again
   253  
   254  `
   255  	if !strings.HasPrefix(output, want) {
   256  		t.Fatalf("output does not start with %q:\n%s", want, output)
   257  	}
   258  
   259  }
   260  
   261  func TestGoexitCrash(t *testing.T) {
   262  	output := runTestProg(t, "testprog", "GoexitExit")
   263  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   264  	if !strings.Contains(output, want) {
   265  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   266  	}
   267  }
   268  
   269  func TestGoexitDefer(t *testing.T) {
   270  	c := make(chan struct{})
   271  	go func() {
   272  		defer func() {
   273  			r := recover()
   274  			if r != nil {
   275  				t.Errorf("non-nil recover during Goexit")
   276  			}
   277  			c <- struct{}{}
   278  		}()
   279  		runtime.Goexit()
   280  	}()
   281  	// Note: if the defer fails to run, we will get a deadlock here
   282  	<-c
   283  }
   284  
   285  func TestGoNil(t *testing.T) {
   286  	output := runTestProg(t, "testprog", "GoNil")
   287  	want := "go of nil func value"
   288  	if !strings.Contains(output, want) {
   289  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   290  	}
   291  }
   292  
   293  func TestMainGoroutineID(t *testing.T) {
   294  	output := runTestProg(t, "testprog", "MainGoroutineID")
   295  	want := "panic: test\n\ngoroutine 1 [running]:\n"
   296  	if !strings.HasPrefix(output, want) {
   297  		t.Fatalf("output does not start with %q:\n%s", want, output)
   298  	}
   299  }
   300  
   301  func TestNoHelperGoroutines(t *testing.T) {
   302  	output := runTestProg(t, "testprog", "NoHelperGoroutines")
   303  	matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
   304  	if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
   305  		t.Fatalf("want to see only goroutine 1, see:\n%s", output)
   306  	}
   307  }
   308  
   309  func TestBreakpoint(t *testing.T) {
   310  	output := runTestProg(t, "testprog", "Breakpoint")
   311  	// If runtime.Breakpoint() is inlined, then the stack trace prints
   312  	// "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()".
   313  	want := "runtime.Breakpoint("
   314  	if !strings.Contains(output, want) {
   315  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   316  	}
   317  }
   318  
   319  func TestGoexitInPanic(t *testing.T) {
   320  	// see issue 8774: this code used to trigger an infinite recursion
   321  	output := runTestProg(t, "testprog", "GoexitInPanic")
   322  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   323  	if !strings.HasPrefix(output, want) {
   324  		t.Fatalf("output does not start with %q:\n%s", want, output)
   325  	}
   326  }
   327  
   328  // Issue 14965: Runtime panics should be of type runtime.Error
   329  func TestRuntimePanicWithRuntimeError(t *testing.T) {
   330  	testCases := [...]func(){
   331  		0: func() {
   332  			var m map[uint64]bool
   333  			m[1234] = true
   334  		},
   335  		1: func() {
   336  			ch := make(chan struct{})
   337  			close(ch)
   338  			close(ch)
   339  		},
   340  		2: func() {
   341  			var ch = make(chan struct{})
   342  			close(ch)
   343  			ch <- struct{}{}
   344  		},
   345  		3: func() {
   346  			var s = make([]int, 2)
   347  			_ = s[2]
   348  		},
   349  		4: func() {
   350  			n := -1
   351  			_ = make(chan bool, n)
   352  		},
   353  		5: func() {
   354  			close((chan bool)(nil))
   355  		},
   356  	}
   357  
   358  	for i, fn := range testCases {
   359  		got := panicValue(fn)
   360  		if _, ok := got.(runtime.Error); !ok {
   361  			t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
   362  		}
   363  	}
   364  }
   365  
   366  func panicValue(fn func()) (recovered interface{}) {
   367  	defer func() {
   368  		recovered = recover()
   369  	}()
   370  	fn()
   371  	return
   372  }
   373  
   374  func TestPanicAfterGoexit(t *testing.T) {
   375  	// an uncaught panic should still work after goexit
   376  	output := runTestProg(t, "testprog", "PanicAfterGoexit")
   377  	want := "panic: hello"
   378  	if !strings.HasPrefix(output, want) {
   379  		t.Fatalf("output does not start with %q:\n%s", want, output)
   380  	}
   381  }
   382  
   383  func TestRecoveredPanicAfterGoexit(t *testing.T) {
   384  	output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
   385  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   386  	if !strings.HasPrefix(output, want) {
   387  		t.Fatalf("output does not start with %q:\n%s", want, output)
   388  	}
   389  }
   390  
   391  func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
   392  	// 1. defer a function that recovers
   393  	// 2. defer a function that panics
   394  	// 3. call goexit
   395  	// Goexit should run the #2 defer. Its panic
   396  	// should be caught by the #1 defer, and execution
   397  	// should resume in the caller. Like the Goexit
   398  	// never happened!
   399  	defer func() {
   400  		r := recover()
   401  		if r == nil {
   402  			panic("bad recover")
   403  		}
   404  	}()
   405  	defer func() {
   406  		panic("hello")
   407  	}()
   408  	runtime.Goexit()
   409  }
   410  
   411  func TestNetpollDeadlock(t *testing.T) {
   412  	t.Parallel()
   413  	output := runTestProg(t, "testprognet", "NetpollDeadlock")
   414  	want := "done\n"
   415  	if !strings.HasSuffix(output, want) {
   416  		t.Fatalf("output does not start with %q:\n%s", want, output)
   417  	}
   418  }
   419  
   420  func TestPanicTraceback(t *testing.T) {
   421  	t.Parallel()
   422  	output := runTestProg(t, "testprog", "PanicTraceback")
   423  	want := "panic: hello"
   424  	if !strings.HasPrefix(output, want) {
   425  		t.Fatalf("output does not start with %q:\n%s", want, output)
   426  	}
   427  
   428  	// Check functions in the traceback.
   429  	fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
   430  	for _, fn := range fns {
   431  		re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
   432  		idx := re.FindStringIndex(output)
   433  		if idx == nil {
   434  			t.Fatalf("expected %q function in traceback:\n%s", fn, output)
   435  		}
   436  		output = output[idx[1]:]
   437  	}
   438  }
   439  
   440  func testPanicDeadlock(t *testing.T, name string, want string) {
   441  	// test issue 14432
   442  	output := runTestProg(t, "testprog", name)
   443  	if !strings.HasPrefix(output, want) {
   444  		t.Fatalf("output does not start with %q:\n%s", want, output)
   445  	}
   446  }
   447  
   448  func TestPanicDeadlockGosched(t *testing.T) {
   449  	testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
   450  }
   451  
   452  func TestPanicDeadlockSyscall(t *testing.T) {
   453  	testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
   454  }
   455  
   456  func TestPanicLoop(t *testing.T) {
   457  	output := runTestProg(t, "testprog", "PanicLoop")
   458  	if want := "panic while printing panic value"; !strings.Contains(output, want) {
   459  		t.Errorf("output does not contain %q:\n%s", want, output)
   460  	}
   461  }
   462  
   463  func TestMemPprof(t *testing.T) {
   464  	testenv.MustHaveGoRun(t)
   465  
   466  	exe, err := buildTestProg(t, "testprog")
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  
   471  	got, err := testEnv(exec.Command(exe, "MemProf")).CombinedOutput()
   472  	if err != nil {
   473  		t.Fatal(err)
   474  	}
   475  	fn := strings.TrimSpace(string(got))
   476  	defer os.Remove(fn)
   477  
   478  	for try := 0; try < 2; try++ {
   479  		cmd := testEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
   480  		// Check that pprof works both with and without explicit executable on command line.
   481  		if try == 0 {
   482  			cmd.Args = append(cmd.Args, exe, fn)
   483  		} else {
   484  			cmd.Args = append(cmd.Args, fn)
   485  		}
   486  		found := false
   487  		for i, e := range cmd.Env {
   488  			if strings.HasPrefix(e, "PPROF_TMPDIR=") {
   489  				cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
   490  				found = true
   491  				break
   492  			}
   493  		}
   494  		if !found {
   495  			cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
   496  		}
   497  
   498  		top, err := cmd.CombinedOutput()
   499  		t.Logf("%s:\n%s", cmd.Args, top)
   500  		if err != nil {
   501  			t.Error(err)
   502  		} else if !bytes.Contains(top, []byte("MemProf")) {
   503  			t.Error("missing MemProf in pprof output")
   504  		}
   505  	}
   506  }
   507  
   508  var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
   509  
   510  func TestConcurrentMapWrites(t *testing.T) {
   511  	if !*concurrentMapTest {
   512  		t.Skip("skipping without -run_concurrent_map_tests")
   513  	}
   514  	testenv.MustHaveGoRun(t)
   515  	output := runTestProg(t, "testprog", "concurrentMapWrites")
   516  	want := "fatal error: concurrent map writes"
   517  	if !strings.HasPrefix(output, want) {
   518  		t.Fatalf("output does not start with %q:\n%s", want, output)
   519  	}
   520  }
   521  func TestConcurrentMapReadWrite(t *testing.T) {
   522  	if !*concurrentMapTest {
   523  		t.Skip("skipping without -run_concurrent_map_tests")
   524  	}
   525  	testenv.MustHaveGoRun(t)
   526  	output := runTestProg(t, "testprog", "concurrentMapReadWrite")
   527  	want := "fatal error: concurrent map read and map write"
   528  	if !strings.HasPrefix(output, want) {
   529  		t.Fatalf("output does not start with %q:\n%s", want, output)
   530  	}
   531  }
   532  func TestConcurrentMapIterateWrite(t *testing.T) {
   533  	if !*concurrentMapTest {
   534  		t.Skip("skipping without -run_concurrent_map_tests")
   535  	}
   536  	testenv.MustHaveGoRun(t)
   537  	output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
   538  	want := "fatal error: concurrent map iteration and map write"
   539  	if !strings.HasPrefix(output, want) {
   540  		t.Fatalf("output does not start with %q:\n%s", want, output)
   541  	}
   542  }
   543  
   544  type point struct {
   545  	x, y *int
   546  }
   547  
   548  func (p *point) negate() {
   549  	*p.x = *p.x * -1
   550  	*p.y = *p.y * -1
   551  }
   552  
   553  // Test for issue #10152.
   554  func TestPanicInlined(t *testing.T) {
   555  	defer func() {
   556  		r := recover()
   557  		if r == nil {
   558  			t.Fatalf("recover failed")
   559  		}
   560  		buf := make([]byte, 2048)
   561  		n := runtime.Stack(buf, false)
   562  		buf = buf[:n]
   563  		if !bytes.Contains(buf, []byte("(*point).negate(")) {
   564  			t.Fatalf("expecting stack trace to contain call to (*point).negate()")
   565  		}
   566  	}()
   567  
   568  	pt := new(point)
   569  	pt.negate()
   570  }
   571  
   572  // Test for issues #3934 and #20018.
   573  // We want to delay exiting until a panic print is complete.
   574  func TestPanicRace(t *testing.T) {
   575  	testenv.MustHaveGoRun(t)
   576  
   577  	exe, err := buildTestProg(t, "testprog")
   578  	if err != nil {
   579  		t.Fatal(err)
   580  	}
   581  
   582  	// The test is intentionally racy, and in my testing does not
   583  	// produce the expected output about 0.05% of the time.
   584  	// So run the program in a loop and only fail the test if we
   585  	// get the wrong output ten times in a row.
   586  	const tries = 10
   587  retry:
   588  	for i := 0; i < tries; i++ {
   589  		got, err := testEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
   590  		if err == nil {
   591  			t.Logf("try %d: program exited successfully, should have failed", i+1)
   592  			continue
   593  		}
   594  
   595  		if i > 0 {
   596  			t.Logf("try %d:\n", i+1)
   597  		}
   598  		t.Logf("%s\n", got)
   599  
   600  		wants := []string{
   601  			"panic: crash",
   602  			"PanicRace",
   603  			"created by ",
   604  		}
   605  		for _, want := range wants {
   606  			if !bytes.Contains(got, []byte(want)) {
   607  				t.Logf("did not find expected string %q", want)
   608  				continue retry
   609  			}
   610  		}
   611  
   612  		// Test generated expected output.
   613  		return
   614  	}
   615  	t.Errorf("test ran %d times without producing expected output", tries)
   616  }