diff options
Diffstat (limited to 'vendor/github.com/go-stack')
-rw-r--r-- | vendor/github.com/go-stack/stack/LICENSE.md | 21 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/README.md | 38 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/format_test.go | 21 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/go.mod | 1 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/stack-go19_test.go | 67 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/stack.go | 400 | ||||
-rw-r--r-- | vendor/github.com/go-stack/stack/stack_test.go | 582 |
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) + } +} |