Bump github.com/pkg/errors to the current git HEAD

This commit is contained in:
Stephen Gelman 2017-07-17 20:27:58 +00:00
parent 160bccba1c
commit ddbdc319c2
12 changed files with 803 additions and 148 deletions

6
glide.lock generated

@ -1,5 +1,5 @@
hash: 53d7484e21e3a93702191bef6c0d8a0f01082c9ab9c9c81fb9884ba31f31002e hash: c96d9438238597d9a70aa231b5a1a00ac2a961154bd76c01f5dec018d60145e1
updated: 2017-07-05T09:11:19.970140093-06:00 updated: 2017-07-17T20:26:21.026348832Z
imports: imports:
- name: github.com/bgentry/go-netrc - name: github.com/bgentry/go-netrc
version: 9fd32a8b3d3d3f9d43c341bfe098430e07609480 version: 9fd32a8b3d3d3f9d43c341bfe098430e07609480
@ -16,7 +16,7 @@ imports:
- name: github.com/olekukonko/ts - name: github.com/olekukonko/ts
version: ecf753e7c962639ab5a1fb46f7da627d4c0a04b8 version: ecf753e7c962639ab5a1fb46f7da627d4c0a04b8
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: 01fa4104b9c248c8945d14d9f128454d5b28d595 version: c605e284fe17294bda444b34710735b29d1a9d90
- name: github.com/rubyist/tracerx - name: github.com/rubyist/tracerx
version: d7bcc0bc315bed2a841841bee5dbecc8d7d7582f version: d7bcc0bc315bed2a841841bee5dbecc8d7d7582f
- name: github.com/spf13/cobra - name: github.com/spf13/cobra

@ -33,4 +33,4 @@ import:
- package: github.com/xeipuuv/gojsonschema - package: github.com/xeipuuv/gojsonschema
version: 6b67b3fab74d992bd07f72550006ab2c6907c416 version: 6b67b3fab74d992bd07f72550006ab2c6907c416
- package: github.com/pkg/errors - package: github.com/pkg/errors
version: 01fa4104b9c248c8945d14d9f128454d5b28d595 version: c605e284fe17294bda444b34710735b29d1a9d90

@ -1,9 +1,11 @@
language: go language: go
go_import_path: github.com/pkg/errors go_import_path: github.com/pkg/errors
go: go:
- 1.4.3 - 1.4.x
- 1.5.4 - 1.5.x
- 1.6.2 - 1.6.x
- 1.7.x
- 1.8.x
- tip - tip
script: script:

