|
|
package stack_test
import ( "fmt" "io/ioutil" "path" "path/filepath" "reflect" "runtime" "strings" "testing"
"github.com/go-stack/stack" )
func TestCaller(t *testing.T) { t.Parallel()
c := stack.Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") }
if got, want := c.Frame().File, file; got != want { t.Errorf("got file == %v, want file == %v", got, want) }
if got, want := c.Frame().Line, line; got != want { t.Errorf("got line == %v, want line == %v", got, want) } }
func f3(f1 func() stack.Call) stack.Call { return f2(f1) }
func f2(f1 func() stack.Call) stack.Call { return f1() }
func TestCallerMidstackInlined(t *testing.T) { t.Parallel()
_, _, line, ok := runtime.Caller(0) line -= 10 // adjust to return f1() line inside f2()
if !ok { t.Fatal("runtime.Caller(0) failed") }
c := f3(func() stack.Call { return stack.Caller(2) })
if got, want := c.Frame().Line, line; got != want { t.Errorf("got line == %v, want line == %v", got, want) } if got, want := c.Frame().Function, "github.com/go-stack/stack_test.f3"; got != want { t.Errorf("got func name == %v, want func name == %v", got, want) } }
func TestCallerPanic(t *testing.T) { t.Parallel()
var ( line int ok bool )
defer func() { if recover() != nil { var pcs [32]uintptr n := runtime.Callers(1, pcs[:]) frames := runtime.CallersFrames(pcs[:n]) // count frames to runtime.sigpanic
panicIdx := 0 for { f, more := frames.Next() if f.Function == "runtime.sigpanic" { break } panicIdx++ if !more { t.Fatal("no runtime.sigpanic entry on the stack") } } c := stack.Caller(panicIdx) if got, want := c.Frame().Function, "runtime.sigpanic"; got != want { t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want) } c1 := stack.Caller(panicIdx + 1) if got, want := c1.Frame().Function, "github.com/go-stack/stack_test.TestCallerPanic"; got != want { t.Errorf("TestCallerPanic frame: got name == %v, want name == %v", got, want) } if got, want := c1.Frame().Line, line; got != want { t.Errorf("TestCallerPanic frame: got line == %v, want line == %v", got, want) } } }()
_, _, line, ok = runtime.Caller(0) line += 7 // adjust to match line of panic below
if !ok { t.Fatal("runtime.Caller(0) failed") } // Initiate a sigpanic.
var x *uintptr _ = *x }
type tholder struct { trace func() stack.CallStack }
func (th *tholder) traceLabyrinth() stack.CallStack { for { return th.trace() } }
func TestTrace(t *testing.T) { t.Parallel()
_, _, line, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller(0) failed") }
fh := tholder{ trace: func() stack.CallStack { cs := stack.Trace() return cs }, }
cs := fh.traceLabyrinth()
lines := []int{line + 7, line - 7, line + 12}
for i, line := range lines { if got, want := cs[i].Frame().Line, line; got != want { t.Errorf("got line[%d] == %v, want line[%d] == %v", i, got, i, want) } } }
// Test stack handling originating from a sigpanic.
func TestTracePanic(t *testing.T) { t.Parallel()
var ( line int ok bool )
defer func() { if recover() != nil { trace := stack.Trace()
// find runtime.sigpanic
panicIdx := -1 for i, c := range trace { if c.Frame().Function == "runtime.sigpanic" { panicIdx = i break } } if panicIdx == -1 { t.Fatal("no runtime.sigpanic entry on the stack") } if got, want := trace[panicIdx].Frame().Function, "runtime.sigpanic"; got != want { t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want) } if got, want := trace[panicIdx+1].Frame().Function, "github.com/go-stack/stack_test.TestTracePanic"; got != want { t.Errorf("TestTracePanic frame: got name == %v, want name == %v", got, want) } if got, want := trace[panicIdx+1].Frame().Line, line; got != want { t.Errorf("TestTracePanic frame: got line == %v, want line == %v", got, want) } } }()
_, _, line, ok = runtime.Caller(0) line += 7 // adjust to match line of panic below
if !ok { t.Fatal("runtime.Caller(0) failed") } // Initiate a sigpanic.
var x *uintptr _ = *x }
const importPath = "github.com/go-stack/stack"
type testType struct{}
func (tt testType) testMethod() (c stack.Call, file string, line int, ok bool) { c = stack.Caller(0) _, file, line, ok = runtime.Caller(0) line-- return }
func TestCallFormat(t *testing.T) { t.Parallel()
c := stack.Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } relFile := path.Join(importPath, filepath.Base(file))
c2, file2, line2, ok2 := testType{}.testMethod() if !ok2 { t.Fatal("runtime.Caller(0) failed") } relFile2 := path.Join(importPath, filepath.Base(file2))
data := []struct { c stack.Call desc string fmt string out string }{ {stack.Call{}, "error", "%s", "%!s(NOFUNC)"},
{c, "func", "%s", path.Base(file)}, {c, "func", "%+s", relFile}, {c, "func", "%#s", file}, {c, "func", "%d", fmt.Sprint(line)}, {c, "func", "%n", "TestCallFormat"}, {c, "func", "%+n", "github.com/go-stack/stack_test.TestCallFormat"}, {c, "func", "%k", "stack_test"}, {c, "func", "%+k", "github.com/go-stack/stack_test"}, {c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)}, {c, "func", "%+v", fmt.Sprint(relFile, ":", line)}, {c, "func", "%#v", fmt.Sprint(file, ":", line)},
{c2, "meth", "%s", path.Base(file2)}, {c2, "meth", "%+s", relFile2}, {c2, "meth", "%#s", file2}, {c2, "meth", "%d", fmt.Sprint(line2)}, {c2, "meth", "%n", "testType.testMethod"}, {c2, "meth", "%+n", "github.com/go-stack/stack_test.testType.testMethod"}, {c2, "meth", "%k", "stack_test"}, {c2, "meth", "%+k", "github.com/go-stack/stack_test"}, {c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)}, {c2, "meth", "%+v", fmt.Sprint(relFile2, ":", line2)}, {c2, "meth", "%#v", fmt.Sprint(file2, ":", line2)}, }
for _, d := range data { got := fmt.Sprintf(d.fmt, d.c) if got != d.out { t.Errorf("fmt.Sprintf(%q, Call(%s)) = %s, want %s", d.fmt, d.desc, got, d.out) } } }
func TestCallString(t *testing.T) { t.Parallel()
c := stack.Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") }
c2, file2, line2, ok2 := testType{}.testMethod() if !ok2 { t.Fatal("runtime.Caller(0) failed") }
data := []struct { c stack.Call desc string out string }{ {stack.Call{}, "error", "%!v(NOFUNC)"}, {c, "func", fmt.Sprint(path.Base(file), ":", line)}, {c2, "meth", fmt.Sprint(path.Base(file2), ":", line2)}, }
for _, d := range data { got := d.c.String() if got != d.out { t.Errorf("got %s, want %s", got, d.out) } } }
func TestCallMarshalText(t *testing.T) { t.Parallel()
c := stack.Caller(0) _, file, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") }
c2, file2, line2, ok2 := testType{}.testMethod() if !ok2 { t.Fatal("runtime.Caller(0) failed") }
data := []struct { c stack.Call desc string out []byte err error }{ {stack.Call{}, "error", nil, stack.ErrNoFunc}, {c, "func", []byte(fmt.Sprint(path.Base(file), ":", line)), nil}, {c2, "meth", []byte(fmt.Sprint(path.Base(file2), ":", line2)), nil}, }
for _, d := range data { text, err := d.c.MarshalText() if got, want := err, d.err; got != want { t.Errorf("%s: got err %v, want err %v", d.desc, got, want) } if got, want := text, d.out; !reflect.DeepEqual(got, want) { t.Errorf("%s: got %s, want %s", d.desc, got, want) } } }
func TestCallStackString(t *testing.T) { cs, line0 := getTrace(t) _, file, line1, ok := runtime.Caller(0) line1-- if !ok { t.Fatal("runtime.Caller(0) failed") } file = path.Base(file) if got, want := cs.String(), fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1); got != want { t.Errorf("\n got %v\nwant %v", got, want) } }
func TestCallStackMarshalText(t *testing.T) { cs, line0 := getTrace(t) _, file, line1, ok := runtime.Caller(0) line1-- if !ok { t.Fatal("runtime.Caller(0) failed") } file = path.Base(file) text, _ := cs.MarshalText() if got, want := text, []byte(fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1)); !reflect.DeepEqual(got, want) { t.Errorf("\n got %v\nwant %v", got, want) } }
func getTrace(t *testing.T) (stack.CallStack, int) { cs := stack.Trace().TrimRuntime() _, _, line, ok := runtime.Caller(0) line-- if !ok { t.Fatal("runtime.Caller(0) failed") } return cs, line }
func TestTrimAbove(t *testing.T) { trace := trimAbove() if got, want := len(trace), 2; got != want { t.Fatalf("got len(trace) == %v, want %v, trace: %n", got, want, trace) } if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want { t.Errorf("got %q, want %q", got, want) } }
func trimAbove() stack.CallStack { call := stack.Caller(1) trace := stack.Trace() return trace.TrimAbove(call) }
func TestTrimBelow(t *testing.T) { trace := trimBelow() if got, want := fmt.Sprintf("%n", trace[0]), "TestTrimBelow"; got != want { t.Errorf("got %q, want %q", got, want) } }
func trimBelow() stack.CallStack { call := stack.Caller(1) trace := stack.Trace() return trace.TrimBelow(call) }
func TestTrimRuntime(t *testing.T) { trace := stack.Trace().TrimRuntime() if got, want := len(trace), 1; got != want { t.Errorf("got len(trace) == %v, want %v, goroot: %q, trace: %#v", got, want, runtime.GOROOT(), trace) } }
func BenchmarkCallVFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprint(ioutil.Discard, c) } }
func BenchmarkCallPlusVFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%+v", c) } }
func BenchmarkCallSharpVFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%#v", c) } }
func BenchmarkCallSFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%s", c) } }
func BenchmarkCallPlusSFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%+s", c) } }
func BenchmarkCallSharpSFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%#s", c) } }
func BenchmarkCallDFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%d", c) } }
func BenchmarkCallNFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%n", c) } }
func BenchmarkCallPlusNFmt(b *testing.B) { c := stack.Caller(0) b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Fprintf(ioutil.Discard, "%+n", c) } }
func BenchmarkCaller(b *testing.B) { for i := 0; i < b.N; i++ { stack.Caller(0) } }
func BenchmarkTrace(b *testing.B) { for i := 0; i < b.N; i++ { stack.Trace() } }
func deepStack(depth int, b *testing.B) stack.CallStack { if depth > 0 { return deepStack(depth-1, b) } b.StartTimer() s := stack.Trace() return s }
func BenchmarkTrace10(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() deepStack(10, b) } }
func BenchmarkTrace50(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { deepStack(50, b) } }
func BenchmarkTrace100(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { deepStack(100, b) } }
////////////////
// Benchmark functions followed by formatting
////////////////
func BenchmarkCallerAndVFmt(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Fprint(ioutil.Discard, stack.Caller(0)) } }
func BenchmarkTraceAndVFmt(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Fprint(ioutil.Discard, stack.Trace()) } }
func BenchmarkTrace10AndVFmt(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() fmt.Fprint(ioutil.Discard, deepStack(10, b)) } }
////////////////
// Baseline against package runtime.
////////////////
func BenchmarkRuntimeCaller(b *testing.B) { for i := 0; i < b.N; i++ { runtime.Caller(0) } }
func BenchmarkRuntimeCallerAndFmt(b *testing.B) { for i := 0; i < b.N; i++ { _, file, line, _ := runtime.Caller(0) const sep = "/" if i := strings.LastIndex(file, sep); i != -1 { file = file[i+len(sep):] } fmt.Fprint(ioutil.Discard, file, ":", line) } }
func BenchmarkFuncForPC(b *testing.B) { pc, _, _, _ := runtime.Caller(0) pc-- b.ResetTimer() for i := 0; i < b.N; i++ { runtime.FuncForPC(pc) } }
func BenchmarkFuncFileLine(b *testing.B) { pc, _, _, _ := runtime.Caller(0) pc-- fn := runtime.FuncForPC(pc) b.ResetTimer() for i := 0; i < b.N; i++ { fn.FileLine(pc) } }
|