Commit 344f9ccf authored by Alessio Treglia's avatar Alessio Treglia

Imported Upstream version 1.5.2

parents
language: go
sudo: false
go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- tip
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- goveralls -service=travis-ci
Copyright 2014 Chris Hines
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
[![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.
// +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))
}
// 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 {
fn *runtime.Func
pc uintptr
}
// 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 {
var pcs [2]uintptr
n := runtime.Callers(skip+1, pcs[:])
var c Call
if n < 2 {
return c
}
c.pc = pcs[1]
if runtime.FuncForPC(pcs[0]) != sigpanic {
c.pc--
}
c.fn = runtime.FuncForPC(c.pc)
return c
}
// 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.fn == nil {
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
// %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
// %#s full path of source file
// %+n import path qualified function name
// %+v equivalent to %+s:%d
// %#v equivalent to %#s:%d
func (c Call) Format(s fmt.State, verb rune) {
if c.fn == nil {
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
return
}
switch verb {
case 's', 'v':
file, line := c.fn.FileLine(c.pc)
switch {
case s.Flag('#'):
// done
case s.Flag('+'):
file = file[pkgIndex(file, c.fn.Name()):]
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(line), 10))
}
case 'd':
_, line := c.fn.FileLine(c.pc)
buf := [6]byte{}
s.Write(strconv.AppendInt(buf[:0], int64(line), 10))
case 'n':
name := c.fn.Name()
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)
}
}
// PC returns the program counter for this call frame; multiple frames may
// have the same PC value.
func (c Call) PC() uintptr {
return c.pc
}
// name returns the import path qualified name of the function containing the
// call.
func (c Call) name() string {
if c.fn == nil {
return "???"
}
return c.fn.Name()
}
func (c Call) file() string {
if c.fn == nil {
return "???"
}
file, _ := c.fn.FileLine(c.pc)
return file
}
func (c Call) line() int {
if c.fn == nil {
return 0
}
_, line := c.fn.FileLine(c.pc)
return line
}
// 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 pc.fn == nil {
return nil, ErrNoFunc
}
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)
}
// findSigpanic intentionally executes faulting code to generate a stack trace
// containing an entry for runtime.sigpanic.
func findSigpanic() *runtime.Func {
var fn *runtime.Func
var p *int
func() int {
defer func() {
if p := recover(); p != nil {
var pcs [512]uintptr
n := runtime.Callers(2, pcs[:])
for _, pc := range pcs[:n] {
f := runtime.FuncForPC(pc)
if f.Name() == "runtime.sigpanic" {
fn = f
break
}
}
}
}()
// intentional nil pointer dereference to trigger sigpanic
return *p
}()
return fn
}
var sigpanic = findSigpanic()
// 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(2, pcs[:])
cs := make([]Call, n)
for i, pc := range pcs[:n] {
pcFix := pc
if i > 0 && cs[i-1].fn != sigpanic {
pcFix--
}
cs[i] = Call{
fn: runtime.FuncForPC(pcFix),
pc: pcFix,
}
}
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].pc != c.pc {
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].pc != c.pc {
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)
}
var runtimePath string
func init() {
var pcs [1]uintptr
runtime.Callers(0, pcs[:])
fn := runtime.FuncForPC(pcs[0])
file, _ := fn.FileLine(pcs[0])
idx := pkgIndex(file, fn.Name())
runtimePath = file[:idx]
if runtime.GOOS == "windows" {
runtimePath = strings.ToLower(runtimePath)
}
}
func inGoroot(c Call) bool {
file := c.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
}
package stack_test
import (
"fmt"
"io/ioutil"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/go-stack/stack"
)
const importPath = "github.com/go-stack/stack"
type testType struct{}
func (tt testType) testMethod() (c stack.Call, pc uintptr, file string, line int, ok bool) {
c = stack.Caller(0)
pc, file, line, ok = runtime.Caller(0)
line--
return
}
func TestCallFormat(t *testing.T) {
t.Parallel()
c := stack.Caller(0)
pc, file, line, ok := runtime.Caller(0)
line--
if !ok {
t.Fatal("runtime.Caller(0) failed")
}
relFile := path.Join(importPath, filepath.Base(file))
c2, pc2, 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", runtime.FuncForPC(pc - 1).Name()},
{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", runtime.FuncForPC(pc2).Name()},
{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.Errorf("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)