@ -21,4 +21,3 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -2,6 +2,8 @@
Package errors provides simple error handling primitives. Package errors provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to The traditional error handling idiom in Go is roughly akin to
```go ```go
if err != nil { if err != nil {

63
vendor/github.com/pkg/errors/bench_test.go generated vendored Normal file

@ -0,0 +1,63 @@
// +build go1.7
package errors
import (
"fmt"
"testing"
stderrors "errors"
)
func noErrors(at, depth int) error {
if at >= depth {
return stderrors.New("no error")
}
return noErrors(at+1, depth)
}
func yesErrors(at, depth int) error {
if at >= depth {
return New("ye error")
}
return yesErrors(at+1, depth)
}
// GlobalE is an exported global to store the result of benchmark results,
// preventing the compiler from optimising the benchmark functions away.
var GlobalE error
func BenchmarkErrors(b *testing.B) {
type run struct {
stack int
std bool
}
runs := []run{
{10, false},
{10, true},
{100, false},
{100, true},
{1000, false},
{1000, true},
}
for _, r := range runs {
part := "pkg/errors"
if r.std {
part = "errors"
}
name := fmt.Sprintf("%s-stack-%d", part, r.stack)
b.Run(name, func(b *testing.B) {
var err error
f := yesErrors
if r.std {
f = noErrors
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
err = f(0, r.stack)
}
b.StopTimer()
GlobalE = err
})
}
}

@ -14,13 +14,18 @@
// Adding context to an error // Adding context to an error
// //
// The errors.Wrap function returns a new error that adds context to the // The errors.Wrap function returns a new error that adds context to the
// original error. For example // original error by recording a stack trace at the point Wrap is called,
// and the supplied message. For example
// //
// _, err := ioutil.ReadAll(r) // _, err := ioutil.ReadAll(r)
// if err != nil { // if err != nil {
// return errors.Wrap(err, "read failed") // return errors.Wrap(err, "read failed")
// } // }
// //
// If additional control is required the errors.WithStack and errors.WithMessage
// functions destructure errors.Wrap into its component operations of annotating
// an error with a stack trace and an a message, respectively.
//
// Retrieving the cause of an error // Retrieving the cause of an error
// //
// Using errors.Wrap constructs a stack of errors, adding context to the // Using errors.Wrap constructs a stack of errors, adding context to the
@ -28,7 +33,7 @@
// to reverse the operation of errors.Wrap to retrieve the original error // to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface // for inspection. Any error value which implements this interface
// //
// type Causer interface { // type causer interface {
// Cause() error // Cause() error
// } // }
// //
@ -43,6 +48,9 @@
// // unknown error // // unknown error
// } // }
// //
// causer interface is not exported by this package, but is considered a part
// of stable public API.
//
// Formatted printing of errors // Formatted printing of errors
// //
// All error values returned from this package implement fmt.Formatter and can // All error values returned from this package implement fmt.Formatter and can
@ -59,7 +67,7 @@
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface. // invoked. This information can be retrieved with the following interface.
// //
// type StackTrace interface { // type stackTracer interface {
// StackTrace() errors.StackTrace // StackTrace() errors.StackTrace
// } // }
// //
@ -67,16 +75,19 @@
// //
// type StackTrace []Frame // type StackTrace []Frame
// //
// The Frame type represents a call site in the stacktrace. Frame supports // The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about // the fmt.Formatter interface that can be used for printing information about
// the stacktrace of this error. For example: // the stack trace of this error. For example:
// //
// if err, ok := err.(StackTrace); ok { // if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() { // for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f) // fmt.Printf("%+s:%d", f)
// } // }
// } // }
// //
// stackTracer interface is not exported by this package, but is considered a part
// of stable public API.
//
// See the documentation for Frame.Format for more details. // See the documentation for Frame.Format for more details.
package errors package errors
@ -85,102 +96,149 @@ import (
"io" "io"
) )
// _error is an error implementation returned by New and Errorf
// that implements its own fmt.Formatter.
type _error struct {
msg string
*stack
}
func (e _error) Error() string { return e.msg }
func (e _error) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, e.msg)
fmt.Fprintf(s, "%+v", e.StackTrace())
return
}
fallthrough
case 's':
io.WriteString(s, e.msg)
}
}
// New returns an error with the supplied message. // New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error { func New(message string) error {
return _error{ return &fundamental{
message, msg: message,
callers(), stack: callers(),
} }
} }
// Errorf formats according to a format specifier and returns the string // Errorf formats according to a format specifier and returns the string
// as a value that satisfies error. // as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error { func Errorf(format string, args ...interface{}) error {
return _error{ return &fundamental{
fmt.Sprintf(format, args...), msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(), callers(),
} }
} }
type cause struct { type withStack struct {
cause error error
msg string
}
func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) }
func (c cause) Cause() error { return c.cause }
// wrapper is an error implementation returned by Wrap and Wrapf
// that implements its own fmt.Formatter.
type wrapper struct {
cause
*stack *stack
} }
func (w wrapper) Format(s fmt.State, verb rune) { func (w *withStack) Cause() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb { switch verb {
case 'v': case 'v':
if s.Flag('+') { if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause()) fmt.Fprintf(s, "%+v", w.Cause())
fmt.Fprintf(s, "%+v: %s", w.StackTrace()[0], w.msg) w.stack.Format(s, verb)
return return
} }
fallthrough fallthrough
case 's': case 's':
io.WriteString(s, w.Error()) io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
} }
} }
// Wrap returns an error annotating err with message. // Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil. // If err is nil, Wrap returns nil.
func Wrap(err error, message string) error { func Wrap(err error, message string) error {
if err == nil { if err == nil {
return nil return nil
} }
return wrapper{ err = &withMessage{
cause: cause{ cause: err,
cause: err, msg: message,
msg: message, }
}, return &withStack{
stack: callers(), err,
callers(),
} }
} }
// Wrapf returns an error annotating err with the format specifier. // Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is call, and the format specifier.
// If err is nil, Wrapf returns nil. // If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error { func Wrapf(err error, format string, args ...interface{}) error {
if err == nil { if err == nil {
return nil return nil
} }
return wrapper{ err = &withMessage{
cause: cause{ cause: err,
cause: err, msg: fmt.Sprintf(format, args...),
msg: fmt.Sprintf(format, args...), }
}, return &withStack{
stack: callers(), err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.Error())
} }
} }
@ -188,7 +246,7 @@ func Wrapf(err error, format string, args ...interface{}) error {
// An error value has a cause if it implements the following // An error value has a cause if it implements the following
// interface: // interface:
// //
// type Causer interface { // type causer interface {
// Cause() error // Cause() error
// } // }
// //

@ -84,6 +84,18 @@ func TestCause(t *testing.T) {
}, { }, {
err: x, // return from errors.New err: x, // return from errors.New
want: x, want: x,
}, {
WithMessage(nil, "whoops"),
nil,
}, {
WithMessage(io.EOF, "whoops"),
io.EOF,
}, {
WithStack(nil),
nil,
}, {
WithStack(io.EOF),
io.EOF,
}} }}
for i, tt := range tests { for i, tt := range tests {
@ -137,23 +149,77 @@ func TestErrorf(t *testing.T) {
} }
} }
func TestWithStackNil(t *testing.T) {
got := WithStack(nil)
if got != nil {
t.Errorf("WithStack(nil): got %#v, expected nil", got)
}
}
func TestWithStack(t *testing.T) {
tests := []struct {
err error
want string
}{
{io.EOF, "EOF"},
{WithStack(io.EOF), "EOF"},
}
for _, tt := range tests {
got := WithStack(tt.err).Error()
if got != tt.want {
t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want)
}
}
}
func TestWithMessageNil(t *testing.T) {
got := WithMessage(nil, "no error")
if got != nil {
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
}
}
func TestWithMessage(t *testing.T) {
tests := []struct {
err error
message string
want string
}{
{io.EOF, "read error", "read error: EOF"},
{WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"},
}
for _, tt := range tests {
got := WithMessage(tt.err, tt.message).Error()
if got != tt.want {
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
}
}
}
// errors.New, etc values are not expected to be compared by value // errors.New, etc values are not expected to be compared by value
// but the change in errors#27 made them incomparable. Assert that // but the change in errors#27 made them incomparable. Assert that
// various kinds of errors have a functional equality operator, even // various kinds of errors have a functional equality operator, even
// if the result of that equality is always false. // if the result of that equality is always false.
func TestErrorEquality(t *testing.T) { func TestErrorEquality(t *testing.T) {
tests := []struct { vals := []error{
err1, err2 error nil,
}{ io.EOF,
{io.EOF, io.EOF}, errors.New("EOF"),
{io.EOF, nil}, New("EOF"),
{io.EOF, errors.New("EOF")}, Errorf("EOF"),
{io.EOF, New("EOF")}, Wrap(io.EOF, "EOF"),
{New("EOF"), New("EOF")}, Wrapf(io.EOF, "EOF%d", 2),
{New("EOF"), Errorf("EOF")}, WithMessage(nil, "whoops"),
{New("EOF"), Wrap(io.EOF, "EOF")}, WithMessage(io.EOF, "whoops"),
WithStack(io.EOF),
WithStack(nil),
} }
for _, tt := range tests {
_ = tt.err1 == tt.err2 // mustn't panic for i := range vals {
for j := range vals {
_ = vals[i] == vals[j] // mustn't panic
}
} }
} }

