pax_global_header00006660000000000000000000000064133405277000014513gustar00rootroot0000000000000052 comment=2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a stack-1.8.0/000077500000000000000000000000001334052770000126265ustar00rootroot00000000000000stack-1.8.0/.travis.yml000066400000000000000000000002721334052770000147400ustar00rootroot00000000000000language: go sudo: false go: - 1.7.x - 1.8.x - 1.9.x - 1.10.x - 1.11.x - tip before_install: - go get github.com/mattn/goveralls script: - goveralls -service=travis-ci stack-1.8.0/LICENSE.md000066400000000000000000000020661334052770000142360ustar00rootroot00000000000000The 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. stack-1.8.0/README.md000066400000000000000000000030271334052770000141070ustar00rootroot00000000000000[![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. stack-1.8.0/format_test.go000066400000000000000000000005241334052770000155050ustar00rootroot00000000000000// +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)) } stack-1.8.0/go.mod000066400000000000000000000000411334052770000137270ustar00rootroot00000000000000module github.com/go-stack/stack stack-1.8.0/stack-go19_test.go000066400000000000000000000027171334052770000161050ustar00rootroot00000000000000// +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 } stack-1.8.0/stack.go000066400000000000000000000270701334052770000142700ustar00rootroot00000000000000// +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 } stack-1.8.0/stack_test.go000066400000000000000000000310401334052770000153170ustar00rootroot00000000000000package 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) } }