diff options
| author | Russ Cox <rsc@golang.org> | 2017-12-06 00:35:28 -0500 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2017-12-06 01:03:36 -0500 |
| commit | 185e6094fd968b35b80e56aad1286c66bb2cc261 (patch) | |
| tree | 411babe570d6faa1e99251a9167123afd07407d2 /src/runtime/stack_test.go | |
| parent | c36033a379a4907fb75309416ffcf2904e613ab9 (diff) | |
| parent | a032f74bf0b40a94669159e7d7e96722eb76199b (diff) | |
| download | go-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.go | 192 |
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()) + } +} |