@ -35,6 +35,59 @@ func ExampleNew_printf() {
// /home/dfc/go/src/runtime/asm_amd64.s:2059 // /home/dfc/go/src/runtime/asm_amd64.s:2059
} }
func ExampleWithMessage() {
cause := errors.New("whoops")
err := errors.WithMessage(cause, "oh noes")
fmt.Println(err)
// Output: oh noes: whoops
}
func ExampleWithStack() {
cause := errors.New("whoops")
err := errors.WithStack(cause)
fmt.Println(err)
// Output: whoops
}
func ExampleWithStack_printf() {
cause := errors.New("whoops")
err := errors.WithStack(cause)
fmt.Printf("%+v", err)
// Example Output:
// whoops
// github.com/pkg/errors_test.ExampleWithStack_printf
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55
// testing.runExample
// /usr/lib/go/src/testing/example.go:114
// testing.RunExamples
// /usr/lib/go/src/testing/example.go:38
// testing.(*M).Run
// /usr/lib/go/src/testing/testing.go:744
// main.main
// github.com/pkg/errors/_test/_testmain.go:106
// runtime.main
// /usr/lib/go/src/runtime/proc.go:183
// runtime.goexit
// /usr/lib/go/src/runtime/asm_amd64.s:2086
// github.com/pkg/errors_test.ExampleWithStack_printf
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56
// testing.runExample
// /usr/lib/go/src/testing/example.go:114
// testing.RunExamples
// /usr/lib/go/src/testing/example.go:38
// testing.(*M).Run
// /usr/lib/go/src/testing/testing.go:744
// main.main
// github.com/pkg/errors/_test/_testmain.go:106
// runtime.main
// /usr/lib/go/src/runtime/proc.go:183
// runtime.goexit
// /usr/lib/go/src/runtime/asm_amd64.s:2086
}
func ExampleWrap() { func ExampleWrap() {
cause := errors.New("whoops") cause := errors.New("whoops")
err := errors.Wrap(cause, "oh noes") err := errors.Wrap(cause, "oh noes")
@ -119,14 +172,14 @@ func ExampleErrorf_extended() {
// /home/dfc/go/src/runtime/asm_amd64.s:2059 // /home/dfc/go/src/runtime/asm_amd64.s:2059
} }
func Example_stacktrace() { func Example_stackTrace() {
type StackTrace interface { type stackTracer interface {
StackTrace() errors.StackTrace StackTrace() errors.StackTrace
} }
err, ok := errors.Cause(fn()).(StackTrace) err, ok := errors.Cause(fn()).(stackTracer)
if !ok { if !ok {
panic("oops, err does not implement StackTrace") panic("oops, err does not implement stackTracer")
} }
st := err.StackTrace() st := err.StackTrace()
@ -135,7 +188,7 @@ func Example_stacktrace() {
// Example output: // Example output:
// github.com/pkg/errors_test.fn // github.com/pkg/errors_test.fn
// /home/dfc/src/github.com/pkg/errors/example_test.go:47 // /home/dfc/src/github.com/pkg/errors/example_test.go:47
// github.com/pkg/errors_test.Example_stacktrace // github.com/pkg/errors_test.Example_stackTrace
// /home/dfc/src/github.com/pkg/errors/example_test.go:127 // /home/dfc/src/github.com/pkg/errors/example_test.go:127
} }

@ -1,6 +1,7 @@
package errors package errors
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"regexp" "regexp"
@ -26,11 +27,15 @@ func TestFormatNew(t *testing.T) {
"%+v", "%+v",
"error\n" + "error\n" +
"github.com/pkg/errors.TestFormatNew\n" + "github.com/pkg/errors.TestFormatNew\n" +
"\t.+/github.com/pkg/errors/format_test.go:25", "\t.+/github.com/pkg/errors/format_test.go:26",
}, {
New("error"),
"%q",
`"error"`,
}} }}
for _, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, tt.error, tt.format, tt.want) testFormatRegexp(t, i, tt.error, tt.format, tt.want)
} }
} }
@ -52,11 +57,11 @@ func TestFormatErrorf(t *testing.T) {
"%+v", "%+v",
"error\n" + "error\n" +
"github.com/pkg/errors.TestFormatErrorf\n" + "github.com/pkg/errors.TestFormatErrorf\n" +
"\t.+/github.com/pkg/errors/format_test.go:51", "\t.+/github.com/pkg/errors/format_test.go:56",
}} }}
for _, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, tt.error, tt.format, tt.want) testFormatRegexp(t, i, tt.error, tt.format, tt.want)
} }
} }
@ -78,15 +83,37 @@ func TestFormatWrap(t *testing.T) {
"%+v", "%+v",
"error\n" + "error\n" +
"github.com/pkg/errors.TestFormatWrap\n" + "github.com/pkg/errors.TestFormatWrap\n" +
"\t.+/github.com/pkg/errors/format_test.go:77", "\t.+/github.com/pkg/errors/format_test.go:82",
}, { }, {
Wrap(io.EOF, "error"), Wrap(io.EOF, "error"),
"%s", "%s",
"error: EOF", "error: EOF",
}, {
Wrap(io.EOF, "error"),
"%v",
"error: EOF",
}, {
Wrap(io.EOF, "error"),
"%+v",
"EOF\n" +
"error\n" +
"github.com/pkg/errors.TestFormatWrap\n" +
"\t.+/github.com/pkg/errors/format_test.go:96",
}, {
Wrap(Wrap(io.EOF, "error1"), "error2"),
"%+v",
"EOF\n" +
"error1\n" +
"github.com/pkg/errors.TestFormatWrap\n" +
"\t.+/github.com/pkg/errors/format_test.go:103\n",
}, {
Wrap(New("error with space"), "context"),
"%q",
`"context: error with space"`,
}} }}
for _, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, tt.error, tt.format, tt.want) testFormatRegexp(t, i, tt.error, tt.format, tt.want)
} }
} }
@ -96,19 +123,24 @@ func TestFormatWrapf(t *testing.T) {
format string format string
want string want string
}{{ }{{
Wrapf(io.EOF, "error%d", 2),
"%s",
"error2: EOF",
}, {
Wrapf(io.EOF, "error%d", 2),
"%v",
"error2: EOF",
}, {
Wrapf(io.EOF, "error%d", 2),
"%+v",
"EOF\n" +
"error2\n" +
"github.com/pkg/errors.TestFormatWrapf\n" +
"\t.+/github.com/pkg/errors/format_test.go:134",
}, {
Wrapf(New("error"), "error%d", 2), Wrapf(New("error"), "error%d", 2),
"%s", "%s",
"error2: error", "error2: error",
}, {
Wrap(io.EOF, "error"),
"%v",
"error: EOF",
}, {
Wrap(io.EOF, "error"),
"%+v",
"EOF\n" +
"github.com/pkg/errors.TestFormatWrapf\n" +
"\t.+/github.com/pkg/errors/format_test.go:107: error",
}, { }, {
Wrapf(New("error"), "error%d", 2), Wrapf(New("error"), "error%d", 2),
"%v", "%v",
@ -118,24 +150,386 @@ func TestFormatWrapf(t *testing.T) {
"%+v", "%+v",
"error\n" + "error\n" +
"github.com/pkg/errors.TestFormatWrapf\n" + "github.com/pkg/errors.TestFormatWrapf\n" +
"\t.+/github.com/pkg/errors/format_test.go:117", "\t.+/github.com/pkg/errors/format_test.go:149",
}} }}
for _, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, tt.error, tt.format, tt.want) testFormatRegexp(t, i, tt.error, tt.format, tt.want)
} }
} }
func testFormatRegexp(t *testing.T, arg interface{}, format, want string) { func TestFormatWithStack(t *testing.T) {
tests := []struct {
error
format string
want []string
}{{
WithStack(io.EOF),
"%s",
[]string{"EOF"},
}, {
WithStack(io.EOF),
"%v",
[]string{"EOF"},
}, {
WithStack(io.EOF),
"%+v",
[]string{"EOF",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:175"},
}, {
WithStack(New("error")),
"%s",
[]string{"error"},
}, {
WithStack(New("error")),
"%v",
[]string{"error"},
}, {
WithStack(New("error")),
"%+v",
[]string{"error",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:189",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:189"},
}, {
WithStack(WithStack(io.EOF)),
"%+v",
[]string{"EOF",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:197",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:197"},
}, {
WithStack(WithStack(Wrapf(io.EOF, "message"))),
"%+v",
[]string{"EOF",
"message",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:205",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:205",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:205"},
}, {
WithStack(Errorf("error%d", 1)),
"%+v",
[]string{"error1",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:216",
"github.com/pkg/errors.TestFormatWithStack\n" +
"\t.+/github.com/pkg/errors/format_test.go:216"},
}}
for i, tt := range tests {
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
}
}
func TestFormatWithMessage(t *testing.T) {
tests := []struct {
error
format string
want []string
}{{
WithMessage(New("error"), "error2"),
"%s",
[]string{"error2: error"},
}, {
WithMessage(New("error"), "error2"),
"%v",
[]string{"error2: error"},
}, {
WithMessage(New("error"), "error2"),
"%+v",
[]string{
"error",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:244",
"error2"},
}, {
WithMessage(io.EOF, "addition1"),
"%s",
[]string{"addition1: EOF"},
}, {
WithMessage(io.EOF, "addition1"),
"%v",
[]string{"addition1: EOF"},
}, {
WithMessage(io.EOF, "addition1"),
"%+v",
[]string{"EOF", "addition1"},
}, {
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
"%v",
[]string{"addition2: addition1: EOF"},
}, {
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
"%+v",
[]string{"EOF", "addition1", "addition2"},
}, {
Wrap(WithMessage(io.EOF, "error1"), "error2"),
"%+v",
[]string{"EOF", "error1", "error2",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:272"},
}, {
WithMessage(Errorf("error%d", 1), "error2"),
"%+v",
[]string{"error1",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:278",
"error2"},
}, {
WithMessage(WithStack(io.EOF), "error"),
"%+v",
[]string{
"EOF",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:285",
"error"},
}, {
WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
"%+v",
[]string{
"EOF",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:293",
"inside-error",
"github.com/pkg/errors.TestFormatWithMessage\n" +
"\t.+/github.com/pkg/errors/format_test.go:293",
"outside-error"},
}}
for i, tt := range tests {
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
}
}
func TestFormatGeneric(t *testing.T) {
starts := []struct {
err error
want []string
}{
{New("new-error"), []string{
"new-error",
"github.com/pkg/errors.TestFormatGeneric\n" +
"\t.+/github.com/pkg/errors/format_test.go:315"},
}, {Errorf("errorf-error"), []string{
"errorf-error",
"github.com/pkg/errors.TestFormatGeneric\n" +
"\t.+/github.com/pkg/errors/format_test.go:319"},
}, {errors.New("errors-new-error"), []string{
"errors-new-error"},
},
}
wrappers := []wrapper{
{
func(err error) error { return WithMessage(err, "with-message") },
[]string{"with-message"},
}, {
func(err error) error { return WithStack(err) },
[]string{
"github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" +
".+/github.com/pkg/errors/format_test.go:333",
},
}, {
func(err error) error { return Wrap(err, "wrap-error") },
[]string{
"wrap-error",
"github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" +
".+/github.com/pkg/errors/format_test.go:339",
},
}, {
func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
[]string{
"wrapf-error1",
"github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" +
".+/github.com/pkg/errors/format_test.go:346",
},
},
}
for s := range starts {
err := starts[s].err
want := starts[s].want
testFormatCompleteCompare(t, s, err, "%+v", want, false)
testGenericRecursive(t, err, want, wrappers, 3)
}
}
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
got := fmt.Sprintf(format, arg) got := fmt.Sprintf(format, arg)
lines := strings.SplitN(got, "\n", -1) gotLines := strings.SplitN(got, "\n", -1)
for i, w := range strings.SplitN(want, "\n", -1) { wantLines := strings.SplitN(want, "\n", -1)
match, err := regexp.MatchString(w, lines[i])
if len(wantLines) > len(gotLines) {
t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
return
}
for i, w := range wantLines {
match, err := regexp.MatchString(w, gotLines[i])
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !match { if !match {
t.Errorf("fmt.Sprintf(%q, err): got: %q, want: %q", format, got, want) t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
}
}
}
var stackLineR = regexp.MustCompile(`\.`)
// parseBlocks parses input into a slice, where:
// - incase entry contains a newline, its a stacktrace
// - incase entry contains no newline, its a solo line.
//
// Detecting stack boundaries only works incase the WithStack-calls are
// to be found on the same line, thats why it is optionally here.
//
// Example use:
//
// for _, e := range blocks {
// if strings.ContainsAny(e, "\n") {
// // Match as stack
// } else {
// // Match as line
// }
// }
//
func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
var blocks []string
stack := ""
wasStack := false
lines := map[string]bool{} // already found lines
for _, l := range strings.Split(input, "\n") {
isStackLine := stackLineR.MatchString(l)
switch {
case !isStackLine && wasStack:
blocks = append(blocks, stack, l)
stack = ""
lines = map[string]bool{}
case isStackLine:
if wasStack {
// Detecting two stacks after another, possible cause lines match in
// our tests due to WithStack(WithStack(io.EOF)) on same line.
if detectStackboundaries {
if lines[l] {
if len(stack) == 0 {
return nil, errors.New("len of block must not be zero here")
}
blocks = append(blocks, stack)
stack = l
lines = map[string]bool{l: true}
continue
}
}
stack = stack + "\n" + l
} else {
stack = l
}
lines[l] = true
case !isStackLine && !wasStack:
blocks = append(blocks, l)
default:
return nil, errors.New("must not happen")
}
wasStack = isStackLine
}
// Use up stack
if stack != "" {
blocks = append(blocks, stack)
}
return blocks, nil
}
func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
gotStr := fmt.Sprintf(format, arg)
got, err := parseBlocks(gotStr, detectStackBoundaries)
if err != nil {
t.Fatal(err)
}
if len(got) != len(want) {
t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
}
for i := range got {
if strings.ContainsAny(want[i], "\n") {
// Match as stack
match, err := regexp.MatchString(want[i], got[i])
if err != nil {
t.Fatal(err)
}
if !match {
t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
}
} else {
// Match as message
if got[i] != want[i] {
t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
}
}
}
}
type wrapper struct {
wrap func(err error) error
want []string
}
func prettyBlocks(blocks []string, prefix ...string) string {
var out []string
for _, b := range blocks {
out = append(out, fmt.Sprintf("%v", b))
}
return " " + strings.Join(out, "\n ")
}
func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
if len(beforeWant) == 0 {
panic("beforeWant must not be empty")
}
for _, w := range list {
if len(w.want) == 0 {
panic("want must not be empty")
}
err := w.wrap(beforeErr)
// Copy required cause append(beforeWant, ..) modified beforeWant subtly.
beforeCopy := make([]string, len(beforeWant))
copy(beforeCopy, beforeWant)
beforeWant := beforeCopy
last := len(beforeWant) - 1
var want []string
// Merge two stacks behind each other.
if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
} else {
want = append(beforeWant, w.want...)
}
testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
if maxDepth > 0 {
testGenericRecursive(t, err, want, list, maxDepth-1)
} }
} }
} }

