2012-05-29 23:10:54 -06:00
|
|
|
// 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 (
|
2016-06-14 15:38:22 -06:00
|
|
|
"bytes"
|
2016-07-06 16:02:49 -06:00
|
|
|
"flag"
|
2015-01-05 14:14:08 -07:00
|
|
|
"fmt"
|
2015-06-05 09:01:53 -06:00
|
|
|
"internal/testenv"
|
2012-05-29 23:10:54 -06:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2015-02-07 05:31:18 -07:00
|
|
|
"regexp"
|
2014-05-20 10:10:19 -06:00
|
|
|
"runtime"
|
2016-06-14 15:38:22 -06:00
|
|
|
"strconv"
|
2013-02-20 01:15:02 -07:00
|
|
|
"strings"
|
2015-01-05 14:14:08 -07:00
|
|
|
"sync"
|
2012-05-29 23:10:54 -06:00
|
|
|
"testing"
|
2016-06-14 15:38:22 -06:00
|
|
|
"time"
|
2012-05-29 23:10:54 -06:00
|
|
|
)
|
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
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
|
|
|
|
}
|
2014-05-20 10:10:19 -06:00
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
type buildexe struct {
|
|
|
|
exe string
|
|
|
|
err error
|
|
|
|
}
|
2012-05-29 23:10:54 -06:00
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
func runTestProg(t *testing.T, binary, name string) string {
|
|
|
|
testenv.MustHaveGoBuild(t)
|
2012-05-29 23:10:54 -06:00
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
exe, err := buildTestProg(t, binary)
|
2012-05-29 23:10:54 -06:00
|
|
|
if err != nil {
|
2015-12-21 08:29:21 -07:00
|
|
|
t.Fatal(err)
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
2016-06-14 15:38:22 -06:00
|
|
|
|
2017-09-13 07:04:16 -06:00
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(exe, name))
|
2016-06-14 15:38:22 -06:00
|
|
|
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()
|
2015-12-21 08:29:21 -07:00
|
|
|
}
|
2012-05-29 23:10:54 -06:00
|
|
|
|
2016-05-27 17:03:44 -06:00
|
|
|
func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
|
2015-12-21 08:29:21 -07:00
|
|
|
checkStaleRuntime(t)
|
2012-05-29 23:10:54 -06:00
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
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)
|
2014-10-28 19:53:09 -06:00
|
|
|
}
|
2015-12-21 08:29:21 -07:00
|
|
|
testprog.dir = dir
|
|
|
|
toRemove = append(toRemove, dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
if testprog.target == nil {
|
|
|
|
testprog.target = make(map[string]buildexe)
|
|
|
|
}
|
2016-05-27 17:03:44 -06:00
|
|
|
name := binary
|
|
|
|
if len(flags) > 0 {
|
|
|
|
name += "_" + strings.Join(flags, "_")
|
|
|
|
}
|
|
|
|
target, ok := testprog.target[name]
|
2015-12-21 08:29:21 -07:00
|
|
|
if ok {
|
|
|
|
return target.exe, target.err
|
2014-10-28 19:53:09 -06:00
|
|
|
}
|
|
|
|
|
2016-05-27 17:03:44 -06:00
|
|
|
exe := filepath.Join(testprog.dir, name+".exe")
|
2016-08-30 12:08:47 -06:00
|
|
|
cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
|
2015-12-21 08:29:21 -07:00
|
|
|
cmd.Dir = "testdata/" + binary
|
2017-09-13 07:04:16 -06:00
|
|
|
out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
|
2014-10-28 19:53:09 -06:00
|
|
|
if err != nil {
|
2016-05-27 17:03:44 -06:00
|
|
|
target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
|
|
|
|
testprog.target[name] = target
|
2016-01-11 10:34:38 -07:00
|
|
|
return "", target.err
|
2014-10-28 19:53:09 -06:00
|
|
|
}
|
2015-12-21 08:29:21 -07:00
|
|
|
target.exe = exe
|
2016-05-27 17:03:44 -06:00
|
|
|
testprog.target[name] = target
|
2015-12-21 08:29:21 -07:00
|
|
|
return exe, nil
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
|
2015-01-05 14:14:08 -07:00
|
|
|
var (
|
|
|
|
staleRuntimeOnce sync.Once // guards init of staleRuntimeErr
|
|
|
|
staleRuntimeErr error
|
|
|
|
)
|
|
|
|
|
2013-02-20 01:15:02 -07:00
|
|
|
func checkStaleRuntime(t *testing.T) {
|
2015-01-05 14:14:08 -07:00
|
|
|
staleRuntimeOnce.Do(func() {
|
|
|
|
// 'go run' uses the installed copy of runtime.a, which may be out of date.
|
2017-09-13 07:04:16 -06:00
|
|
|
out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput()
|
2015-01-05 14:14:08 -07:00
|
|
|
if err != nil {
|
|
|
|
staleRuntimeErr = fmt.Errorf("failed to execute 'go list': %v\n%v", err, string(out))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if string(out) != "false\n" {
|
2017-02-13 12:46:48 -07:00
|
|
|
t.Logf("go list -f {{.Stale}} runtime:\n%s", out)
|
2017-09-13 07:04:16 -06:00
|
|
|
out, err := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "list", "-f", "{{.StaleReason}}", "runtime")).CombinedOutput()
|
2017-02-13 16:30:42 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Logf("go list -f {{.StaleReason}} failed: %v", err)
|
|
|
|
}
|
|
|
|
t.Logf("go list -f {{.StaleReason}} runtime:\n%s", out)
|
2015-01-05 14:14:08 -07:00
|
|
|
staleRuntimeErr = fmt.Errorf("Stale runtime.a. Run 'go install runtime'.")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if staleRuntimeErr != nil {
|
|
|
|
t.Fatal(staleRuntimeErr)
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func testCrashHandler(t *testing.T, cgo bool) {
|
|
|
|
type crashTest struct {
|
|
|
|
Cgo bool
|
|
|
|
}
|
2015-12-21 08:29:21 -07:00
|
|
|
var output string
|
|
|
|
if cgo {
|
|
|
|
output = runTestProg(t, "testprogcgo", "Crash")
|
|
|
|
} else {
|
|
|
|
output = runTestProg(t, "testprog", "Crash")
|
|
|
|
}
|
2012-05-29 23:10:54 -06:00
|
|
|
want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
|
2013-08-15 20:34:06 -06:00
|
|
|
if output != want {
|
|
|
|
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCrashHandler(t *testing.T) {
|
2013-02-20 01:15:02 -07:00
|
|
|
testCrashHandler(t, false)
|
|
|
|
}
|
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
func testDeadlock(t *testing.T, name string) {
|
|
|
|
output := runTestProg(t, "testprog", name)
|
2013-02-20 01:15:02 -07:00
|
|
|
want := "fatal error: all goroutines are asleep - deadlock!\n"
|
2013-08-15 20:34:06 -06:00
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSimpleDeadlock(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
testDeadlock(t, "SimpleDeadlock")
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestInitDeadlock(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
testDeadlock(t, "InitDeadlock")
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestLockedDeadlock(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
testDeadlock(t, "LockedDeadlock")
|
2013-02-20 01:15:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestLockedDeadlock2(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
testDeadlock(t, "LockedDeadlock2")
|
2012-05-29 23:10:54 -06:00
|
|
|
}
|
|
|
|
|
2013-03-05 00:40:17 -07:00
|
|
|
func TestGoexitDeadlock(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "GoexitDeadlock")
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 11:12:18 -06:00
|
|
|
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)
|
2013-08-15 20:34:06 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStackOverflow(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "StackOverflow")
|
|
|
|
want := "runtime: goroutine stack exceeds 1474560-byte limit\nfatal error: stack overflow"
|
2013-08-15 20:34:06 -06:00
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
2013-03-05 00:40:17 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-16 20:25:26 -06:00
|
|
|
func TestThreadExhaustion(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "ThreadExhaustion")
|
2013-08-16 20:25:26 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-07 09:50:30 -07:00
|
|
|
func TestRecursivePanic(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "RecursivePanic")
|
2014-03-07 09:50:30 -07:00
|
|
|
want := `wrap: bad
|
|
|
|
panic: again
|
|
|
|
|
|
|
|
`
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 11:12:18 -06:00
|
|
|
func TestGoexitCrash(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "GoexitExit")
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 11:12:18 -06:00
|
|
|
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)
|
2014-04-15 09:48:17 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-15 16:09:17 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-05-27 22:00:01 -06:00
|
|
|
func TestGoNil(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "GoNil")
|
2014-05-27 22:00:01 -06:00
|
|
|
want := "go of nil func value"
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-21 08:29:21 -07:00
|
|
|
func TestMainGoroutineID(t *testing.T) {
|
|
|
|
output := runTestProg(t, "testprog", "MainGoroutineID")
|
2014-07-16 02:19:33 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-07 05:31:18 -07:00
|
|
|
func TestNoHelperGoroutines(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "NoHelperGoroutines")
|
2015-02-07 05:31:18 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-11 10:08:30 -06:00
|
|
|
func TestBreakpoint(t *testing.T) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "Breakpoint")
|
2017-04-19 10:04:18 -06:00
|
|
|
// If runtime.Breakpoint() is inlined, then the stack trace prints
|
|
|
|
// "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()".
|
|
|
|
want := "runtime.Breakpoint("
|
2014-09-11 10:08:30 -06:00
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-19 17:33:14 -06:00
|
|
|
func TestGoexitInPanic(t *testing.T) {
|
|
|
|
// see issue 8774: this code used to trigger an infinite recursion
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "GoexitInPanic")
|
2014-09-19 17:33:14 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-27 18:29:53 -06:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-09-19 17:33:14 -06:00
|
|
|
func TestPanicAfterGoexit(t *testing.T) {
|
|
|
|
// an uncaught panic should still work after goexit
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "PanicAfterGoexit")
|
2014-09-19 17:33:14 -06:00
|
|
|
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) {
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
|
2014-09-19 17:33:14 -06:00
|
|
|
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
|
2016-03-01 16:21:55 -07:00
|
|
|
// Goexit should run the #2 defer. Its panic
|
2014-09-19 17:33:14 -06:00
|
|
|
// should be caught by the #1 defer, and execution
|
2016-03-01 16:21:55 -07:00
|
|
|
// should resume in the caller. Like the Goexit
|
2014-09-19 17:33:14 -06:00
|
|
|
// never happened!
|
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
panic("bad recover")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
defer func() {
|
|
|
|
panic("hello")
|
|
|
|
}()
|
|
|
|
runtime.Goexit()
|
|
|
|
}
|
2015-01-13 10:12:50 -07:00
|
|
|
|
|
|
|
func TestNetpollDeadlock(t *testing.T) {
|
2016-11-03 23:28:01 -06:00
|
|
|
t.Parallel()
|
2015-12-21 08:29:21 -07:00
|
|
|
output := runTestProg(t, "testprognet", "NetpollDeadlock")
|
2015-01-13 10:12:50 -07:00
|
|
|
want := "done\n"
|
|
|
|
if !strings.HasSuffix(output, want) {
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
}
|
|
|
|
}
|
2016-02-12 08:33:51 -07:00
|
|
|
|
|
|
|
func TestPanicTraceback(t *testing.T) {
|
2016-11-03 23:28:01 -06:00
|
|
|
t.Parallel()
|
2016-02-12 08:33:51 -07:00
|
|
|
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.
|
runtime: do not print runtime panic frame at top of user stack
The expected default behavior (no explicit GOTRACEBACK setting)
is for the stack trace to start in user code, eliding unnecessary runtime
frames that led up to the actual trace printing code. The idea was that
the first line number printed was the one that crashed.
For #5832 we added code to show 'panic' frames so that if code panics
and then starts running defers and then we trace from there, the panic
frame can help explain why the code seems to have made a call not
present in the code. But that's only needed for panics between two different
call frames, not the panic at the very top of the stack trace.
Fix the fix to again elide the runtime code at the very top of the stack trace.
Simple panic:
package main
func main() {
var x []int
println(x[1])
}
Before this CL:
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x1056980, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:531 +0x1cf
main.main()
/tmp/x.go:5 +0x5
After this CL:
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
/tmp/x.go:5 +0x5
Panic inside defer triggered by panic:
package main
func main() {
var x []int
defer func() {
println(x[1])
}()
println(x[2])
}
Before this CL:
panic: runtime error: index out of range
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x1056aa0, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:531 +0x1cf
main.main.func1(0x0, 0x0, 0x0)
/tmp/y.go:6 +0x62
panic(0x1056aa0, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:489 +0x2cf
main.main()
/tmp/y.go:8 +0x59
The middle panic is important: it explains why main.main ended up calling main.main.func1 on a line that looks like a call to println. The top panic is noise.
After this CL:
panic: runtime error: index out of range
panic: runtime error: index out of range
goroutine 1 [running]:
main.main.func1(0x0, 0x0, 0x0)
/tmp/y.go:6 +0x62
panic(0x1056ac0, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:489 +0x2cf
main.main()
/tmp/y.go:8 +0x59
Fixes #17901.
Change-Id: Id6d7c76373f7a658a537a39ca32b7dc23e1e76aa
Reviewed-on: https://go-review.googlesource.com/33165
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2016-11-12 21:01:37 -07:00
|
|
|
fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
|
2016-02-12 08:33:51 -07:00
|
|
|
for _, fn := range fns {
|
|
|
|
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]:]
|
|
|
|
}
|
|
|
|
}
|
2016-02-21 11:56:08 -07:00
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
2016-06-20 15:00:58 -06:00
|
|
|
|
2016-10-04 22:15:42 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-20 15:00:58 -06:00
|
|
|
func TestMemPprof(t *testing.T) {
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
|
|
|
exe, err := buildTestProg(t, "testprog")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-09-13 07:04:16 -06:00
|
|
|
got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput()
|
2016-06-20 15:00:58 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
fn := strings.TrimSpace(string(got))
|
|
|
|
defer os.Remove(fn)
|
|
|
|
|
2017-02-17 13:27:12 -07:00
|
|
|
for try := 0; try < 2; try++ {
|
2017-09-13 07:04:16 -06:00
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
|
2017-02-17 13:27:12 -07:00
|
|
|
// 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())
|
2016-06-20 15:00:58 -06:00
|
|
|
}
|
|
|
|
|
2017-02-17 13:27:12 -07:00
|
|
|
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")
|
|
|
|
}
|
2016-06-20 15:00:58 -06:00
|
|
|
}
|
|
|
|
}
|
2016-07-06 16:02:49 -06:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2017-02-17 14:08:36 -07:00
|
|
|
|
|
|
|
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]
|
|
|
|
if !bytes.Contains(buf, []byte("(*point).negate(")) {
|
|
|
|
t.Fatalf("expecting stack trace to contain call to (*point).negate()")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
pt := new(point)
|
|
|
|
pt.negate()
|
|
|
|
}
|
2017-04-19 08:32:34 -06:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2017-06-06 18:31:57 -06:00
|
|
|
// 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++ {
|
2017-09-13 07:04:16 -06:00
|
|
|
got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
|
2017-06-06 18:31:57 -06:00
|
|
|
if err == nil {
|
|
|
|
t.Logf("try %d: program exited successfully, should have failed", i+1)
|
|
|
|
continue
|
|
|
|
}
|
2017-04-19 08:32:34 -06:00
|
|
|
|
2017-06-06 18:31:57 -06:00
|
|
|
if i > 0 {
|
|
|
|
t.Logf("try %d:\n", i+1)
|
|
|
|
}
|
|
|
|
t.Logf("%s\n", got)
|
2017-04-19 08:32:34 -06:00
|
|
|
|
2017-06-06 18:31:57 -06:00
|
|
|
wants := []string{
|
|
|
|
"panic: crash",
|
|
|
|
"PanicRace",
|
|
|
|
"created by ",
|
|
|
|
}
|
|
|
|
for _, want := range wants {
|
|
|
|
if !bytes.Contains(got, []byte(want)) {
|
|
|
|
t.Logf("did not find expected string %q", want)
|
|
|
|
continue retry
|
|
|
|
}
|
2017-04-19 08:32:34 -06:00
|
|
|
}
|
2017-06-06 18:31:57 -06:00
|
|
|
|
|
|
|
// Test generated expected output.
|
|
|
|
return
|
2017-04-19 08:32:34 -06:00
|
|
|
}
|
2017-06-06 18:31:57 -06:00
|
|
|
t.Errorf("test ran %d times without producing expected output", tries)
|
2017-04-19 08:32:34 -06:00
|
|
|
}
|