github.com/likebike/go--@v0.0.0-20190911215757-0bd925d16e96/go/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  var testprog struct {
    36  	sync.Mutex
    37  	dir    string
    38  	target map[string]buildexe
    39  }
    40  
    41  type buildexe struct {
    42  	exe string
    43  	err error
    44  }
    45  
    46  func runTestProg(t *testing.T, binary, name string, env ...string) string {
    47  	if *flagQuick {
    48  		t.Skip("-quick")
    49  	}
    50  
    51  	testenv.MustHaveGoBuild(t)
    52  
    53  	exe, err := buildTestProg(t, binary)
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  
    58  	cmd := testenv.CleanCmdEnv(exec.Command(exe, name))
    59  	cmd.Env = append(cmd.Env, env...)
    60  	if testing.Short() {
    61  		cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1")
    62  	}
    63  	var b bytes.Buffer
    64  	cmd.Stdout = &b
    65  	cmd.Stderr = &b
    66  	if err := cmd.Start(); err != nil {
    67  		t.Fatalf("starting %s %s: %v", binary, name, err)
    68  	}
    69  
    70  	// If the process doesn't complete within 1 minute,
    71  	// assume it is hanging and kill it to get a stack trace.
    72  	p := cmd.Process
    73  	done := make(chan bool)
    74  	go func() {
    75  		scale := 1
    76  		// This GOARCH/GOOS test is copied from cmd/dist/test.go.
    77  		// TODO(iant): Have cmd/dist update the environment variable.
    78  		if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
    79  			scale = 2
    80  		}
    81  		if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
    82  			if sc, err := strconv.Atoi(s); err == nil {
    83  				scale = sc
    84  			}
    85  		}
    86  
    87  		select {
    88  		case <-done:
    89  		case <-time.After(time.Duration(scale) * time.Minute):
    90  			p.Signal(sigquit)
    91  		}
    92  	}()
    93  
    94  	if err := cmd.Wait(); err != nil {
    95  		t.Logf("%s %s exit status: %v", binary, name, err)
    96  	}
    97  	close(done)
    98  
    99  	return b.String()
   100  }
   101  
   102  func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
   103  	if *flagQuick {
   104  		t.Skip("-quick")
   105  	}
   106  
   107  	checkStaleRuntime(t)
   108  
   109  	testprog.Lock()
   110  	defer testprog.Unlock()
   111  	if testprog.dir == "" {
   112  		dir, err := ioutil.TempDir("", "go-build")
   113  		if err != nil {
   114  			t.Fatalf("failed to create temp directory: %v", err)
   115  		}
   116  		testprog.dir = dir
   117  		toRemove = append(toRemove, dir)
   118  	}
   119  
   120  	if testprog.target == nil {
   121  		testprog.target = make(map[string]buildexe)
   122  	}
   123  	name := binary
   124  	if len(flags) > 0 {
   125  		name += "_" + strings.Join(flags, "_")
   126  	}
   127  	target, ok := testprog.target[name]
   128  	if ok {
   129  		return target.exe, target.err
   130  	}
   131  
   132  	exe := filepath.Join(testprog.dir, name+".exe")
   133  	cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
   134  	cmd.Dir = "testdata/" + binary
   135  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   136  	if err != nil {
   137  		target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
   138  		testprog.target[name] = target
   139  		return "", target.err
   140  	}
   141  	target.exe = exe
   142  	testprog.target[name] = target
   143  	return exe, nil
   144  }
   145  
   146  var (
   147  	staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
   148  	staleRuntimeErr  error
   149  )
   150  
   151  func checkStaleRuntime(t *testing.T) {
   152  	staleRuntimeOnce.Do(func() {
   153  		// 'go run' uses the installed copy of runtime.a, which may be out of date.
   154  		out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.Stale}}", "runtime")).CombinedOutput()
   155  		if err != nil {
   156  			staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
   157  			return
   158  		}
   159  		if string(out) != "false\n" {
   160  			t.Logf("go list -f {{.Stale}} runtime:\n%s", out)
   161  			out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.StaleReason}}", "runtime")).CombinedOutput()
   162  			if err != nil {
   163  				t.Logf("go list -f {{.StaleReason}} failed: %v", err)
   164  			}
   165  			t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out)
   166  			staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
   167  		}
   168  	})
   169  	if staleRuntimeErr != nil {
   170  		t.Fatal(staleRuntimeErr)
   171  	}
   172  }
   173  
   174  func testCrashHandler(t *testing.T, cgo bool) {
   175  	type crashTest struct {
   176  		Cgo bool
   177  	}
   178  	var output string
   179  	if cgo {
   180  		output = runTestProg(t, "testprogcgo", "Crash")
   181  	} else {
   182  		output = runTestProg(t, "testprog", "Crash")
   183  	}
   184  	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
   185  	if output != want {
   186  		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
   187  	}
   188  }
   189  
   190  func TestCrashHandler(t *testing.T) {
   191  	testCrashHandler(t, false)
   192  }
   193  
   194  func testDeadlock(t *testing.T, name string) {
   195  	output := runTestProg(t, "testprog", name)
   196  	want := "fatal error: all goroutines are asleep - deadlock!\n"
   197  	if !strings.HasPrefix(output, want) {
   198  		t.Fatalf("output does not start with %q:\n%s", want, output)
   199  	}
   200  }
   201  
   202  func TestSimpleDeadlock(t *testing.T) {
   203  	testDeadlock(t, "SimpleDeadlock")
   204  }
   205  
   206  func TestInitDeadlock(t *testing.T) {
   207  	testDeadlock(t, "InitDeadlock")
   208  }
   209  
   210  func TestLockedDeadlock(t *testing.T) {
   211  	testDeadlock(t, "LockedDeadlock")
   212  }
   213  
   214  func TestLockedDeadlock2(t *testing.T) {
   215  	testDeadlock(t, "LockedDeadlock2")
   216  }
   217  
   218  func TestGoexitDeadlock(t *testing.T) {
   219  	output := runTestProg(t, "testprog", "GoexitDeadlock")
   220  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   221  	if !strings.Contains(output, want) {
   222  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   223  	}
   224  }
   225  
   226  func TestStackOverflow(t *testing.T) {
   227  	output := runTestProg(t, "testprog", "StackOverflow")
   228  	want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
   229  	if !strings.HasPrefix(output, want) {
   230  		t.Fatalf("output does not start with %q:\n%s", want, output)
   231  	}
   232  }
   233  
   234  func TestThreadExhaustion(t *testing.T) {
   235  	output := runTestProg(t, "testprog", "ThreadExhaustion")
   236  	want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
   237  	if !strings.HasPrefix(output, want) {
   238  		t.Fatalf("output does not start with %q:\n%s", want, output)
   239  	}
   240  }
   241  
   242  func TestRecursivePanic(t *testing.T) {
   243  	output := runTestProg(t, "testprog", "RecursivePanic")
   244  	want := `wrap: bad
   245  panic: again
   246  
   247  `
   248  	if !strings.HasPrefix(output, want) {
   249  		t.Fatalf("output does not start with %q:\n%s", want, output)
   250  	}
   251  
   252  }
   253  
   254  func TestGoexitCrash(t *testing.T) {
   255  	output := runTestProg(t, "testprog", "GoexitExit")
   256  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   257  	if !strings.Contains(output, want) {
   258  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   259  	}
   260  }
   261  
   262  func TestGoexitDefer(t *testing.T) {
   263  	c := make(chan struct{})
   264  	go func() {
   265  		defer func() {
   266  			r := recover()
   267  			if r != nil {
   268  				t.Errorf("non-nil recover during Goexit")
   269  			}
   270  			c <- struct{}{}
   271  		}()
   272  		runtime.Goexit()
   273  	}()
   274  	// Note: if the defer fails to run, we will get a deadlock here
   275  	<-c
   276  }
   277  
   278  func TestGoNil(t *testing.T) {
   279  	output := runTestProg(t, "testprog", "GoNil")
   280  	want := "go of nil func value"
   281  	if !strings.Contains(output, want) {
   282  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   283  	}
   284  }
   285  
   286  func TestMainGoroutineID(t *testing.T) {
   287  	output := runTestProg(t, "testprog", "MainGoroutineID")
   288  	want := "panic: test\n\ngoroutine 1 [running]:\n"
   289  	if !strings.HasPrefix(output, want) {
   290  		t.Fatalf("output does not start with %q:\n%s", want, output)
   291  	}
   292  }
   293  
   294  func TestNoHelperGoroutines(t *testing.T) {
   295  	output := runTestProg(t, "testprog", "NoHelperGoroutines")
   296  	matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
   297  	if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
   298  		t.Fatalf("want to see only goroutine 1, see:\n%s", output)
   299  	}
   300  }
   301  
   302  func TestBreakpoint(t *testing.T) {
   303  	output := runTestProg(t, "testprog", "Breakpoint")
   304  	// If runtime.Breakpoint() is inlined, then the stack trace prints
   305  	// "runtime.Breakpoint(...)" instead of "runtime.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  	t.Parallel()
   406  	output := runTestProg(t, "testprognet", "NetpollDeadlock")
   407  	want := "done\n"
   408  	if !strings.HasSuffix(output, want) {
   409  		t.Fatalf("output does not start with %q:\n%s", want, output)
   410  	}
   411  }
   412  
   413  func TestPanicTraceback(t *testing.T) {
   414  	t.Parallel()
   415  	output := runTestProg(t, "testprog", "PanicTraceback")
   416  	want := "panic: hello"
   417  	if !strings.HasPrefix(output, want) {
   418  		t.Fatalf("output does not start with %q:\n%s", want, output)
   419  	}
   420  
   421  	// Check functions in the traceback.
   422  	fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
   423  	for _, fn := range fns {
   424  		re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
   425  		idx := re.FindStringIndex(output)
   426  		if idx == nil {
   427  			t.Fatalf("expected %q function in traceback:\n%s", fn, output)
   428  		}
   429  		output = output[idx[1]:]
   430  	}
   431  }
   432  
   433  func testPanicDeadlock(t *testing.T, name string, want string) {
   434  	// test issue 14432
   435  	output := runTestProg(t, "testprog", name)
   436  	if !strings.HasPrefix(output, want) {
   437  		t.Fatalf("output does not start with %q:\n%s", want, output)
   438  	}
   439  }
   440  
   441  func TestPanicDeadlockGosched(t *testing.T) {
   442  	testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
   443  }
   444  
   445  func TestPanicDeadlockSyscall(t *testing.T) {
   446  	testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
   447  }
   448  
   449  func TestPanicLoop(t *testing.T) {
   450  	output := runTestProg(t, "testprog", "PanicLoop")
   451  	if want := "panic while printing panic value"; !strings.Contains(output, want) {
   452  		t.Errorf("output does not contain %q:\n%s", want, output)
   453  	}
   454  }
   455  
   456  func TestMemPprof(t *testing.T) {
   457  	testenv.MustHaveGoRun(t)
   458  
   459  	exe, err := buildTestProg(t, "testprog")
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  
   464  	got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput()
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	fn := strings.TrimSpace(string(got))
   469  	defer os.Remove(fn)
   470  
   471  	for try := 0; try < 2; try++ {
   472  		cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
   473  		// Check that pprof works both with and without explicit executable on command line.
   474  		if try == 0 {
   475  			cmd.Args = append(cmd.Args, exe, fn)
   476  		} else {
   477  			cmd.Args = append(cmd.Args, fn)
   478  		}
   479  		found := false
   480  		for i, e := range cmd.Env {
   481  			if strings.HasPrefix(e, "PPROF_TMPDIR=") {
   482  				cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
   483  				found = true
   484  				break
   485  			}
   486  		}
   487  		if !found {
   488  			cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
   489  		}
   490  
   491  		top, err := cmd.CombinedOutput()
   492  		t.Logf("%s:\n%s", cmd.Args, top)
   493  		if err != nil {
   494  			t.Error(err)
   495  		} else if !bytes.Contains(top, []byte("MemProf")) {
   496  			t.Error("missing MemProf in pprof output")
   497  		}
   498  	}
   499  }
   500  
   501  var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
   502  
   503  func TestConcurrentMapWrites(t *testing.T) {
   504  	if !*concurrentMapTest {
   505  		t.Skip("skipping without -run_concurrent_map_tests")
   506  	}
   507  	testenv.MustHaveGoRun(t)
   508  	output := runTestProg(t, "testprog", "concurrentMapWrites")
   509  	want := "fatal error: concurrent map writes"
   510  	if !strings.HasPrefix(output, want) {
   511  		t.Fatalf("output does not start with %q:\n%s", want, output)
   512  	}
   513  }
   514  func TestConcurrentMapReadWrite(t *testing.T) {
   515  	if !*concurrentMapTest {
   516  		t.Skip("skipping without -run_concurrent_map_tests")
   517  	}
   518  	testenv.MustHaveGoRun(t)
   519  	output := runTestProg(t, "testprog", "concurrentMapReadWrite")
   520  	want := "fatal error: concurrent map read and map write"
   521  	if !strings.HasPrefix(output, want) {
   522  		t.Fatalf("output does not start with %q:\n%s", want, output)
   523  	}
   524  }
   525  func TestConcurrentMapIterateWrite(t *testing.T) {
   526  	if !*concurrentMapTest {
   527  		t.Skip("skipping without -run_concurrent_map_tests")
   528  	}
   529  	testenv.MustHaveGoRun(t)
   530  	output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
   531  	want := "fatal error: concurrent map iteration and map write"
   532  	if !strings.HasPrefix(output, want) {
   533  		t.Fatalf("output does not start with %q:\n%s", want, output)
   534  	}
   535  }
   536  
   537  type point struct {
   538  	x, y *int
   539  }
   540  
   541  func (p *point) negate() {
   542  	*p.x = *p.x * -1
   543  	*p.y = *p.y * -1
   544  }
   545  
   546  // Test for issue #10152.
   547  func TestPanicInlined(t *testing.T) {
   548  	defer func() {
   549  		r := recover()
   550  		if r == nil {
   551  			t.Fatalf("recover failed")
   552  		}
   553  		buf := make([]byte, 2048)
   554  		n := runtime.Stack(buf, false)
   555  		buf = buf[:n]
   556  		if !bytes.Contains(buf, []byte("(*point).negate(")) {
   557  			t.Fatalf("expecting stack trace to contain call to (*point).negate()")
   558  		}
   559  	}()
   560  
   561  	pt := new(point)
   562  	pt.negate()
   563  }
   564  
   565  // Test for issues #3934 and #20018.
   566  // We want to delay exiting until a panic print is complete.
   567  func TestPanicRace(t *testing.T) {
   568  	testenv.MustHaveGoRun(t)
   569  
   570  	exe, err := buildTestProg(t, "testprog")
   571  	if err != nil {
   572  		t.Fatal(err)
   573  	}
   574  
   575  	// The test is intentionally racy, and in my testing does not
   576  	// produce the expected output about 0.05% of the time.
   577  	// So run the program in a loop and only fail the test if we
   578  	// get the wrong output ten times in a row.
   579  	const tries = 10
   580  retry:
   581  	for i := 0; i < tries; i++ {
   582  		got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
   583  		if err == nil {
   584  			t.Logf("try %d: program exited successfully, should have failed", i+1)
   585  			continue
   586  		}
   587  
   588  		if i > 0 {
   589  			t.Logf("try %d:\n", i+1)
   590  		}
   591  		t.Logf("%s\n", got)
   592  
   593  		wants := []string{
   594  			"panic: crash",
   595  			"PanicRace",
   596  			"created by ",
   597  		}
   598  		for _, want := range wants {
   599  			if !bytes.Contains(got, []byte(want)) {
   600  				t.Logf("did not find expected string %q", want)
   601  				continue retry
   602  			}
   603  		}
   604  
   605  		// Test generated expected output.
   606  		return
   607  	}
   608  	t.Errorf("test ran %d times without producing expected output", tries)
   609  }
   610  
   611  func TestBadTraceback(t *testing.T) {
   612  	output := runTestProg(t, "testprog", "BadTraceback")
   613  	for _, want := range []string{
   614  		"runtime: unexpected return pc",
   615  		"called from 0xbad",
   616  		"00000bad",    // Smashed LR in hex dump
   617  		"<main.badLR", // Symbolization in hex dump (badLR1 or badLR2)
   618  	} {
   619  		if !strings.Contains(output, want) {
   620  			t.Errorf("output does not contain %q:\n%s", want, output)
   621  		}
   622  	}
   623  }