@ -79,6 +79,14 @@ func (f Frame) Format(s fmt.State, verb rune) {
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). // StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame type StackTrace []Frame
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) { func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb { switch verb {
case 'v': case 'v':
@ -100,6 +108,19 @@ func (st StackTrace) Format(s fmt.State, verb rune) {
// stack represents a stack of program counters. // stack represents a stack of program counters.
type stack []uintptr type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace { func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s)) f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ { for i := 0; i < len(f); i++ {

@ -120,8 +120,8 @@ func TestFrameFormat(t *testing.T) {
"unknown:0", "unknown:0",
}} }}
for _, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, tt.Frame, tt.format, tt.want) testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
} }
} }
@ -155,15 +155,12 @@ func TestTrimGOPATH(t *testing.T) {
"github.com/pkg/errors/stack_test.go", "github.com/pkg/errors/stack_test.go",
}} }}
for _, tt := range tests { for i, tt := range tests {
pc := tt.Frame.pc() pc := tt.Frame.pc()
fn := runtime.FuncForPC(pc) fn := runtime.FuncForPC(pc)
file, _ := fn.FileLine(pc) file, _ := fn.FileLine(pc)
got := trimGOPATH(fn.Name(), file) got := trimGOPATH(fn.Name(), file)
want := tt.want testFormatRegexp(t, i, got, "%s", tt.want)
if want != got {
t.Errorf("%v: want %q, got %q", tt.Frame, want, got)
}
} }
} }
@ -174,24 +171,24 @@ func TestStackTrace(t *testing.T) {
}{{ }{{
New("ooh"), []string{ New("ooh"), []string{
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:175", "\t.+/github.com/pkg/errors/stack_test.go:172",
}, },
}, { }, {
Wrap(New("ooh"), "ahh"), []string{ Wrap(New("ooh"), "ahh"), []string{
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:180", // this is the stack of Wrap, not New "\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New
}, },
}, { }, {
Cause(Wrap(New("ooh"), "ahh")), []string{ Cause(Wrap(New("ooh"), "ahh")), []string{
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:185", // this is the stack of New "\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New
}, },
}, { }, {
func() error { return New("ooh") }(), []string{ func() error { return New("ooh") }(), []string{
`github.com/pkg/errors.(func·005|TestStackTrace.func1)` + `github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:190", // this is the stack of New "\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:190", // this is the stack of New's caller "\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller
}, },
}, { }, {
Cause(func() error { Cause(func() error {
@ -199,15 +196,15 @@ func TestStackTrace(t *testing.T) {
return Errorf("hello %s", fmt.Sprintf("world")) return Errorf("hello %s", fmt.Sprintf("world"))
}() }()
}()), []string{ }()), []string{
`github.com/pkg/errors.(func·006|TestStackTrace.func2.1)` + `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:199", // this is the stack of Errorf "\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
`github.com/pkg/errors.(func·007|TestStackTrace.func2)` + `github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
"\n\t.+/github.com/pkg/errors/stack_test.go:200", // this is the stack of Errorf's caller "\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
"github.com/pkg/errors.TestStackTrace\n" + "github.com/pkg/errors.TestStackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:201", // this is the stack of Errorf's caller's caller "\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
}, },
}} }}
for _, tt := range tests { for i, tt := range tests {
x, ok := tt.err.(interface { x, ok := tt.err.(interface {
StackTrace() StackTrace StackTrace() StackTrace
}) })
@ -217,12 +214,12 @@ func TestStackTrace(t *testing.T) {
} }
st := x.StackTrace() st := x.StackTrace()
for j, want := range tt.want { for j, want := range tt.want {
testFormatRegexp(t, st[j], "%+v", want) testFormatRegexp(t, i, st[j], "%+v", want)
} }
} }
} }
func stacktrace() StackTrace { func stackTrace() StackTrace {
const depth = 8 const depth = 8
var pcs [depth]uintptr var pcs [depth]uintptr
n := runtime.Callers(1, pcs[:]) n := runtime.Callers(1, pcs[:])
@ -268,28 +265,28 @@ func TestStackTraceFormat(t *testing.T) {
"%#v", "%#v",
`\[\]errors.Frame{}`, `\[\]errors.Frame{}`,
}, { }, {
stacktrace()[:2], stackTrace()[:2],
"%s", "%s",
`\[stack_test.go stack_test.go\]`, `\[stack_test.go stack_test.go\]`,
}, { }, {
stacktrace()[:2], stackTrace()[:2],
"%v", "%v",
`\[stack_test.go:228 stack_test.go:275\]`, `\[stack_test.go:225 stack_test.go:272\]`,
}, { }, {
stacktrace()[:2], stackTrace()[:2],
"%+v", "%+v",
"\n" + "\n" +
"github.com/pkg/errors.stacktrace\n" + "github.com/pkg/errors.stackTrace\n" +
"\t.+/github.com/pkg/errors/stack_test.go:228\n" + "\t.+/github.com/pkg/errors/stack_test.go:225\n" +
"github.com/pkg/errors.TestStackTraceFormat\n" + "github.com/pkg/errors.TestStackTraceFormat\n" +
"\t.+/github.com/pkg/errors/stack_test.go:279", "\t.+/github.com/pkg/errors/stack_test.go:276",
}, { }, {
stacktrace()[:2], stackTrace()[:2],
"%#v", "%#v",
`\[\]errors.Frame{stack_test.go:228, stack_test.go:287}`, `\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
}} }}
for _, tt := range tests { for i, tt := range tests {
testFormatRegexp(t, tt.StackTrace, tt.format, tt.want) testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
} }
} }