summaryrefslogtreecommitdiff
path: root/vendor/github.com/go-stack/stack
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/go-stack/stack')
-rw-r--r--vendor/github.com/go-stack/stack/LICENSE.md21
-rw-r--r--vendor/github.com/go-stack/stack/README.md38
-rw-r--r--vendor/github.com/go-stack/stack/format_test.go21
-rw-r--r--vendor/github.com/go-stack/stack/go.mod1
-rw-r--r--vendor/github.com/go-stack/stack/stack-go19_test.go67
-rw-r--r--vendor/github.com/go-stack/stack/stack.go400
-rw-r--r--vendor/github.com/go-stack/stack/stack_test.go582
7 files changed, 1130 insertions, 0 deletions
diff --git a/vendor/github.com/go-stack/stack/LICENSE.md b/vendor/github.com/go-stack/stack/LICENSE.md
new file mode 100644
index 0000000..2abf98e
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Chris Hines
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/go-stack/stack/README.md b/vendor/github.com/go-stack/stack/README.md
new file mode 100644
index 0000000..f11cccc
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/README.md
@@ -0,0 +1,38 @@
+[![GoDoc](https://godoc.org/github.com/go-stack/stack?status.svg)](https://godoc.org/github.com/go-stack/stack)
+[![Go Report Card](https://goreportcard.com/badge/go-stack/stack)](https://goreportcard.com/report/go-stack/stack)
+[![TravisCI](https://travis-ci.org/go-stack/stack.svg?branch=master)](https://travis-ci.org/go-stack/stack)
+[![Coverage Status](https://coveralls.io/repos/github/go-stack/stack/badge.svg?branch=master)](https://coveralls.io/github/go-stack/stack?branch=master)
+
+# stack
+
+Package stack implements utilities to capture, manipulate, and format call
+stacks. It provides a simpler API than package runtime.
+
+The implementation takes care of the minutia and special cases of interpreting
+the program counter (pc) values returned by runtime.Callers.
+
+## Versioning
+
+Package stack publishes releases via [semver](http://semver.org/) compatible Git
+tags prefixed with a single 'v'. The master branch always contains the latest
+release. The develop branch contains unreleased commits.
+
+## Formatting
+
+Package stack's types implement fmt.Formatter, which provides a simple and
+flexible way to declaratively configure formatting when used with logging or
+error tracking packages.
+
+```go
+func DoTheThing() {
+ c := stack.Caller(0)
+ log.Print(c) // "source.go:10"
+ log.Printf("%+v", c) // "pkg/path/source.go:10"
+ log.Printf("%n", c) // "DoTheThing"
+
+ s := stack.Trace().TrimRuntime()
+ log.Print(s) // "[source.go:15 caller.go:42 main.go:14]"
+}
+```
+
+See the docs for all of the supported formatting options.
diff --git a/vendor/github.com/go-stack/stack/format_test.go b/vendor/github.com/go-stack/stack/format_test.go
new file mode 100644
index 0000000..013ad67
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/format_test.go
@@ -0,0 +1,21 @@
+// +build go1.2
+
+package stack_test
+
+import (
+ "fmt"
+
+ "github.com/go-stack/stack"
+)
+
+func Example_callFormat() {
+ logCaller("%+s")
+ logCaller("%v %[1]n()")
+ // Output:
+ // github.com/go-stack/stack/format_test.go
+ // format_test.go:13 Example_callFormat()
+}
+
+func logCaller(format string) {
+ fmt.Printf(format+"\n", stack.Caller(1))
+}
diff --git a/vendor/github.com/go-stack/stack/go.mod b/vendor/github.com/go-stack/stack/go.mod
new file mode 100644
index 0000000..96a53a1
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/go.mod
@@ -0,0 +1 @@
+module github.com/go-stack/stack
diff --git a/vendor/github.com/go-stack/stack/stack-go19_test.go b/vendor/github.com/go-stack/stack/stack-go19_test.go
new file mode 100644
index 0000000..d7aeea2
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/stack-go19_test.go
@@ -0,0 +1,67 @@
+// +build go1.9
+
+package stack_test
+
+import (
+ "runtime"
+ "testing"
+
+ "github.com/go-stack/stack"
+)
+
+func TestCallerInlinedPanic(t *testing.T) {
+ t.Parallel()
+
+ var line int
+
+ 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.inlinablePanic"; got != want {
+ t.Errorf("TestCallerInlinedPanic frame: got name == %v, want name == %v", got, want)
+ }
+ if got, want := c1.Frame().Line, line; got != want {
+ t.Errorf("TestCallerInlinedPanic frame: got line == %v, want line == %v", got, want)
+ }
+ }
+ }()
+
+ doPanic(t, &line)
+ t.Fatal("failed to panic")
+}
+
+func doPanic(t *testing.T, panicLine *int) {
+ _, _, line, ok := runtime.Caller(0)
+ *panicLine = line + 11 // adjust to match line of panic below
+ if !ok {
+ t.Fatal("runtime.Caller(0) failed")
+ }
+ inlinablePanic()
+}
+
+func inlinablePanic() {
+ // Initiate a sigpanic.
+ var x *uintptr
+ _ = *x
+}
diff --git a/vendor/github.com/go-stack/stack/stack.go b/vendor/github.com/go-stack/stack/stack.go
new file mode 100644
index 0000000..ac3b93b
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/stack.go
@@ -0,0 +1,400 @@
+// +build go1.7
+
+// Package stack implements utilities to capture, manipulate, and format call
+// stacks. It provides a simpler API than package runtime.
+//
+// The implementation takes care of the minutia and special cases of
+// interpreting the program counter (pc) values returned by runtime.Callers.
+//
+// Package stack's types implement fmt.Formatter, which provides a simple and
+// flexible way to declaratively configure formatting when used with logging
+// or error tracking packages.
+package stack
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+// Call records a single function invocation from a goroutine stack.
+type Call struct {
+ frame runtime.Frame
+}
+
+// Caller returns a Call from the stack of the current goroutine. The argument
+// skip is the number of stack frames to ascend, with 0 identifying the
+// calling function.
+func Caller(skip int) Call {
+ // As of Go 1.9 we need room for up to three PC entries.
+ //
+ // 0. An entry for the stack frame prior to the target to check for
+ // special handling needed if that prior entry is runtime.sigpanic.
+ // 1. A possible second entry to hold metadata about skipped inlined
+ // functions. If inline functions were not skipped the target frame
+ // PC will be here.
+ // 2. A third entry for the target frame PC when the second entry
+ // is used for skipped inline functions.
+ var pcs [3]uintptr
+ n := runtime.Callers(skip+1, pcs[:])
+ frames := runtime.CallersFrames(pcs[:n])
+ frame, _ := frames.Next()
+ frame, _ = frames.Next()
+
+ return Call{
+ frame: frame,
+ }
+}
+
+// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
+func (c Call) String() string {
+ return fmt.Sprint(c)
+}
+
+// MarshalText implements encoding.TextMarshaler. It formats the Call the same
+// as fmt.Sprintf("%v", c).
+func (c Call) MarshalText() ([]byte, error) {
+ if c.frame == (runtime.Frame{}) {
+ return nil, ErrNoFunc
+ }
+
+ buf := bytes.Buffer{}
+ fmt.Fprint(&buf, c)
+ return buf.Bytes(), nil
+}
+
+// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
+// cause is a Call with the zero value.
+var ErrNoFunc = errors.New("no call stack information")
+
+// Format implements fmt.Formatter with support for the following verbs.
+//
+// %s source file
+// %d line number
+// %n function name
+// %k last segment of the package path
+// %v equivalent to %s:%d
+//
+// It accepts the '+' and '#' flags for most of the verbs as follows.
+//
+// %+s path of source file relative to the compile time GOPATH,
+// or the module path joined to the path of source file relative
+// to module root
+// %#s full path of source file
+// %+n import path qualified function name
+// %+k full package path
+// %+v equivalent to %+s:%d
+// %#v equivalent to %#s:%d
+func (c Call) Format(s fmt.State, verb rune) {
+ if c.frame == (runtime.Frame{}) {
+ fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
+ return
+ }
+
+ switch verb {
+ case 's', 'v':
+ file := c.frame.File
+ switch {
+ case s.Flag('#'):
+ // done
+ case s.Flag('+'):
+ file = pkgFilePath(&c.frame)
+ default:
+ const sep = "/"
+ if i := strings.LastIndex(file, sep); i != -1 {
+ file = file[i+len(sep):]
+ }
+ }
+ io.WriteString(s, file)
+ if verb == 'v' {
+ buf := [7]byte{':'}
+ s.Write(strconv.AppendInt(buf[:1], int64(c.frame.Line), 10))
+ }
+
+ case 'd':
+ buf := [6]byte{}
+ s.Write(strconv.AppendInt(buf[:0], int64(c.frame.Line), 10))
+
+ case 'k':
+ name := c.frame.Function
+ const pathSep = "/"
+ start, end := 0, len(name)
+ if i := strings.LastIndex(name, pathSep); i != -1 {
+ start = i + len(pathSep)
+ }
+ const pkgSep = "."
+ if i := strings.Index(name[start:], pkgSep); i != -1 {
+ end = start + i
+ }
+ if s.Flag('+') {
+ start = 0
+ }
+ io.WriteString(s, name[start:end])
+
+ case 'n':
+ name := c.frame.Function
+ if !s.Flag('+') {
+ const pathSep = "/"
+ if i := strings.LastIndex(name, pathSep); i != -1 {
+ name = name[i+len(pathSep):]
+ }
+ const pkgSep = "."
+ if i := strings.Index(name, pkgSep); i != -1 {
+ name = name[i+len(pkgSep):]
+ }
+ }
+ io.WriteString(s, name)
+ }
+}
+
+// Frame returns the call frame infomation for the Call.
+func (c Call) Frame() runtime.Frame {
+ return c.frame
+}
+
+// PC returns the program counter for this call frame; multiple frames may
+// have the same PC value.
+//
+// Deprecated: Use Call.Frame instead.
+func (c Call) PC() uintptr {
+ return c.frame.PC
+}
+
+// CallStack records a sequence of function invocations from a goroutine
+// stack.
+type CallStack []Call
+
+// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
+func (cs CallStack) String() string {
+ return fmt.Sprint(cs)
+}
+
+var (
+ openBracketBytes = []byte("[")
+ closeBracketBytes = []byte("]")
+ spaceBytes = []byte(" ")
+)
+
+// MarshalText implements encoding.TextMarshaler. It formats the CallStack the
+// same as fmt.Sprintf("%v", cs).
+func (cs CallStack) MarshalText() ([]byte, error) {
+ buf := bytes.Buffer{}
+ buf.Write(openBracketBytes)
+ for i, pc := range cs {
+ if i > 0 {
+ buf.Write(spaceBytes)
+ }
+ fmt.Fprint(&buf, pc)
+ }
+ buf.Write(closeBracketBytes)
+ return buf.Bytes(), nil
+}
+
+// Format implements fmt.Formatter by printing the CallStack as square brackets
+// ([, ]) surrounding a space separated list of Calls each formatted with the
+// supplied verb and options.
+func (cs CallStack) Format(s fmt.State, verb rune) {
+ s.Write(openBracketBytes)
+ for i, pc := range cs {
+ if i > 0 {
+ s.Write(spaceBytes)
+ }
+ pc.Format(s, verb)
+ }
+ s.Write(closeBracketBytes)
+}
+
+// Trace returns a CallStack for the current goroutine with element 0
+// identifying the calling function.
+func Trace() CallStack {
+ var pcs [512]uintptr
+ n := runtime.Callers(1, pcs[:])
+
+ frames := runtime.CallersFrames(pcs[:n])
+ cs := make(CallStack, 0, n)
+
+ // Skip extra frame retrieved just to make sure the runtime.sigpanic
+ // special case is handled.
+ frame, more := frames.Next()
+
+ for more {
+ frame, more = frames.Next()
+ cs = append(cs, Call{frame: frame})
+ }
+
+ return cs
+}
+
+// TrimBelow returns a slice of the CallStack with all entries below c
+// removed.
+func (cs CallStack) TrimBelow(c Call) CallStack {
+ for len(cs) > 0 && cs[0] != c {
+ cs = cs[1:]
+ }
+ return cs
+}
+
+// TrimAbove returns a slice of the CallStack with all entries above c
+// removed.
+func (cs CallStack) TrimAbove(c Call) CallStack {
+ for len(cs) > 0 && cs[len(cs)-1] != c {
+ cs = cs[:len(cs)-1]
+ }
+ return cs
+}
+
+// pkgIndex returns the index that results in file[index:] being the path of
+// file relative to the compile time GOPATH, and file[:index] being the
+// $GOPATH/src/ portion of file. funcName must be the name of a function in
+// file as returned by runtime.Func.Name.
+func pkgIndex(file, funcName string) int {
+ // As of Go 1.6.2 there is no direct way to know the compile time GOPATH
+ // at runtime, but we can infer the number of path segments in the GOPATH.
+ // We note that runtime.Func.Name() returns the function name qualified by
+ // the import path, which does not include the GOPATH. Thus we can trim
+ // segments from the beginning of the file path until the number of path
+ // separators remaining is one more than the number of path separators in
+ // the function name. For example, given:
+ //
+ // GOPATH /home/user
+ // file /home/user/src/pkg/sub/file.go
+ // fn.Name() pkg/sub.Type.Method
+ //
+ // We want to produce:
+ //
+ // file[:idx] == /home/user/src/
+ // file[idx:] == pkg/sub/file.go
+ //
+ // From this we can easily see that fn.Name() has one less path separator
+ // than our desired result for file[idx:]. We count separators from the
+ // end of the file path until it finds two more than in the function name
+ // and then move one character forward to preserve the initial path
+ // segment without a leading separator.
+ const sep = "/"
+ i := len(file)
+ for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
+ i = strings.LastIndex(file[:i], sep)
+ if i == -1 {
+ i = -len(sep)
+ break
+ }
+ }
+ // get back to 0 or trim the leading separator
+ return i + len(sep)
+}
+
+// pkgFilePath returns the frame's filepath relative to the compile-time GOPATH,
+// or its module path joined to its path relative to the module root.
+//
+// As of Go 1.11 there is no direct way to know the compile time GOPATH or
+// module paths at runtime, but we can piece together the desired information
+// from available information. We note that runtime.Frame.Function contains the
+// function name qualified by the package path, which includes the module path
+// but not the GOPATH. We can extract the package path from that and append the
+// last segments of the file path to arrive at the desired package qualified
+// file path. For example, given:
+//
+// GOPATH /home/user
+// import path pkg/sub
+// frame.File /home/user/src/pkg/sub/file.go
+// frame.Function pkg/sub.Type.Method
+// Desired return pkg/sub/file.go
+//
+// It appears that we simply need to trim ".Type.Method" from frame.Function and
+// append "/" + path.Base(file).
+//
+// But there are other wrinkles. Although it is idiomatic to do so, the internal
+// name of a package is not required to match the last segment of its import
+// path. In addition, the introduction of modules in Go 1.11 allows working
+// without a GOPATH. So we also must make these work right:
+//
+// GOPATH /home/user
+// import path pkg/go-sub
+// package name sub
+// frame.File /home/user/src/pkg/go-sub/file.go
+// frame.Function pkg/sub.Type.Method
+// Desired return pkg/go-sub/file.go
+//
+// Module path pkg/v2
+// import path pkg/v2/go-sub
+// package name sub
+// frame.File /home/user/cloned-pkg/go-sub/file.go
+// frame.Function pkg/v2/sub.Type.Method
+// Desired return pkg/v2/go-sub/file.go
+//
+// We can handle all of these situations by using the package path extracted
+// from frame.Function up to, but not including, the last segment as the prefix
+// and the last two segments of frame.File as the suffix of the returned path.
+// This preserves the existing behavior when working in a GOPATH without modules
+// and a semantically equivalent behavior when used in module aware project.
+func pkgFilePath(frame *runtime.Frame) string {
+ pre := pkgPrefix(frame.Function)
+ post := pathSuffix(frame.File)
+ if pre == "" {
+ return post
+ }
+ return pre + "/" + post
+}
+
+// pkgPrefix returns the import path of the function's package with the final
+// segment removed.
+func pkgPrefix(funcName string) string {
+ const pathSep = "/"
+ end := strings.LastIndex(funcName, pathSep)
+ if end == -1 {
+ return ""
+ }
+ return funcName[:end]
+}
+
+// pathSuffix returns the last two segments of path.
+func pathSuffix(path string) string {
+ const pathSep = "/"
+ lastSep := strings.LastIndex(path, pathSep)
+ if lastSep == -1 {
+ return path
+ }
+ return path[strings.LastIndex(path[:lastSep], pathSep)+1:]
+}
+
+var runtimePath string
+
+func init() {
+ var pcs [3]uintptr
+ runtime.Callers(0, pcs[:])
+ frames := runtime.CallersFrames(pcs[:])
+ frame, _ := frames.Next()
+ file := frame.File
+
+ idx := pkgIndex(frame.File, frame.Function)
+
+ runtimePath = file[:idx]
+ if runtime.GOOS == "windows" {
+ runtimePath = strings.ToLower(runtimePath)
+ }
+}
+
+func inGoroot(c Call) bool {
+ file := c.frame.File
+ if len(file) == 0 || file[0] == '?' {
+ return true
+ }
+ if runtime.GOOS == "windows" {
+ file = strings.ToLower(file)
+ }
+ return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
+}
+
+// TrimRuntime returns a slice of the CallStack with the topmost entries from
+// the go runtime removed. It considers any calls originating from unknown
+// files, files under GOROOT, or _testmain.go as part of the runtime.
+func (cs CallStack) TrimRuntime() CallStack {
+ for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {
+ cs = cs[:len(cs)-1]
+ }
+ return cs
+}
diff --git a/vendor/github.com/go-stack/stack/stack_test.go b/vendor/github.com/go-stack/stack/stack_test.go
new file mode 100644
index 0000000..44f3a7d
--- /dev/null
+++ b/vendor/github.com/go-stack/stack/stack_test.go
@@ -0,0 +1,582 @@
+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)
+ }
+}