|  | // Copyright 2012 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package runtime_test | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "flag" | 
|  | "fmt" | 
|  | "internal/testenv" | 
|  | "io/ioutil" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path/filepath" | 
|  | "regexp" | 
|  | "runtime" | 
|  | "strconv" | 
|  | "strings" | 
|  | "sync" | 
|  | "testing" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | var toRemove []string | 
|  |  | 
|  | func TestMain(m *testing.M) { | 
|  | status := m.Run() | 
|  | for _, file := range toRemove { | 
|  | os.RemoveAll(file) | 
|  | } | 
|  | os.Exit(status) | 
|  | } | 
|  |  | 
|  | var testprog struct { | 
|  | sync.Mutex | 
|  | dir    string | 
|  | target map[string]buildexe | 
|  | } | 
|  |  | 
|  | type buildexe struct { | 
|  | exe string | 
|  | err error | 
|  | } | 
|  |  | 
|  | func runTestProg(t *testing.T, binary, name string, env ...string) string { | 
|  | if *flagQuick { | 
|  | t.Skip("-quick") | 
|  | } | 
|  |  | 
|  | testenv.MustHaveGoBuild(t) | 
|  |  | 
|  | exe, err := buildTestProg(t, binary) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | cmd := testenv.CleanCmdEnv(exec.Command(exe, name)) | 
|  | cmd.Env = append(cmd.Env, env...) | 
|  | if testing.Short() { | 
|  | cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1") | 
|  | } | 
|  | var b bytes.Buffer | 
|  | cmd.Stdout = &b | 
|  | cmd.Stderr = &b | 
|  | if err := cmd.Start(); err != nil { | 
|  | t.Fatalf("starting %s %s: %v", binary, name, err) | 
|  | } | 
|  |  | 
|  | // If the process doesn't complete within 1 minute, | 
|  | // assume it is hanging and kill it to get a stack trace. | 
|  | p := cmd.Process | 
|  | done := make(chan bool) | 
|  | go func() { | 
|  | scale := 1 | 
|  | // This GOARCH/GOOS test is copied from cmd/dist/test.go. | 
|  | // TODO(iant): Have cmd/dist update the environment variable. | 
|  | if runtime.GOARCH == "arm" || runtime.GOOS == "windows" { | 
|  | scale = 2 | 
|  | } | 
|  | if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { | 
|  | if sc, err := strconv.Atoi(s); err == nil { | 
|  | scale = sc | 
|  | } | 
|  | } | 
|  |  | 
|  | select { | 
|  | case <-done: | 
|  | case <-time.After(time.Duration(scale) * time.Minute): | 
|  | p.Signal(sigquit) | 
|  | } | 
|  | }() | 
|  |  | 
|  | if err := cmd.Wait(); err != nil { | 
|  | t.Logf("%s %s exit status: %v", binary, name, err) | 
|  | } | 
|  | close(done) | 
|  |  | 
|  | return b.String() | 
|  | } | 
|  |  | 
|  | func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) { | 
|  | if *flagQuick { | 
|  | t.Skip("-quick") | 
|  | } | 
|  |  | 
|  | checkStaleRuntime(t) | 
|  |  | 
|  | testprog.Lock() | 
|  | defer testprog.Unlock() | 
|  | if testprog.dir == "" { | 
|  | dir, err := ioutil.TempDir("", "go-build") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create temp directory: %v", err) | 
|  | } | 
|  | testprog.dir = dir | 
|  | toRemove = append(toRemove, dir) | 
|  | } | 
|  |  | 
|  | if testprog.target == nil { | 
|  | testprog.target = make(map[string]buildexe) | 
|  | } | 
|  | name := binary | 
|  | if len(flags) > 0 { | 
|  | name += "_" + strings.Join(flags, "_") | 
|  | } | 
|  | target, ok := testprog.target[name] | 
|  | if ok { | 
|  | return target.exe, target.err | 
|  | } | 
|  |  | 
|  | exe := filepath.Join(testprog.dir, name+".exe") | 
|  | cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...) | 
|  | cmd.Dir = "testdata/" + binary | 
|  | out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() | 
|  | if err != nil { | 
|  | target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out) | 
|  | testprog.target[name] = target | 
|  | return "", target.err | 
|  | } | 
|  | target.exe = exe | 
|  | testprog.target[name] = target | 
|  | return exe, nil | 
|  | } | 
|  |  | 
|  | var ( | 
|  | staleRuntimeOnce sync.Once // guards init of staleRuntimeErr | 
|  | staleRuntimeErr  error | 
|  | ) | 
|  |  | 
|  | func checkStaleRuntime(t *testing.T) { | 
|  | staleRuntimeOnce.Do(func() { | 
|  | if runtime.Compiler == "gccgo" { | 
|  | return | 
|  | } | 
|  | // 'go run' uses the installed copy of runtime.a, which may be out of date. | 
|  | out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.Stale}}", "runtime")).CombinedOutput() | 
|  | if err != nil { | 
|  | staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out)) | 
|  | return | 
|  | } | 
|  | if string(out) != "false\n" { | 
|  | t.Logf("go list -f {{.Stale}} runtime:\n%s", out) | 
|  | out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "-f", "{{.StaleReason}}", "runtime")).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Logf("go list -f {{.StaleReason}} failed: %v", err) | 
|  | } | 
|  | t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out) | 
|  | staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.") | 
|  | } | 
|  | }) | 
|  | if staleRuntimeErr != nil { | 
|  | t.Fatal(staleRuntimeErr) | 
|  | } | 
|  | } | 
|  |  | 
|  | func testCrashHandler(t *testing.T, cgo bool) { | 
|  | type crashTest struct { | 
|  | Cgo bool | 
|  | } | 
|  | var output string | 
|  | if cgo { | 
|  | output = runTestProg(t, "testprogcgo", "Crash") | 
|  | } else { | 
|  | output = runTestProg(t, "testprog", "Crash") | 
|  | } | 
|  | want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" | 
|  | if output != want { | 
|  | t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestCrashHandler(t *testing.T) { | 
|  | testCrashHandler(t, false) | 
|  | } | 
|  |  | 
|  | func testDeadlock(t *testing.T, name string) { | 
|  | output := runTestProg(t, "testprog", name) | 
|  | want := "fatal error: all goroutines are asleep - deadlock!\n" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSimpleDeadlock(t *testing.T) { | 
|  | testDeadlock(t, "SimpleDeadlock") | 
|  | } | 
|  |  | 
|  | func TestInitDeadlock(t *testing.T) { | 
|  | testDeadlock(t, "InitDeadlock") | 
|  | } | 
|  |  | 
|  | func TestLockedDeadlock(t *testing.T) { | 
|  | testDeadlock(t, "LockedDeadlock") | 
|  | } | 
|  |  | 
|  | func TestLockedDeadlock2(t *testing.T) { | 
|  | testDeadlock(t, "LockedDeadlock2") | 
|  | } | 
|  |  | 
|  | func TestGoexitDeadlock(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "GoexitDeadlock") | 
|  | want := "no goroutines (main called runtime.Goexit) - deadlock!" | 
|  | if !strings.Contains(output, want) { | 
|  | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestStackOverflow(t *testing.T) { | 
|  | if runtime.Compiler == "gccgo" { | 
|  | t.Skip("gccgo does not do stack overflow checking") | 
|  | } | 
|  | output := runTestProg(t, "testprog", "StackOverflow") | 
|  | want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestThreadExhaustion(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "ThreadExhaustion") | 
|  | want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestRecursivePanic(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "RecursivePanic") | 
|  | want := `wrap: bad | 
|  | panic: again | 
|  |  | 
|  | ` | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | func TestGoexitCrash(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "GoexitExit") | 
|  | want := "no goroutines (main called runtime.Goexit) - deadlock!" | 
|  | if !strings.Contains(output, want) { | 
|  | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestGoexitDefer(t *testing.T) { | 
|  | c := make(chan struct{}) | 
|  | go func() { | 
|  | defer func() { | 
|  | r := recover() | 
|  | if r != nil { | 
|  | t.Errorf("non-nil recover during Goexit") | 
|  | } | 
|  | c <- struct{}{} | 
|  | }() | 
|  | runtime.Goexit() | 
|  | }() | 
|  | // Note: if the defer fails to run, we will get a deadlock here | 
|  | <-c | 
|  | } | 
|  |  | 
|  | func TestGoNil(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "GoNil") | 
|  | want := "go of nil func value" | 
|  | if !strings.Contains(output, want) { | 
|  | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMainGoroutineID(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "MainGoroutineID") | 
|  | want := "panic: test\n\ngoroutine 1 [running]:\n" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestNoHelperGoroutines(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "NoHelperGoroutines") | 
|  | matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1) | 
|  | if len(matches) != 1 || matches[0][0] != "goroutine 1 [" { | 
|  | t.Fatalf("want to see only goroutine 1, see:\n%s", output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestBreakpoint(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "Breakpoint") | 
|  | // If runtime.Breakpoint() is inlined, then the stack trace prints | 
|  | // "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()". | 
|  | // For gccgo, no parens. | 
|  | want := "runtime.Breakpoint" | 
|  | if !strings.Contains(output, want) { | 
|  | t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestGoexitInPanic(t *testing.T) { | 
|  | // see issue 8774: this code used to trigger an infinite recursion | 
|  | output := runTestProg(t, "testprog", "GoexitInPanic") | 
|  | want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Issue 14965: Runtime panics should be of type runtime.Error | 
|  | func TestRuntimePanicWithRuntimeError(t *testing.T) { | 
|  | testCases := [...]func(){ | 
|  | 0: func() { | 
|  | var m map[uint64]bool | 
|  | m[1234] = true | 
|  | }, | 
|  | 1: func() { | 
|  | ch := make(chan struct{}) | 
|  | close(ch) | 
|  | close(ch) | 
|  | }, | 
|  | 2: func() { | 
|  | var ch = make(chan struct{}) | 
|  | close(ch) | 
|  | ch <- struct{}{} | 
|  | }, | 
|  | 3: func() { | 
|  | var s = make([]int, 2) | 
|  | _ = s[2] | 
|  | }, | 
|  | 4: func() { | 
|  | n := -1 | 
|  | _ = make(chan bool, n) | 
|  | }, | 
|  | 5: func() { | 
|  | close((chan bool)(nil)) | 
|  | }, | 
|  | } | 
|  |  | 
|  | for i, fn := range testCases { | 
|  | got := panicValue(fn) | 
|  | if _, ok := got.(runtime.Error); !ok { | 
|  | t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func panicValue(fn func()) (recovered interface{}) { | 
|  | defer func() { | 
|  | recovered = recover() | 
|  | }() | 
|  | fn() | 
|  | return | 
|  | } | 
|  |  | 
|  | func TestPanicAfterGoexit(t *testing.T) { | 
|  | // an uncaught panic should still work after goexit | 
|  | output := runTestProg(t, "testprog", "PanicAfterGoexit") | 
|  | want := "panic: hello" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestRecoveredPanicAfterGoexit(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit") | 
|  | want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestRecoverBeforePanicAfterGoexit(t *testing.T) { | 
|  | // 1. defer a function that recovers | 
|  | // 2. defer a function that panics | 
|  | // 3. call goexit | 
|  | // Goexit should run the #2 defer. Its panic | 
|  | // should be caught by the #1 defer, and execution | 
|  | // should resume in the caller. Like the Goexit | 
|  | // never happened! | 
|  | defer func() { | 
|  | r := recover() | 
|  | if r == nil { | 
|  | panic("bad recover") | 
|  | } | 
|  | }() | 
|  | defer func() { | 
|  | panic("hello") | 
|  | }() | 
|  | runtime.Goexit() | 
|  | } | 
|  |  | 
|  | func TestNetpollDeadlock(t *testing.T) { | 
|  | t.Parallel() | 
|  | output := runTestProg(t, "testprognet", "NetpollDeadlock") | 
|  | want := "done\n" | 
|  | if !strings.HasSuffix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPanicTraceback(t *testing.T) { | 
|  | t.Parallel() | 
|  | output := runTestProg(t, "testprog", "PanicTraceback") | 
|  | want := "panic: hello" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  |  | 
|  | // Check functions in the traceback. | 
|  | fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"} | 
|  | if runtime.Compiler == "gccgo" { | 
|  | fns = []string{"main.pt1..func1", "panic", "main.pt2..func1", "panic", "main.pt2", "main.pt1"} | 
|  | } | 
|  | for _, fn := range fns { | 
|  | var re *regexp.Regexp | 
|  | if runtime.Compiler != "gccgo" { | 
|  | re = regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`) | 
|  | } else { | 
|  | re = regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `.*\n`) | 
|  | } | 
|  | idx := re.FindStringIndex(output) | 
|  | if idx == nil { | 
|  | t.Fatalf("expected %q function in traceback:\n%s", fn, output) | 
|  | } | 
|  | output = output[idx[1]:] | 
|  | } | 
|  | } | 
|  |  | 
|  | func testPanicDeadlock(t *testing.T, name string, want string) { | 
|  | // test issue 14432 | 
|  | output := runTestProg(t, "testprog", name) | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPanicDeadlockGosched(t *testing.T) { | 
|  | testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n") | 
|  | } | 
|  |  | 
|  | func TestPanicDeadlockSyscall(t *testing.T) { | 
|  | testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n") | 
|  | } | 
|  |  | 
|  | func TestPanicLoop(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "PanicLoop") | 
|  | if want := "panic while printing panic value"; !strings.Contains(output, want) { | 
|  | t.Errorf("output does not contain %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMemPprof(t *testing.T) { | 
|  | testenv.MustHaveGoRun(t) | 
|  | if runtime.Compiler == "gccgo" { | 
|  | t.Skip("gccgo may not have the pprof tool") | 
|  | } | 
|  |  | 
|  | exe, err := buildTestProg(t, "testprog") | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | fn := strings.TrimSpace(string(got)) | 
|  | defer os.Remove(fn) | 
|  |  | 
|  | for try := 0; try < 2; try++ { | 
|  | cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top")) | 
|  | // Check that pprof works both with and without explicit executable on command line. | 
|  | if try == 0 { | 
|  | cmd.Args = append(cmd.Args, exe, fn) | 
|  | } else { | 
|  | cmd.Args = append(cmd.Args, fn) | 
|  | } | 
|  | found := false | 
|  | for i, e := range cmd.Env { | 
|  | if strings.HasPrefix(e, "PPROF_TMPDIR=") { | 
|  | cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir() | 
|  | found = true | 
|  | break | 
|  | } | 
|  | } | 
|  | if !found { | 
|  | cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir()) | 
|  | } | 
|  |  | 
|  | top, err := cmd.CombinedOutput() | 
|  | t.Logf("%s:\n%s", cmd.Args, top) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | } else if !bytes.Contains(top, []byte("MemProf")) { | 
|  | t.Error("missing MemProf in pprof output") | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests") | 
|  |  | 
|  | func TestConcurrentMapWrites(t *testing.T) { | 
|  | if !*concurrentMapTest { | 
|  | t.Skip("skipping without -run_concurrent_map_tests") | 
|  | } | 
|  | testenv.MustHaveGoRun(t) | 
|  | output := runTestProg(t, "testprog", "concurrentMapWrites") | 
|  | want := "fatal error: concurrent map writes" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  | func TestConcurrentMapReadWrite(t *testing.T) { | 
|  | if !*concurrentMapTest { | 
|  | t.Skip("skipping without -run_concurrent_map_tests") | 
|  | } | 
|  | testenv.MustHaveGoRun(t) | 
|  | output := runTestProg(t, "testprog", "concurrentMapReadWrite") | 
|  | want := "fatal error: concurrent map read and map write" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  | func TestConcurrentMapIterateWrite(t *testing.T) { | 
|  | if !*concurrentMapTest { | 
|  | t.Skip("skipping without -run_concurrent_map_tests") | 
|  | } | 
|  | testenv.MustHaveGoRun(t) | 
|  | output := runTestProg(t, "testprog", "concurrentMapIterateWrite") | 
|  | want := "fatal error: concurrent map iteration and map write" | 
|  | if !strings.HasPrefix(output, want) { | 
|  | t.Fatalf("output does not start with %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | type point struct { | 
|  | x, y *int | 
|  | } | 
|  |  | 
|  | func (p *point) negate() { | 
|  | *p.x = *p.x * -1 | 
|  | *p.y = *p.y * -1 | 
|  | } | 
|  |  | 
|  | // Test for issue #10152. | 
|  | func TestPanicInlined(t *testing.T) { | 
|  | defer func() { | 
|  | r := recover() | 
|  | if r == nil { | 
|  | t.Fatalf("recover failed") | 
|  | } | 
|  | buf := make([]byte, 2048) | 
|  | n := runtime.Stack(buf, false) | 
|  | buf = buf[:n] | 
|  | want := []byte("(*point).negate(") | 
|  | if runtime.Compiler == "gccgo" { | 
|  | want = []byte("point.negate") | 
|  | } | 
|  | if !bytes.Contains(buf, want) { | 
|  | t.Logf("%s", buf) | 
|  | t.Fatalf("expecting stack trace to contain call to %s", want) | 
|  | } | 
|  | }() | 
|  |  | 
|  | pt := new(point) | 
|  | pt.negate() | 
|  | } | 
|  |  | 
|  | // Test for issues #3934 and #20018. | 
|  | // We want to delay exiting until a panic print is complete. | 
|  | func TestPanicRace(t *testing.T) { | 
|  | testenv.MustHaveGoRun(t) | 
|  |  | 
|  | exe, err := buildTestProg(t, "testprog") | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | // The test is intentionally racy, and in my testing does not | 
|  | // produce the expected output about 0.05% of the time. | 
|  | // So run the program in a loop and only fail the test if we | 
|  | // get the wrong output ten times in a row. | 
|  | const tries = 10 | 
|  | retry: | 
|  | for i := 0; i < tries; i++ { | 
|  | got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput() | 
|  | if err == nil { | 
|  | t.Logf("try %d: program exited successfully, should have failed", i+1) | 
|  | continue | 
|  | } | 
|  |  | 
|  | if i > 0 { | 
|  | t.Logf("try %d:\n", i+1) | 
|  | } | 
|  | t.Logf("%s\n", got) | 
|  |  | 
|  | wants := []string{ | 
|  | "panic: crash", | 
|  | "PanicRace", | 
|  | "created by ", | 
|  | } | 
|  | if runtime.Compiler == "gccgo" { | 
|  | // gccgo will dump a function name like main.$nested30. | 
|  | // Match on the file name instead. | 
|  | wants[1] = "panicrace" | 
|  | } | 
|  | for _, want := range wants { | 
|  | if !bytes.Contains(got, []byte(want)) { | 
|  | t.Logf("did not find expected string %q", want) | 
|  | continue retry | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test generated expected output. | 
|  | return | 
|  | } | 
|  | t.Errorf("test ran %d times without producing expected output", tries) | 
|  | } | 
|  |  | 
|  | func TestBadTraceback(t *testing.T) { | 
|  | if runtime.Compiler == "gccgo" { | 
|  | t.Skip("gccgo does not do a hex dump") | 
|  | } | 
|  | output := runTestProg(t, "testprog", "BadTraceback") | 
|  | for _, want := range []string{ | 
|  | "runtime: unexpected return pc", | 
|  | "called from 0xbad", | 
|  | "00000bad",    // Smashed LR in hex dump | 
|  | "<main.badLR", // Symbolization in hex dump (badLR1 or badLR2) | 
|  | } { | 
|  | if !strings.Contains(output, want) { | 
|  | t.Errorf("output does not contain %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestTimePprof(t *testing.T) { | 
|  | if runtime.Compiler == "gccgo" { | 
|  | t.Skip("gccgo may not have the pprof tool") | 
|  | } | 
|  | if runtime.GOOS == "aix" { | 
|  | t.Skip("pprof not yet available on AIX (see golang.org/issue/28555)") | 
|  | } | 
|  | fn := runTestProg(t, "testprog", "TimeProf") | 
|  | fn = strings.TrimSpace(fn) | 
|  | defer os.Remove(fn) | 
|  |  | 
|  | cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-top", "-nodecount=1", fn)) | 
|  | cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir()) | 
|  | top, err := cmd.CombinedOutput() | 
|  | t.Logf("%s", top) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | } else if bytes.Contains(top, []byte("ExternalCode")) { | 
|  | t.Error("profiler refers to ExternalCode") | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that runtime.abort does so. | 
|  | func TestAbort(t *testing.T) { | 
|  | // Pass GOTRACEBACK to ensure we get runtime frames. | 
|  | output := runTestProg(t, "testprog", "Abort", "GOTRACEBACK=system") | 
|  | if want := "runtime.abort"; !strings.Contains(output, want) { | 
|  | t.Errorf("output does not contain %q:\n%s", want, output) | 
|  | } | 
|  | if strings.Contains(output, "BAD") { | 
|  | t.Errorf("output contains BAD:\n%s", output) | 
|  | } | 
|  | // Check that it's a signal traceback. | 
|  | want := "PC=" | 
|  | // For systems that use a breakpoint, check specifically for that. | 
|  | if runtime.Compiler == "gc" { | 
|  | switch runtime.GOARCH { | 
|  | case "386", "amd64": | 
|  | switch runtime.GOOS { | 
|  | case "plan9": | 
|  | want = "sys: breakpoint" | 
|  | case "windows": | 
|  | want = "Exception 0x80000003" | 
|  | default: | 
|  | want = "SIGTRAP" | 
|  | } | 
|  | } | 
|  | } | 
|  | if !strings.Contains(output, want) { | 
|  | t.Errorf("output does not contain %q:\n%s", want, output) | 
|  | } | 
|  | } | 
|  |  | 
|  | // For TestRuntimePanic: test a panic in the runtime package without | 
|  | // involving the testing harness. | 
|  | func init() { | 
|  | if os.Getenv("GO_TEST_RUNTIME_PANIC") == "1" { | 
|  | defer func() { | 
|  | if r := recover(); r != nil { | 
|  | // We expect to crash, so exit 0 | 
|  | // to indicate failure. | 
|  | os.Exit(0) | 
|  | } | 
|  | }() | 
|  | runtime.PanicForTesting(nil, 1) | 
|  | // We expect to crash, so exit 0 to indicate failure. | 
|  | os.Exit(0) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestRuntimePanic(t *testing.T) { | 
|  | testenv.MustHaveExec(t) | 
|  | cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestRuntimePanic")) | 
|  | cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_PANIC=1") | 
|  | out, err := cmd.CombinedOutput() | 
|  | t.Logf("%s", out) | 
|  | if err == nil { | 
|  | t.Error("child process did not fail") | 
|  | } else if want := "runtime.unexportedPanicForTesting"; !bytes.Contains(out, []byte(want)) { | 
|  | t.Errorf("output did not contain expected string %q", want) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that g0 stack overflows are handled gracefully. | 
|  | func TestG0StackOverflow(t *testing.T) { | 
|  | testenv.MustHaveExec(t) | 
|  |  | 
|  | switch runtime.GOOS { | 
|  | case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "android": | 
|  | t.Skipf("g0 stack is wrong on pthread platforms (see golang.org/issue/26061)") | 
|  | } | 
|  |  | 
|  | if os.Getenv("TEST_G0_STACK_OVERFLOW") != "1" { | 
|  | cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestG0StackOverflow", "-test.v")) | 
|  | cmd.Env = append(cmd.Env, "TEST_G0_STACK_OVERFLOW=1") | 
|  | out, err := cmd.CombinedOutput() | 
|  | // Don't check err since it's expected to crash. | 
|  | if n := strings.Count(string(out), "morestack on g0\n"); n != 1 { | 
|  | t.Fatalf("%s\n(exit status %v)", out, err) | 
|  | } | 
|  | // Check that it's a signal-style traceback. | 
|  | if runtime.GOOS != "windows" { | 
|  | if want := "PC="; !strings.Contains(string(out), want) { | 
|  | t.Errorf("output does not contain %q:\n%s", want, out) | 
|  | } | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | runtime.G0StackOverflow() | 
|  | } | 
|  |  | 
|  | // Test that panic message is not clobbered. | 
|  | // See issue 30150. | 
|  | func TestDoublePanic(t *testing.T) { | 
|  | output := runTestProg(t, "testprog", "DoublePanic", "GODEBUG=clobberfree=1") | 
|  | wants := []string{"panic: XXX", "panic: YYY"} | 
|  | for _, want := range wants { | 
|  | if !strings.Contains(output, want) { | 
|  | t.Errorf("output:\n%s\n\nwant output containing: %s", output, want) | 
|  | } | 
|  | } | 
|  | } |