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