internal/stack: helper package for querying stack frames

Intended for #5. Also replaces a helper function in the `printer`
package.
This commit is contained in:
Michael McLoughlin
2019-01-04 00:45:01 -08:00
parent 52b6e2c03b
commit 301d0c137a
3 changed files with 109 additions and 16 deletions

72
internal/stack/stack.go Normal file
View File

@@ -0,0 +1,72 @@
package stack
import (
"path"
"runtime"
"strings"
)
// Frames returns at most max callstack Frames, starting with its caller and
// skipping skip Frames.
func Frames(skip, max int) []runtime.Frame {
pc := make([]uintptr, max)
n := runtime.Callers(skip+2, pc)
if n == 0 {
return nil
}
pc = pc[:n]
frames := runtime.CallersFrames(pc)
var fs []runtime.Frame
for {
f, more := frames.Next()
fs = append(fs, f)
if !more {
break
}
}
return fs
}
// Match returns the first stack frame for which the predicate function returns
// true. Returns nil if no match is found. Starts matching after skip frames,
// starting with its caller.
func Match(skip int, predicate func(runtime.Frame) bool) *runtime.Frame {
i, n := skip+1, 16
for {
fs := Frames(i, n)
for _, f := range fs {
if predicate(f) {
return &f
}
}
if len(fs) < n {
break
}
i += n
}
return nil
}
// Main returns the main() function Frame.
func Main() *runtime.Frame {
return Match(1, func(f runtime.Frame) bool {
return f.Function == "main.main"
})
}
// ExternalCaller returns the first frame outside the callers package.
func ExternalCaller() *runtime.Frame {
var first *runtime.Frame
return Match(1, func(f runtime.Frame) bool {
if first == nil {
first = &f
}
return pkg(first.Function) != pkg(f.Function)
})
}
func pkg(ident string) string {
dir, name := path.Split(ident)
parts := strings.Split(name, ".")
return dir + parts[0]
}

View File

@@ -0,0 +1,34 @@
package stack_test
import (
"runtime"
"testing"
"github.com/mmcloughlin/avo/internal/stack"
)
const pkg = "github.com/mmcloughlin/avo/internal/stack_test"
func TestFramesFirst(t *testing.T) {
fs := stack.Frames(0, 1)
if len(fs) == 0 {
t.Fatalf("empty slice")
}
got := fs[0].Function
expect := pkg + ".TestFramesFirst"
if got != expect {
t.Fatalf("bad function name %s; expect %s", got, expect)
}
}
func TestMatchFirst(t *testing.T) {
first := stack.Match(0, func(_ runtime.Frame) bool { return true })
if first == nil {
t.Fatalf("nil match")
}
got := first.Function
expect := pkg + ".TestMatchFirst"
if got != expect {
t.Fatalf("bad function name %s; expect %s", got, expect)
}
}