blob: 7f782ca930d70177a90ff7eb84d9639044cbcaad [file] [log] [blame]
// stackdump_test checks that the heuristics the stackdump package applies to
// prune frames work as expected in production Go compilers.
package stackdump_test
import (
"bytes"
"fmt"
"regexp"
"runtime"
"testing"
"github.com/golang/glog/internal/stackdump"
)
var file string
func init() {
_, file, _, _ = runtime.Caller(0)
}
func TestCallerText(t *testing.T) {
stack := stackdump.CallerText(0)
_, _, line, _ := runtime.Caller(0)
line--
wantRE := regexp.MustCompile(fmt.Sprintf(
`^goroutine \d+ \[running\]:
github.com/golang/glog/internal/stackdump_test\.TestCallerText(\([^)]*\))?
%v:%v.*
`, file, line))
if !wantRE.Match(stack) {
t.Errorf("Stack dump:\n%s\nwant matching regexp:\n%s", stack, wantRE.String())
buf := make([]byte, len(stack)*2)
origStack := buf[:runtime.Stack(buf, false)]
t.Logf("Unpruned stack:\n%s", origStack)
}
}
func callerAt(calls int, depth int) (stack []byte) {
if calls == 1 {
return stackdump.CallerText(depth)
}
return callerAt(calls-1, depth)
}
func TestCallerTextSkip(t *testing.T) {
const calls = 3
cases := []struct {
depth int
callerAtFrames int
wantEndOfStack bool
}{
{depth: 0, callerAtFrames: calls},
{depth: calls - 1, callerAtFrames: 1},
{depth: calls, callerAtFrames: 0},
{depth: calls + 1, callerAtFrames: 0},
{depth: calls + 100, wantEndOfStack: true},
}
for _, tc := range cases {
stack := callerAt(calls, tc.depth)
wantREBuf := bytes.NewBuffer(nil)
fmt.Fprintf(wantREBuf, `^goroutine \d+ \[running\]:
`)
if tc.wantEndOfStack {
fmt.Fprintf(wantREBuf, "\n|$")
} else {
for n := tc.callerAtFrames; n > 0; n-- {
fmt.Fprintf(wantREBuf, `github.com/golang/glog/internal/stackdump_test\.callerAt(\([^)]*\))?
%v:\d+.*
`, file)
}
if tc.depth <= calls {
fmt.Fprintf(wantREBuf, `github.com/golang/glog/internal/stackdump_test\.TestCallerTextSkip(\([^)]*\))?
%v:\d+.*
`, file)
}
}
wantRE := regexp.MustCompile(wantREBuf.String())
if !wantRE.Match(stack) {
t.Errorf("for %v calls, stackdump.CallerText(%v) =\n%s\n\nwant matching regexp:\n%s", calls, tc.depth, stack, wantRE.String())
}
}
}
func pcAt(calls int, depth int) (stack []uintptr) {
if calls == 1 {
return stackdump.CallerPC(depth)
}
stack = pcAt(calls-1, depth)
runtime.Gosched() // Thwart tail-call optimization.
return stack
}
func TestCallerPC(t *testing.T) {
const calls = 3
cases := []struct {
depth int
pcAtFrames int
wantEndOfStack bool
}{
{depth: 0, pcAtFrames: calls},
{depth: calls - 1, pcAtFrames: 1},
{depth: calls, pcAtFrames: 0},
{depth: calls + 1, pcAtFrames: 0},
{depth: calls + 100, wantEndOfStack: true},
}
for _, tc := range cases {
stack := pcAt(calls, tc.depth)
if tc.wantEndOfStack {
if len(stack) != 0 {
t.Errorf("for %v calls, stackdump.CallerPC(%v) =\n%q\nwant []", calls, tc.depth, stack)
}
continue
}
wantFuncs := []string{}
for n := tc.pcAtFrames; n > 0; n-- {
wantFuncs = append(wantFuncs, `github.com/golang/glog/internal/stackdump_test\.pcAt$`)
}
if tc.depth <= calls {
wantFuncs = append(wantFuncs, `^github.com/golang/glog/internal/stackdump_test\.TestCallerPC$`)
}
gotFuncs := []string{}
for _, pc := range stack {
gotFuncs = append(gotFuncs, runtime.FuncForPC(pc).Name())
}
if len(gotFuncs) > len(wantFuncs) {
gotFuncs = gotFuncs[:len(wantFuncs)]
}
ok := true
for i, want := range wantFuncs {
re := regexp.MustCompile(want)
if i >= len(gotFuncs) || !re.MatchString(gotFuncs[i]) {
ok = false
break
}
}
if !ok {
t.Errorf("for %v calls, stackdump.CallerPC(%v) =\n%q\nwant %q", calls, tc.depth, gotFuncs, wantFuncs)
}
}
}