aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/stack_test.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2017-12-06 00:35:28 -0500
committerRuss Cox <rsc@golang.org>2017-12-06 01:03:36 -0500
commit185e6094fd968b35b80e56aad1286c66bb2cc261 (patch)
tree411babe570d6faa1e99251a9167123afd07407d2 /src/runtime/stack_test.go
parentc36033a379a4907fb75309416ffcf2904e613ab9 (diff)
parenta032f74bf0b40a94669159e7d7e96722eb76199b (diff)
downloadgo-185e6094fd968b35b80e56aad1286c66bb2cc261.tar.xz
[dev.boringcrypto] all: merge master (nearly Go 1.10 beta 1) into dev.boringcrypto
This is a git merge of master into dev.boringcrypto. The branch was previously based on release-branch.go1.9, so there are a handful of spurious conflicts that would also arise if trying to merge master into release-branch.go1.9 (which we never do). Those have all been resolved by taking the original file from master, discarding any Go 1.9-specific edits. all.bash passes on darwin/amd64, which is to say without actually using BoringCrypto. Go 1.10-related fixes to BoringCrypto itself will be in a followup CL. This CL is just the merge. Change-Id: I4c97711fec0fb86761913dcde28d25c001246c35
Diffstat (limited to 'src/runtime/stack_test.go')
-rw-r--r--src/runtime/stack_test.go192
1 files changed, 186 insertions, 6 deletions
diff --git a/src/runtime/stack_test.go b/src/runtime/stack_test.go
index 25e8f77da4..0fed241704 100644
--- a/src/runtime/stack_test.go
+++ b/src/runtime/stack_test.go
@@ -5,6 +5,9 @@
package runtime_test
import (
+ "bytes"
+ "fmt"
+ "reflect"
. "runtime"
"strings"
"sync"
@@ -78,10 +81,13 @@ func TestStackGrowth(t *testing.T) {
var wg sync.WaitGroup
// in a normal goroutine
+ var growDuration time.Duration // For debugging failures
wg.Add(1)
go func() {
defer wg.Done()
- growStack()
+ start := time.Now()
+ growStack(nil)
+ growDuration = time.Since(start)
}()
wg.Wait()
@@ -90,7 +96,7 @@ func TestStackGrowth(t *testing.T) {
go func() {
defer wg.Done()
LockOSThread()
- growStack()
+ growStack(nil)
UnlockOSThread()
}()
wg.Wait()
@@ -100,12 +106,14 @@ func TestStackGrowth(t *testing.T) {
go func() {
defer wg.Done()
done := make(chan bool)
- var started uint32
+ var startTime time.Time
+ var started, progress uint32
go func() {
s := new(string)
SetFinalizer(s, func(ss *string) {
+ startTime = time.Now()
atomic.StoreUint32(&started, 1)
- growStack()
+ growStack(&progress)
done <- true
})
s = nil
@@ -118,7 +126,10 @@ func TestStackGrowth(t *testing.T) {
case <-time.After(20 * time.Second):
if atomic.LoadUint32(&started) == 0 {
t.Log("finalizer did not start")
+ } else {
+ t.Logf("finalizer started %s ago and finished %d iterations", time.Since(startTime), atomic.LoadUint32(&progress))
}
+ t.Log("first growStack took", growDuration)
t.Error("finalizer did not run")
return
}
@@ -131,7 +142,7 @@ func TestStackGrowth(t *testing.T) {
// growStack()
//}
-func growStack() {
+func growStack(progress *uint32) {
n := 1 << 10
if testing.Short() {
n = 1 << 8
@@ -142,6 +153,9 @@ func growStack() {
if x != i+1 {
panic("stack is corrupted")
}
+ if progress != nil {
+ atomic.StoreUint32(progress, uint32(i))
+ }
}
GC()
}
@@ -231,7 +245,7 @@ func TestDeferPtrs(t *testing.T) {
}
}()
defer set(&y, 42)
- growStack()
+ growStack(nil)
}
type bigBuf [4 * 1024]byte
@@ -627,3 +641,169 @@ func count23(n int) int {
}
return 1 + count1(n-1)
}
+
+type structWithMethod struct{}
+
+func (s structWithMethod) caller() string {
+ _, file, line, ok := Caller(1)
+ if !ok {
+ panic("Caller failed")
+ }
+ return fmt.Sprintf("%s:%d", file, line)
+}
+
+func (s structWithMethod) callers() []uintptr {
+ pc := make([]uintptr, 16)
+ return pc[:Callers(0, pc)]
+}
+
+func (s structWithMethod) stack() string {
+ buf := make([]byte, 4<<10)
+ return string(buf[:Stack(buf, false)])
+}
+
+func (s structWithMethod) nop() {}
+
+func TestStackWrapperCaller(t *testing.T) {
+ var d structWithMethod
+ // Force the compiler to construct a wrapper method.
+ wrapper := (*structWithMethod).caller
+ // Check that the wrapper doesn't affect the stack trace.
+ if dc, ic := d.caller(), wrapper(&d); dc != ic {
+ t.Fatalf("direct caller %q != indirect caller %q", dc, ic)
+ }
+}
+
+func TestStackWrapperCallers(t *testing.T) {
+ var d structWithMethod
+ wrapper := (*structWithMethod).callers
+ // Check that <autogenerated> doesn't appear in the stack trace.
+ pcs := wrapper(&d)
+ frames := CallersFrames(pcs)
+ for {
+ fr, more := frames.Next()
+ if fr.File == "<autogenerated>" {
+ t.Fatalf("<autogenerated> appears in stack trace: %+v", fr)
+ }
+ if !more {
+ break
+ }
+ }
+}
+
+func TestStackWrapperStack(t *testing.T) {
+ var d structWithMethod
+ wrapper := (*structWithMethod).stack
+ // Check that <autogenerated> doesn't appear in the stack trace.
+ stk := wrapper(&d)
+ if strings.Contains(stk, "<autogenerated>") {
+ t.Fatalf("<autogenerated> appears in stack trace:\n%s", stk)
+ }
+}
+
+type I interface {
+ M()
+}
+
+func TestStackWrapperStackPanic(t *testing.T) {
+ t.Run("sigpanic", func(t *testing.T) {
+ // nil calls to interface methods cause a sigpanic.
+ testStackWrapperPanic(t, func() { I.M(nil) }, "runtime_test.I.M")
+ })
+ t.Run("panicwrap", func(t *testing.T) {
+ // Nil calls to value method wrappers call panicwrap.
+ wrapper := (*structWithMethod).nop
+ testStackWrapperPanic(t, func() { wrapper(nil) }, "runtime_test.(*structWithMethod).nop")
+ })
+}
+
+func testStackWrapperPanic(t *testing.T, cb func(), expect string) {
+ // Test that the stack trace from a panicking wrapper includes
+ // the wrapper, even though elide these when they don't panic.
+ t.Run("CallersFrames", func(t *testing.T) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Fatalf("expected panic")
+ }
+ pcs := make([]uintptr, 10)
+ n := Callers(0, pcs)
+ frames := CallersFrames(pcs[:n])
+ for {
+ frame, more := frames.Next()
+ t.Log(frame.Function)
+ if frame.Function == expect {
+ return
+ }
+ if !more {
+ break
+ }
+ }
+ t.Fatalf("panicking wrapper %s missing from stack trace", expect)
+ }()
+ cb()
+ })
+ t.Run("Stack", func(t *testing.T) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Fatalf("expected panic")
+ }
+ buf := make([]byte, 4<<10)
+ stk := string(buf[:Stack(buf, false)])
+ if !strings.Contains(stk, "\n"+expect) {
+ t.Fatalf("panicking wrapper %s missing from stack trace:\n%s", expect, stk)
+ }
+ }()
+ cb()
+ })
+}
+
+func TestCallersFromWrapper(t *testing.T) {
+ // Test that invoking CallersFrames on a stack where the first
+ // PC is an autogenerated wrapper keeps the wrapper in the
+ // trace. Normally we elide these, assuming that the wrapper
+ // calls the thing you actually wanted to see, but in this
+ // case we need to keep it.
+ pc := reflect.ValueOf(I.M).Pointer()
+ frames := CallersFrames([]uintptr{pc})
+ frame, more := frames.Next()
+ if frame.Function != "runtime_test.I.M" {
+ t.Fatalf("want function %s, got %s", "runtime_test.I.M", frame.Function)
+ }
+ if more {
+ t.Fatalf("want 1 frame, got > 1")
+ }
+}
+
+func TestTracebackSystemstack(t *testing.T) {
+ if GOARCH == "ppc64" || GOARCH == "ppc64le" {
+ t.Skip("systemstack tail call not implemented on ppc64x")
+ }
+
+ // Test that profiles correctly jump over systemstack,
+ // including nested systemstack calls.
+ pcs := make([]uintptr, 20)
+ pcs = pcs[:TracebackSystemstack(pcs, 5)]
+ // Check that runtime.TracebackSystemstack appears five times
+ // and that we see TestTracebackSystemstack.
+ countIn, countOut := 0, 0
+ frames := CallersFrames(pcs)
+ var tb bytes.Buffer
+ for {
+ frame, more := frames.Next()
+ fmt.Fprintf(&tb, "\n%s+0x%x %s:%d", frame.Function, frame.PC-frame.Entry, frame.File, frame.Line)
+ switch frame.Function {
+ case "runtime.TracebackSystemstack":
+ countIn++
+ case "runtime_test.TestTracebackSystemstack":
+ countOut++
+ }
+ if !more {
+ break
+ }
+ }
+ if countIn != 5 || countOut != 1 {
+ t.Fatalf("expected 5 calls to TracebackSystemstack and 1 call to TestTracebackSystemstack, got:%s", tb.String())
+ }
+}