github.com/aloncn/graphics-go@v0.0.1/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  	"fmt"
     9  	"internal/testenv"
    10  	"io/ioutil"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  )
    20  
    21  var toRemove []string
    22  
    23  func TestMain(m *testing.M) {
    24  	status := m.Run()
    25  	for _, file := range toRemove {
    26  		os.RemoveAll(file)
    27  	}
    28  	os.Exit(status)
    29  }
    30  
    31  func testEnv(cmd *exec.Cmd) *exec.Cmd {
    32  	if cmd.Env != nil {
    33  		panic("environment already set")
    34  	}
    35  	for _, env := range os.Environ() {
    36  		// Exclude GODEBUG from the environment to prevent its output
    37  		// from breaking tests that are trying to parse other command output.
    38  		if strings.HasPrefix(env, "GODEBUG=") {
    39  			continue
    40  		}
    41  		// Exclude GOTRACEBACK for the same reason.
    42  		if strings.HasPrefix(env, "GOTRACEBACK=") {
    43  			continue
    44  		}
    45  		cmd.Env = append(cmd.Env, env)
    46  	}
    47  	return cmd
    48  }
    49  
    50  var testprog struct {
    51  	sync.Mutex
    52  	dir    string
    53  	target map[string]buildexe
    54  }
    55  
    56  type buildexe struct {
    57  	exe string
    58  	err error
    59  }
    60  
    61  func runTestProg(t *testing.T, binary, name string) string {
    62  	testenv.MustHaveGoBuild(t)
    63  
    64  	exe, err := buildTestProg(t, binary)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	got, _ := testEnv(exec.Command(exe, name)).CombinedOutput()
    69  	return string(got)
    70  }
    71  
    72  func buildTestProg(t *testing.T, binary string) (string, error) {
    73  	checkStaleRuntime(t)
    74  
    75  	testprog.Lock()
    76  	defer testprog.Unlock()
    77  	if testprog.dir == "" {
    78  		dir, err := ioutil.TempDir("", "go-build")
    79  		if err != nil {
    80  			t.Fatalf("failed to create temp directory: %v", err)
    81  		}
    82  		testprog.dir = dir
    83  		toRemove = append(toRemove, dir)
    84  	}
    85  
    86  	if testprog.target == nil {
    87  		testprog.target = make(map[string]buildexe)
    88  	}
    89  	target, ok := testprog.target[binary]
    90  	if ok {
    91  		return target.exe, target.err
    92  	}
    93  
    94  	exe := filepath.Join(testprog.dir, binary+".exe")
    95  	cmd := exec.Command("go", "build", "-o", exe)
    96  	cmd.Dir = "testdata/" + binary
    97  	out, err := testEnv(cmd).CombinedOutput()
    98  	if err != nil {
    99  		exe = ""
   100  		target.err = fmt.Errorf("building %s: %v\n%s", binary, err, out)
   101  		testprog.target[binary] = target
   102  		return "", target.err
   103  	}
   104  	target.exe = exe
   105  	testprog.target[binary] = target
   106  	return exe, nil
   107  }
   108  
   109  var (
   110  	staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
   111  	staleRuntimeErr  error
   112  )
   113  
   114  func checkStaleRuntime(t *testing.T) {
   115  	staleRuntimeOnce.Do(func() {
   116  		// 'go run' uses the installed copy of runtime.a, which may be out of date.
   117  		out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput()
   118  		if err != nil {
   119  			staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
   120  			return
   121  		}
   122  		if string(out) != "false\n" {
   123  			staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
   124  		}
   125  	})
   126  	if staleRuntimeErr != nil {
   127  		t.Fatal(staleRuntimeErr)
   128  	}
   129  }
   130  
   131  func testCrashHandler(t *testing.T, cgo bool) {
   132  	type crashTest struct {
   133  		Cgo bool
   134  	}
   135  	var output string
   136  	if cgo {
   137  		output = runTestProg(t, "testprogcgo", "Crash")
   138  	} else {
   139  		output = runTestProg(t, "testprog", "Crash")
   140  	}
   141  	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
   142  	if output != want {
   143  		t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
   144  	}
   145  }
   146  
   147  func TestCrashHandler(t *testing.T) {
   148  	testCrashHandler(t, false)
   149  }
   150  
   151  func testDeadlock(t *testing.T, name string) {
   152  	output := runTestProg(t, "testprog", name)
   153  	want := "fatal error: all goroutines are asleep - deadlock!\n"
   154  	if !strings.HasPrefix(output, want) {
   155  		t.Fatalf("output does not start with %q:\n%s", want, output)
   156  	}
   157  }
   158  
   159  func TestSimpleDeadlock(t *testing.T) {
   160  	testDeadlock(t, "SimpleDeadlock")
   161  }
   162  
   163  func TestInitDeadlock(t *testing.T) {
   164  	testDeadlock(t, "InitDeadlock")
   165  }
   166  
   167  func TestLockedDeadlock(t *testing.T) {
   168  	testDeadlock(t, "LockedDeadlock")
   169  }
   170  
   171  func TestLockedDeadlock2(t *testing.T) {
   172  	testDeadlock(t, "LockedDeadlock2")
   173  }
   174  
   175  func TestGoexitDeadlock(t *testing.T) {
   176  	output := runTestProg(t, "testprog", "GoexitDeadlock")
   177  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   178  	if !strings.Contains(output, want) {
   179  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   180  	}
   181  }
   182  
   183  func TestStackOverflow(t *testing.T) {
   184  	output := runTestProg(t, "testprog", "StackOverflow")
   185  	want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
   186  	if !strings.HasPrefix(output, want) {
   187  		t.Fatalf("output does not start with %q:\n%s", want, output)
   188  	}
   189  }
   190  
   191  func TestThreadExhaustion(t *testing.T) {
   192  	output := runTestProg(t, "testprog", "ThreadExhaustion")
   193  	want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
   194  	if !strings.HasPrefix(output, want) {
   195  		t.Fatalf("output does not start with %q:\n%s", want, output)
   196  	}
   197  }
   198  
   199  func TestRecursivePanic(t *testing.T) {
   200  	output := runTestProg(t, "testprog", "RecursivePanic")
   201  	want := `wrap: bad
   202  panic: again
   203  
   204  `
   205  	if !strings.HasPrefix(output, want) {
   206  		t.Fatalf("output does not start with %q:\n%s", want, output)
   207  	}
   208  
   209  }
   210  
   211  func TestGoexitCrash(t *testing.T) {
   212  	output := runTestProg(t, "testprog", "GoexitExit")
   213  	want := "no goroutines (main called runtime.Goexit) - deadlock!"
   214  	if !strings.Contains(output, want) {
   215  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   216  	}
   217  }
   218  
   219  func TestGoexitDefer(t *testing.T) {
   220  	c := make(chan struct{})
   221  	go func() {
   222  		defer func() {
   223  			r := recover()
   224  			if r != nil {
   225  				t.Errorf("non-nil recover during Goexit")
   226  			}
   227  			c <- struct{}{}
   228  		}()
   229  		runtime.Goexit()
   230  	}()
   231  	// Note: if the defer fails to run, we will get a deadlock here
   232  	<-c
   233  }
   234  
   235  func TestGoNil(t *testing.T) {
   236  	output := runTestProg(t, "testprog", "GoNil")
   237  	want := "go of nil func value"
   238  	if !strings.Contains(output, want) {
   239  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   240  	}
   241  }
   242  
   243  func TestMainGoroutineID(t *testing.T) {
   244  	output := runTestProg(t, "testprog", "MainGoroutineID")
   245  	want := "panic: test\n\ngoroutine 1 [running]:\n"
   246  	if !strings.HasPrefix(output, want) {
   247  		t.Fatalf("output does not start with %q:\n%s", want, output)
   248  	}
   249  }
   250  
   251  func TestNoHelperGoroutines(t *testing.T) {
   252  	output := runTestProg(t, "testprog", "NoHelperGoroutines")
   253  	matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
   254  	if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
   255  		t.Fatalf("want to see only goroutine 1, see:\n%s", output)
   256  	}
   257  }
   258  
   259  func TestBreakpoint(t *testing.T) {
   260  	output := runTestProg(t, "testprog", "Breakpoint")
   261  	want := "runtime.Breakpoint()"
   262  	if !strings.Contains(output, want) {
   263  		t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
   264  	}
   265  }
   266  
   267  func TestGoexitInPanic(t *testing.T) {
   268  	// see issue 8774: this code used to trigger an infinite recursion
   269  	output := runTestProg(t, "testprog", "GoexitInPanic")
   270  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   271  	if !strings.HasPrefix(output, want) {
   272  		t.Fatalf("output does not start with %q:\n%s", want, output)
   273  	}
   274  }
   275  
   276  func TestPanicAfterGoexit(t *testing.T) {
   277  	// an uncaught panic should still work after goexit
   278  	output := runTestProg(t, "testprog", "PanicAfterGoexit")
   279  	want := "panic: hello"
   280  	if !strings.HasPrefix(output, want) {
   281  		t.Fatalf("output does not start with %q:\n%s", want, output)
   282  	}
   283  }
   284  
   285  func TestRecoveredPanicAfterGoexit(t *testing.T) {
   286  	output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
   287  	want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
   288  	if !strings.HasPrefix(output, want) {
   289  		t.Fatalf("output does not start with %q:\n%s", want, output)
   290  	}
   291  }
   292  
   293  func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
   294  	// 1. defer a function that recovers
   295  	// 2. defer a function that panics
   296  	// 3. call goexit
   297  	// Goexit should run the #2 defer.  Its panic
   298  	// should be caught by the #1 defer, and execution
   299  	// should resume in the caller.  Like the Goexit
   300  	// never happened!
   301  	defer func() {
   302  		r := recover()
   303  		if r == nil {
   304  			panic("bad recover")
   305  		}
   306  	}()
   307  	defer func() {
   308  		panic("hello")
   309  	}()
   310  	runtime.Goexit()
   311  }
   312  
   313  func TestNetpollDeadlock(t *testing.T) {
   314  	output := runTestProg(t, "testprognet", "NetpollDeadlock")
   315  	want := "done\n"
   316  	if !strings.HasSuffix(output, want) {
   317  		t.Fatalf("output does not start with %q:\n%s", want, output)
   318  	}
   319  }
   320  
   321  func TestPanicTraceback(t *testing.T) {
   322  	output := runTestProg(t, "testprog", "PanicTraceback")
   323  	want := "panic: hello"
   324  	if !strings.HasPrefix(output, want) {
   325  		t.Fatalf("output does not start with %q:\n%s", want, output)
   326  	}
   327  
   328  	// Check functions in the traceback.
   329  	fns := []string{"panic", "main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
   330  	for _, fn := range fns {
   331  		re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
   332  		idx := re.FindStringIndex(output)
   333  		if idx == nil {
   334  			t.Fatalf("expected %q function in traceback:\n%s", fn, output)
   335  		}
   336  		output = output[idx[1]:]
   337  	}
   338  }