├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── appveyor.yml ├── bench_test.go ├── errors.go ├── errors_test.go ├── example_test.go ├── format_test.go ├── go113.go ├── go113_test.go ├── json_test.go ├── stack.go └── stack_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | 4 | language: go 5 | go_import_path: github.com/pkg/errors 6 | go: 7 | - 1.14 8 | - 1.15 9 | - tip 10 | 11 | script: 12 | - make check 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PKGS := github.com/pkg/errors 2 | SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) 3 | GO := go 4 | 5 | check: test vet gofmt misspell unconvert staticcheck ineffassign unparam 6 | 7 | test: 8 | $(GO) test $(PKGS) 9 | 10 | vet: | test 11 | $(GO) vet $(PKGS) 12 | 13 | staticcheck: 14 | $(GO) get honnef.co/go/tools/cmd/staticcheck 15 | staticcheck -checks all $(PKGS) 16 | 17 | misspell: 18 | $(GO) get github.com/client9/misspell/cmd/misspell 19 | misspell \ 20 | -locale GB \ 21 | -error \ 22 | *.md *.go 23 | 24 | unconvert: 25 | $(GO) get github.com/mdempsky/unconvert 26 | unconvert -v $(PKGS) 27 | 28 | ineffassign: 29 | $(GO) get github.com/gordonklaus/ineffassign 30 | find $(SRCDIRS) -name '*.go' | xargs ineffassign 31 | 32 | pedantic: check errcheck 33 | 34 | unparam: 35 | $(GO) get mvdan.cc/unparam 36 | unparam ./... 37 | 38 | errcheck: 39 | $(GO) get github.com/kisielk/errcheck 40 | errcheck $(PKGS) 41 | 42 | gofmt: 43 | @echo Checking code is gofmted 44 | @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) 2 | 3 | Package errors provides simple error handling primitives. 4 | 5 | `go get github.com/pkg/errors` 6 | 7 | The traditional error handling idiom in Go is roughly akin to 8 | ```go 9 | if err != nil { 10 | return err 11 | } 12 | ``` 13 | which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. 14 | 15 | ## Adding context to an error 16 | 17 | The errors.Wrap function returns a new error that adds context to the original error. For example 18 | ```go 19 | _, err := ioutil.ReadAll(r) 20 | if err != nil { 21 | return errors.Wrap(err, "read failed") 22 | } 23 | ``` 24 | ## Retrieving the cause of an error 25 | 26 | Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. 27 | ```go 28 | type causer interface { 29 | Cause() error 30 | } 31 | ``` 32 | `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: 33 | ```go 34 | switch err := errors.Cause(err).(type) { 35 | case *MyError: 36 | // handle specifically 37 | default: 38 | // unknown error 39 | } 40 | ``` 41 | 42 | [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). 43 | 44 | ## Roadmap 45 | 46 | With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: 47 | 48 | - 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) 49 | - 1.0. Final release. 50 | 51 | ## Contributing 52 | 53 | Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. 54 | 55 | Before sending a PR, please discuss your change by raising an issue. 56 | 57 | ## License 58 | 59 | BSD-2-Clause 60 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: build-{build}.{branch} 2 | 3 | clone_folder: C:\gopath\src\github.com\pkg\errors 4 | shallow_clone: true # for startup speed 5 | 6 | environment: 7 | GOPATH: C:\gopath 8 | 9 | platform: 10 | - x64 11 | 12 | # http://www.appveyor.com/docs/installed-software 13 | install: 14 | # some helpful output for debugging builds 15 | - go version 16 | - go env 17 | # pre-installed MinGW at C:\MinGW is 32bit only 18 | # but MSYS2 at C:\msys64 has mingw64 19 | - set PATH=C:\msys64\mingw64\bin;%PATH% 20 | - gcc --version 21 | - g++ --version 22 | 23 | build_script: 24 | - go install -v ./... 25 | 26 | test_script: 27 | - set PATH=C:\gopath\bin;%PATH% 28 | - go test -v ./... 29 | 30 | #artifacts: 31 | # - path: '%GOPATH%\bin\*.exe' 32 | deploy: off 33 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package errors 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | 9 | stderrors "errors" 10 | ) 11 | 12 | func noErrors(at, depth int) error { 13 | if at >= depth { 14 | return stderrors.New("no error") 15 | } 16 | return noErrors(at+1, depth) 17 | } 18 | 19 | func yesErrors(at, depth int) error { 20 | if at >= depth { 21 | return New("ye error") 22 | } 23 | return yesErrors(at+1, depth) 24 | } 25 | 26 | // GlobalE is an exported global to store the result of benchmark results, 27 | // preventing the compiler from optimising the benchmark functions away. 28 | var GlobalE interface{} 29 | 30 | func BenchmarkErrors(b *testing.B) { 31 | type run struct { 32 | stack int 33 | std bool 34 | } 35 | runs := []run{ 36 | {10, false}, 37 | {10, true}, 38 | {100, false}, 39 | {100, true}, 40 | {1000, false}, 41 | {1000, true}, 42 | } 43 | for _, r := range runs { 44 | part := "pkg/errors" 45 | if r.std { 46 | part = "errors" 47 | } 48 | name := fmt.Sprintf("%s-stack-%d", part, r.stack) 49 | b.Run(name, func(b *testing.B) { 50 | var err error 51 | f := yesErrors 52 | if r.std { 53 | f = noErrors 54 | } 55 | b.ReportAllocs() 56 | for i := 0; i < b.N; i++ { 57 | err = f(0, r.stack) 58 | } 59 | b.StopTimer() 60 | GlobalE = err 61 | }) 62 | } 63 | } 64 | 65 | func BenchmarkStackFormatting(b *testing.B) { 66 | type run struct { 67 | stack int 68 | format string 69 | } 70 | runs := []run{ 71 | {10, "%s"}, 72 | {10, "%v"}, 73 | {10, "%+v"}, 74 | {30, "%s"}, 75 | {30, "%v"}, 76 | {30, "%+v"}, 77 | {60, "%s"}, 78 | {60, "%v"}, 79 | {60, "%+v"}, 80 | } 81 | 82 | var stackStr string 83 | for _, r := range runs { 84 | name := fmt.Sprintf("%s-stack-%d", r.format, r.stack) 85 | b.Run(name, func(b *testing.B) { 86 | err := yesErrors(0, r.stack) 87 | b.ReportAllocs() 88 | b.ResetTimer() 89 | for i := 0; i < b.N; i++ { 90 | stackStr = fmt.Sprintf(r.format, err) 91 | } 92 | b.StopTimer() 93 | }) 94 | } 95 | 96 | for _, r := range runs { 97 | name := fmt.Sprintf("%s-stacktrace-%d", r.format, r.stack) 98 | b.Run(name, func(b *testing.B) { 99 | err := yesErrors(0, r.stack) 100 | st := err.(*fundamental).stack.StackTrace() 101 | b.ReportAllocs() 102 | b.ResetTimer() 103 | for i := 0; i < b.N; i++ { 104 | stackStr = fmt.Sprintf(r.format, st) 105 | } 106 | b.StopTimer() 107 | }) 108 | } 109 | GlobalE = stackStr 110 | } 111 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides simple error handling primitives. 2 | // 3 | // The traditional error handling idiom in Go is roughly akin to 4 | // 5 | // if err != nil { 6 | // return err 7 | // } 8 | // 9 | // which when applied recursively up the call stack results in error reports 10 | // without context or debugging information. The errors package allows 11 | // programmers to add context to the failure path in their code in a way 12 | // that does not destroy the original value of the error. 13 | // 14 | // Adding context to an error 15 | // 16 | // The errors.Wrap function returns a new error that adds context to the 17 | // original error by recording a stack trace at the point Wrap is called, 18 | // together with the supplied message. For example 19 | // 20 | // _, err := ioutil.ReadAll(r) 21 | // if err != nil { 22 | // return errors.Wrap(err, "read failed") 23 | // } 24 | // 25 | // If additional control is required, the errors.WithStack and 26 | // errors.WithMessage functions destructure errors.Wrap into its component 27 | // operations: annotating an error with a stack trace and with a message, 28 | // respectively. 29 | // 30 | // Retrieving the cause of an error 31 | // 32 | // Using errors.Wrap constructs a stack of errors, adding context to the 33 | // preceding error. Depending on the nature of the error it may be necessary 34 | // to reverse the operation of errors.Wrap to retrieve the original error 35 | // for inspection. Any error value which implements this interface 36 | // 37 | // type causer interface { 38 | // Cause() error 39 | // } 40 | // 41 | // can be inspected by errors.Cause. errors.Cause will recursively retrieve 42 | // the topmost error that does not implement causer, which is assumed to be 43 | // the original cause. For example: 44 | // 45 | // switch err := errors.Cause(err).(type) { 46 | // case *MyError: 47 | // // handle specifically 48 | // default: 49 | // // unknown error 50 | // } 51 | // 52 | // Although the causer interface is not exported by this package, it is 53 | // considered a part of its stable public interface. 54 | // 55 | // Formatted printing of errors 56 | // 57 | // All error values returned from this package implement fmt.Formatter and can 58 | // be formatted by the fmt package. The following verbs are supported: 59 | // 60 | // %s print the error. If the error has a Cause it will be 61 | // printed recursively. 62 | // %v see %s 63 | // %+v extended format. Each Frame of the error's StackTrace will 64 | // be printed in detail. 65 | // 66 | // Retrieving the stack trace of an error or wrapper 67 | // 68 | // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are 69 | // invoked. This information can be retrieved with the following interface: 70 | // 71 | // type stackTracer interface { 72 | // StackTrace() errors.StackTrace 73 | // } 74 | // 75 | // The returned errors.StackTrace type is defined as 76 | // 77 | // type StackTrace []Frame 78 | // 79 | // The Frame type represents a call site in the stack trace. Frame supports 80 | // the fmt.Formatter interface that can be used for printing information about 81 | // the stack trace of this error. For example: 82 | // 83 | // if err, ok := err.(stackTracer); ok { 84 | // for _, f := range err.StackTrace() { 85 | // fmt.Printf("%+s:%d\n", f, f) 86 | // } 87 | // } 88 | // 89 | // Although the stackTracer interface is not exported by this package, it is 90 | // considered a part of its stable public interface. 91 | // 92 | // See the documentation for Frame.Format for more details. 93 | package errors 94 | 95 | import ( 96 | "fmt" 97 | "io" 98 | ) 99 | 100 | // New returns an error with the supplied message. 101 | // New also records the stack trace at the point it was called. 102 | func New(message string) error { 103 | return &fundamental{ 104 | msg: message, 105 | stack: callers(), 106 | } 107 | } 108 | 109 | // Errorf formats according to a format specifier and returns the string 110 | // as a value that satisfies error. 111 | // Errorf also records the stack trace at the point it was called. 112 | func Errorf(format string, args ...interface{}) error { 113 | return &fundamental{ 114 | msg: fmt.Sprintf(format, args...), 115 | stack: callers(), 116 | } 117 | } 118 | 119 | // fundamental is an error that has a message and a stack, but no caller. 120 | type fundamental struct { 121 | msg string 122 | *stack 123 | } 124 | 125 | func (f *fundamental) Error() string { return f.msg } 126 | 127 | func (f *fundamental) Format(s fmt.State, verb rune) { 128 | switch verb { 129 | case 'v': 130 | if s.Flag('+') { 131 | io.WriteString(s, f.msg) 132 | f.stack.Format(s, verb) 133 | return 134 | } 135 | fallthrough 136 | case 's': 137 | io.WriteString(s, f.msg) 138 | case 'q': 139 | fmt.Fprintf(s, "%q", f.msg) 140 | } 141 | } 142 | 143 | // WithStack annotates err with a stack trace at the point WithStack was called. 144 | // If err is nil, WithStack returns nil. 145 | func WithStack(err error) error { 146 | if err == nil { 147 | return nil 148 | } 149 | return &withStack{ 150 | err, 151 | callers(), 152 | } 153 | } 154 | 155 | type withStack struct { 156 | error 157 | *stack 158 | } 159 | 160 | func (w *withStack) Cause() error { return w.error } 161 | 162 | // Unwrap provides compatibility for Go 1.13 error chains. 163 | func (w *withStack) Unwrap() error { return w.error } 164 | 165 | func (w *withStack) Format(s fmt.State, verb rune) { 166 | switch verb { 167 | case 'v': 168 | if s.Flag('+') { 169 | fmt.Fprintf(s, "%+v", w.Cause()) 170 | w.stack.Format(s, verb) 171 | return 172 | } 173 | fallthrough 174 | case 's': 175 | io.WriteString(s, w.Error()) 176 | case 'q': 177 | fmt.Fprintf(s, "%q", w.Error()) 178 | } 179 | } 180 | 181 | // Wrap returns an error annotating err with a stack trace 182 | // at the point Wrap is called, and the supplied message. 183 | // If err is nil, Wrap returns nil. 184 | func Wrap(err error, message string) error { 185 | if err == nil { 186 | return nil 187 | } 188 | err = &withMessage{ 189 | cause: err, 190 | msg: message, 191 | } 192 | return &withStack{ 193 | err, 194 | callers(), 195 | } 196 | } 197 | 198 | // Wrapf returns an error annotating err with a stack trace 199 | // at the point Wrapf is called, and the format specifier. 200 | // If err is nil, Wrapf returns nil. 201 | func Wrapf(err error, format string, args ...interface{}) error { 202 | if err == nil { 203 | return nil 204 | } 205 | err = &withMessage{ 206 | cause: err, 207 | msg: fmt.Sprintf(format, args...), 208 | } 209 | return &withStack{ 210 | err, 211 | callers(), 212 | } 213 | } 214 | 215 | // WithMessage annotates err with a new message. 216 | // If err is nil, WithMessage returns nil. 217 | func WithMessage(err error, message string) error { 218 | if err == nil { 219 | return nil 220 | } 221 | return &withMessage{ 222 | cause: err, 223 | msg: message, 224 | } 225 | } 226 | 227 | // WithMessagef annotates err with the format specifier. 228 | // If err is nil, WithMessagef returns nil. 229 | func WithMessagef(err error, format string, args ...interface{}) error { 230 | if err == nil { 231 | return nil 232 | } 233 | return &withMessage{ 234 | cause: err, 235 | msg: fmt.Sprintf(format, args...), 236 | } 237 | } 238 | 239 | type withMessage struct { 240 | cause error 241 | msg string 242 | } 243 | 244 | func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } 245 | func (w *withMessage) Cause() error { return w.cause } 246 | 247 | // Unwrap provides compatibility for Go 1.13 error chains. 248 | func (w *withMessage) Unwrap() error { return w.cause } 249 | 250 | func (w *withMessage) Format(s fmt.State, verb rune) { 251 | switch verb { 252 | case 'v': 253 | if s.Flag('+') { 254 | fmt.Fprintf(s, "%+v\n", w.Cause()) 255 | io.WriteString(s, w.msg) 256 | return 257 | } 258 | fallthrough 259 | case 's', 'q': 260 | io.WriteString(s, w.Error()) 261 | } 262 | } 263 | 264 | // Cause returns the underlying cause of the error, if possible. 265 | // An error value has a cause if it implements the following 266 | // interface: 267 | // 268 | // type causer interface { 269 | // Cause() error 270 | // } 271 | // 272 | // If the error does not implement Cause, the original error will 273 | // be returned. If the error is nil, nil will be returned without further 274 | // investigation. 275 | func Cause(err error) error { 276 | type causer interface { 277 | Cause() error 278 | } 279 | 280 | for err != nil { 281 | cause, ok := err.(causer) 282 | if !ok { 283 | break 284 | } 285 | err = cause.Cause() 286 | } 287 | return err 288 | } 289 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | tests := []struct { 13 | err string 14 | want error 15 | }{ 16 | {"", fmt.Errorf("")}, 17 | {"foo", fmt.Errorf("foo")}, 18 | {"foo", New("foo")}, 19 | {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, 20 | } 21 | 22 | for _, tt := range tests { 23 | got := New(tt.err) 24 | if got.Error() != tt.want.Error() { 25 | t.Errorf("New.Error(): got: %q, want %q", got, tt.want) 26 | } 27 | } 28 | } 29 | 30 | func TestWrapNil(t *testing.T) { 31 | got := Wrap(nil, "no error") 32 | if got != nil { 33 | t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) 34 | } 35 | } 36 | 37 | func TestWrap(t *testing.T) { 38 | tests := []struct { 39 | err error 40 | message string 41 | want string 42 | }{ 43 | {io.EOF, "read error", "read error: EOF"}, 44 | {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, 45 | } 46 | 47 | for _, tt := range tests { 48 | got := Wrap(tt.err, tt.message).Error() 49 | if got != tt.want { 50 | t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) 51 | } 52 | } 53 | } 54 | 55 | type nilError struct{} 56 | 57 | func (nilError) Error() string { return "nil error" } 58 | 59 | func TestCause(t *testing.T) { 60 | x := New("error") 61 | tests := []struct { 62 | err error 63 | want error 64 | }{{ 65 | // nil error is nil 66 | err: nil, 67 | want: nil, 68 | }, { 69 | // explicit nil error is nil 70 | err: (error)(nil), 71 | want: nil, 72 | }, { 73 | // typed nil is nil 74 | err: (*nilError)(nil), 75 | want: (*nilError)(nil), 76 | }, { 77 | // uncaused error is unaffected 78 | err: io.EOF, 79 | want: io.EOF, 80 | }, { 81 | // caused error returns cause 82 | err: Wrap(io.EOF, "ignored"), 83 | want: io.EOF, 84 | }, { 85 | err: x, // return from errors.New 86 | want: x, 87 | }, { 88 | WithMessage(nil, "whoops"), 89 | nil, 90 | }, { 91 | WithMessage(io.EOF, "whoops"), 92 | io.EOF, 93 | }, { 94 | WithStack(nil), 95 | nil, 96 | }, { 97 | WithStack(io.EOF), 98 | io.EOF, 99 | }} 100 | 101 | for i, tt := range tests { 102 | got := Cause(tt.err) 103 | if !reflect.DeepEqual(got, tt.want) { 104 | t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) 105 | } 106 | } 107 | } 108 | 109 | func TestWrapfNil(t *testing.T) { 110 | got := Wrapf(nil, "no error") 111 | if got != nil { 112 | t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) 113 | } 114 | } 115 | 116 | func TestWrapf(t *testing.T) { 117 | tests := []struct { 118 | err error 119 | message string 120 | want string 121 | }{ 122 | {io.EOF, "read error", "read error: EOF"}, 123 | {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, 124 | {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, 125 | } 126 | 127 | for _, tt := range tests { 128 | got := Wrapf(tt.err, tt.message).Error() 129 | if got != tt.want { 130 | t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) 131 | } 132 | } 133 | } 134 | 135 | func TestErrorf(t *testing.T) { 136 | tests := []struct { 137 | err error 138 | want string 139 | }{ 140 | {Errorf("read error without format specifiers"), "read error without format specifiers"}, 141 | {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, 142 | } 143 | 144 | for _, tt := range tests { 145 | got := tt.err.Error() 146 | if got != tt.want { 147 | t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) 148 | } 149 | } 150 | } 151 | 152 | func TestWithStackNil(t *testing.T) { 153 | got := WithStack(nil) 154 | if got != nil { 155 | t.Errorf("WithStack(nil): got %#v, expected nil", got) 156 | } 157 | } 158 | 159 | func TestWithStack(t *testing.T) { 160 | tests := []struct { 161 | err error 162 | want string 163 | }{ 164 | {io.EOF, "EOF"}, 165 | {WithStack(io.EOF), "EOF"}, 166 | } 167 | 168 | for _, tt := range tests { 169 | got := WithStack(tt.err).Error() 170 | if got != tt.want { 171 | t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want) 172 | } 173 | } 174 | } 175 | 176 | func TestWithMessageNil(t *testing.T) { 177 | got := WithMessage(nil, "no error") 178 | if got != nil { 179 | t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) 180 | } 181 | } 182 | 183 | func TestWithMessage(t *testing.T) { 184 | tests := []struct { 185 | err error 186 | message string 187 | want string 188 | }{ 189 | {io.EOF, "read error", "read error: EOF"}, 190 | {WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"}, 191 | } 192 | 193 | for _, tt := range tests { 194 | got := WithMessage(tt.err, tt.message).Error() 195 | if got != tt.want { 196 | t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) 197 | } 198 | } 199 | } 200 | 201 | func TestWithMessagefNil(t *testing.T) { 202 | got := WithMessagef(nil, "no error") 203 | if got != nil { 204 | t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) 205 | } 206 | } 207 | 208 | func TestWithMessagef(t *testing.T) { 209 | tests := []struct { 210 | err error 211 | message string 212 | want string 213 | }{ 214 | {io.EOF, "read error", "read error: EOF"}, 215 | {WithMessagef(io.EOF, "read error without format specifier"), "client error", "client error: read error without format specifier: EOF"}, 216 | {WithMessagef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, 217 | } 218 | 219 | for _, tt := range tests { 220 | got := WithMessagef(tt.err, tt.message).Error() 221 | if got != tt.want { 222 | t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) 223 | } 224 | } 225 | } 226 | 227 | // errors.New, etc values are not expected to be compared by value 228 | // but the change in errors#27 made them incomparable. Assert that 229 | // various kinds of errors have a functional equality operator, even 230 | // if the result of that equality is always false. 231 | func TestErrorEquality(t *testing.T) { 232 | vals := []error{ 233 | nil, 234 | io.EOF, 235 | errors.New("EOF"), 236 | New("EOF"), 237 | Errorf("EOF"), 238 | Wrap(io.EOF, "EOF"), 239 | Wrapf(io.EOF, "EOF%d", 2), 240 | WithMessage(nil, "whoops"), 241 | WithMessage(io.EOF, "whoops"), 242 | WithStack(io.EOF), 243 | WithStack(nil), 244 | } 245 | 246 | for i := range vals { 247 | for j := range vals { 248 | _ = vals[i] == vals[j] // mustn't panic 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package errors_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func ExampleNew() { 10 | err := errors.New("whoops") 11 | fmt.Println(err) 12 | 13 | // Output: whoops 14 | } 15 | 16 | func ExampleNew_printf() { 17 | err := errors.New("whoops") 18 | fmt.Printf("%+v", err) 19 | 20 | // Example output: 21 | // whoops 22 | // github.com/pkg/errors_test.ExampleNew_printf 23 | // /home/dfc/src/github.com/pkg/errors/example_test.go:17 24 | // testing.runExample 25 | // /home/dfc/go/src/testing/example.go:114 26 | // testing.RunExamples 27 | // /home/dfc/go/src/testing/example.go:38 28 | // testing.(*M).Run 29 | // /home/dfc/go/src/testing/testing.go:744 30 | // main.main 31 | // /github.com/pkg/errors/_test/_testmain.go:106 32 | // runtime.main 33 | // /home/dfc/go/src/runtime/proc.go:183 34 | // runtime.goexit 35 | // /home/dfc/go/src/runtime/asm_amd64.s:2059 36 | } 37 | 38 | func ExampleWithMessage() { 39 | cause := errors.New("whoops") 40 | err := errors.WithMessage(cause, "oh noes") 41 | fmt.Println(err) 42 | 43 | // Output: oh noes: whoops 44 | } 45 | 46 | func ExampleWithStack() { 47 | cause := errors.New("whoops") 48 | err := errors.WithStack(cause) 49 | fmt.Println(err) 50 | 51 | // Output: whoops 52 | } 53 | 54 | func ExampleWithStack_printf() { 55 | cause := errors.New("whoops") 56 | err := errors.WithStack(cause) 57 | fmt.Printf("%+v", err) 58 | 59 | // Example Output: 60 | // whoops 61 | // github.com/pkg/errors_test.ExampleWithStack_printf 62 | // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55 63 | // testing.runExample 64 | // /usr/lib/go/src/testing/example.go:114 65 | // testing.RunExamples 66 | // /usr/lib/go/src/testing/example.go:38 67 | // testing.(*M).Run 68 | // /usr/lib/go/src/testing/testing.go:744 69 | // main.main 70 | // github.com/pkg/errors/_test/_testmain.go:106 71 | // runtime.main 72 | // /usr/lib/go/src/runtime/proc.go:183 73 | // runtime.goexit 74 | // /usr/lib/go/src/runtime/asm_amd64.s:2086 75 | // github.com/pkg/errors_test.ExampleWithStack_printf 76 | // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56 77 | // testing.runExample 78 | // /usr/lib/go/src/testing/example.go:114 79 | // testing.RunExamples 80 | // /usr/lib/go/src/testing/example.go:38 81 | // testing.(*M).Run 82 | // /usr/lib/go/src/testing/testing.go:744 83 | // main.main 84 | // github.com/pkg/errors/_test/_testmain.go:106 85 | // runtime.main 86 | // /usr/lib/go/src/runtime/proc.go:183 87 | // runtime.goexit 88 | // /usr/lib/go/src/runtime/asm_amd64.s:2086 89 | } 90 | 91 | func ExampleWrap() { 92 | cause := errors.New("whoops") 93 | err := errors.Wrap(cause, "oh noes") 94 | fmt.Println(err) 95 | 96 | // Output: oh noes: whoops 97 | } 98 | 99 | func fn() error { 100 | e1 := errors.New("error") 101 | e2 := errors.Wrap(e1, "inner") 102 | e3 := errors.Wrap(e2, "middle") 103 | return errors.Wrap(e3, "outer") 104 | } 105 | 106 | func ExampleCause() { 107 | err := fn() 108 | fmt.Println(err) 109 | fmt.Println(errors.Cause(err)) 110 | 111 | // Output: outer: middle: inner: error 112 | // error 113 | } 114 | 115 | func ExampleWrap_extended() { 116 | err := fn() 117 | fmt.Printf("%+v\n", err) 118 | 119 | // Example output: 120 | // error 121 | // github.com/pkg/errors_test.fn 122 | // /home/dfc/src/github.com/pkg/errors/example_test.go:47 123 | // github.com/pkg/errors_test.ExampleCause_printf 124 | // /home/dfc/src/github.com/pkg/errors/example_test.go:63 125 | // testing.runExample 126 | // /home/dfc/go/src/testing/example.go:114 127 | // testing.RunExamples 128 | // /home/dfc/go/src/testing/example.go:38 129 | // testing.(*M).Run 130 | // /home/dfc/go/src/testing/testing.go:744 131 | // main.main 132 | // /github.com/pkg/errors/_test/_testmain.go:104 133 | // runtime.main 134 | // /home/dfc/go/src/runtime/proc.go:183 135 | // runtime.goexit 136 | // /home/dfc/go/src/runtime/asm_amd64.s:2059 137 | // github.com/pkg/errors_test.fn 138 | // /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner 139 | // github.com/pkg/errors_test.fn 140 | // /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle 141 | // github.com/pkg/errors_test.fn 142 | // /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer 143 | } 144 | 145 | func ExampleWrapf() { 146 | cause := errors.New("whoops") 147 | err := errors.Wrapf(cause, "oh noes #%d", 2) 148 | fmt.Println(err) 149 | 150 | // Output: oh noes #2: whoops 151 | } 152 | 153 | func ExampleErrorf_extended() { 154 | err := errors.Errorf("whoops: %s", "foo") 155 | fmt.Printf("%+v", err) 156 | 157 | // Example output: 158 | // whoops: foo 159 | // github.com/pkg/errors_test.ExampleErrorf 160 | // /home/dfc/src/github.com/pkg/errors/example_test.go:101 161 | // testing.runExample 162 | // /home/dfc/go/src/testing/example.go:114 163 | // testing.RunExamples 164 | // /home/dfc/go/src/testing/example.go:38 165 | // testing.(*M).Run 166 | // /home/dfc/go/src/testing/testing.go:744 167 | // main.main 168 | // /github.com/pkg/errors/_test/_testmain.go:102 169 | // runtime.main 170 | // /home/dfc/go/src/runtime/proc.go:183 171 | // runtime.goexit 172 | // /home/dfc/go/src/runtime/asm_amd64.s:2059 173 | } 174 | 175 | func Example_stackTrace() { 176 | type stackTracer interface { 177 | StackTrace() errors.StackTrace 178 | } 179 | 180 | err, ok := errors.Cause(fn()).(stackTracer) 181 | if !ok { 182 | panic("oops, err does not implement stackTracer") 183 | } 184 | 185 | st := err.StackTrace() 186 | fmt.Printf("%+v", st[0:2]) // top two frames 187 | 188 | // Example output: 189 | // github.com/pkg/errors_test.fn 190 | // /home/dfc/src/github.com/pkg/errors/example_test.go:47 191 | // github.com/pkg/errors_test.Example_stackTrace 192 | // /home/dfc/src/github.com/pkg/errors/example_test.go:127 193 | } 194 | 195 | func ExampleCause_printf() { 196 | err := errors.Wrap(func() error { 197 | return func() error { 198 | return errors.New("hello world") 199 | }() 200 | }(), "failed") 201 | 202 | fmt.Printf("%v", err) 203 | 204 | // Output: failed: hello world 205 | } 206 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "regexp" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestFormatNew(t *testing.T) { 13 | tests := []struct { 14 | error 15 | format string 16 | want string 17 | }{{ 18 | New("error"), 19 | "%s", 20 | "error", 21 | }, { 22 | New("error"), 23 | "%v", 24 | "error", 25 | }, { 26 | New("error"), 27 | "%+v", 28 | "error\n" + 29 | "github.com/pkg/errors.TestFormatNew\n" + 30 | "\t.+/github.com/pkg/errors/format_test.go:26", 31 | }, { 32 | New("error"), 33 | "%q", 34 | `"error"`, 35 | }} 36 | 37 | for i, tt := range tests { 38 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 39 | } 40 | } 41 | 42 | func TestFormatErrorf(t *testing.T) { 43 | tests := []struct { 44 | error 45 | format string 46 | want string 47 | }{{ 48 | Errorf("%s", "error"), 49 | "%s", 50 | "error", 51 | }, { 52 | Errorf("%s", "error"), 53 | "%v", 54 | "error", 55 | }, { 56 | Errorf("%s", "error"), 57 | "%+v", 58 | "error\n" + 59 | "github.com/pkg/errors.TestFormatErrorf\n" + 60 | "\t.+/github.com/pkg/errors/format_test.go:56", 61 | }} 62 | 63 | for i, tt := range tests { 64 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 65 | } 66 | } 67 | 68 | func TestFormatWrap(t *testing.T) { 69 | tests := []struct { 70 | error 71 | format string 72 | want string 73 | }{{ 74 | Wrap(New("error"), "error2"), 75 | "%s", 76 | "error2: error", 77 | }, { 78 | Wrap(New("error"), "error2"), 79 | "%v", 80 | "error2: error", 81 | }, { 82 | Wrap(New("error"), "error2"), 83 | "%+v", 84 | "error\n" + 85 | "github.com/pkg/errors.TestFormatWrap\n" + 86 | "\t.+/github.com/pkg/errors/format_test.go:82", 87 | }, { 88 | Wrap(io.EOF, "error"), 89 | "%s", 90 | "error: EOF", 91 | }, { 92 | Wrap(io.EOF, "error"), 93 | "%v", 94 | "error: EOF", 95 | }, { 96 | Wrap(io.EOF, "error"), 97 | "%+v", 98 | "EOF\n" + 99 | "error\n" + 100 | "github.com/pkg/errors.TestFormatWrap\n" + 101 | "\t.+/github.com/pkg/errors/format_test.go:96", 102 | }, { 103 | Wrap(Wrap(io.EOF, "error1"), "error2"), 104 | "%+v", 105 | "EOF\n" + 106 | "error1\n" + 107 | "github.com/pkg/errors.TestFormatWrap\n" + 108 | "\t.+/github.com/pkg/errors/format_test.go:103\n", 109 | }, { 110 | Wrap(New("error with space"), "context"), 111 | "%q", 112 | `"context: error with space"`, 113 | }} 114 | 115 | for i, tt := range tests { 116 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 117 | } 118 | } 119 | 120 | func TestFormatWrapf(t *testing.T) { 121 | tests := []struct { 122 | error 123 | format string 124 | want string 125 | }{{ 126 | Wrapf(io.EOF, "error%d", 2), 127 | "%s", 128 | "error2: EOF", 129 | }, { 130 | Wrapf(io.EOF, "error%d", 2), 131 | "%v", 132 | "error2: EOF", 133 | }, { 134 | Wrapf(io.EOF, "error%d", 2), 135 | "%+v", 136 | "EOF\n" + 137 | "error2\n" + 138 | "github.com/pkg/errors.TestFormatWrapf\n" + 139 | "\t.+/github.com/pkg/errors/format_test.go:134", 140 | }, { 141 | Wrapf(New("error"), "error%d", 2), 142 | "%s", 143 | "error2: error", 144 | }, { 145 | Wrapf(New("error"), "error%d", 2), 146 | "%v", 147 | "error2: error", 148 | }, { 149 | Wrapf(New("error"), "error%d", 2), 150 | "%+v", 151 | "error\n" + 152 | "github.com/pkg/errors.TestFormatWrapf\n" + 153 | "\t.+/github.com/pkg/errors/format_test.go:149", 154 | }} 155 | 156 | for i, tt := range tests { 157 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 158 | } 159 | } 160 | 161 | func TestFormatWithStack(t *testing.T) { 162 | tests := []struct { 163 | error 164 | format string 165 | want []string 166 | }{{ 167 | WithStack(io.EOF), 168 | "%s", 169 | []string{"EOF"}, 170 | }, { 171 | WithStack(io.EOF), 172 | "%v", 173 | []string{"EOF"}, 174 | }, { 175 | WithStack(io.EOF), 176 | "%+v", 177 | []string{"EOF", 178 | "github.com/pkg/errors.TestFormatWithStack\n" + 179 | "\t.+/github.com/pkg/errors/format_test.go:175"}, 180 | }, { 181 | WithStack(New("error")), 182 | "%s", 183 | []string{"error"}, 184 | }, { 185 | WithStack(New("error")), 186 | "%v", 187 | []string{"error"}, 188 | }, { 189 | WithStack(New("error")), 190 | "%+v", 191 | []string{"error", 192 | "github.com/pkg/errors.TestFormatWithStack\n" + 193 | "\t.+/github.com/pkg/errors/format_test.go:189", 194 | "github.com/pkg/errors.TestFormatWithStack\n" + 195 | "\t.+/github.com/pkg/errors/format_test.go:189"}, 196 | }, { 197 | WithStack(WithStack(io.EOF)), 198 | "%+v", 199 | []string{"EOF", 200 | "github.com/pkg/errors.TestFormatWithStack\n" + 201 | "\t.+/github.com/pkg/errors/format_test.go:197", 202 | "github.com/pkg/errors.TestFormatWithStack\n" + 203 | "\t.+/github.com/pkg/errors/format_test.go:197"}, 204 | }, { 205 | WithStack(WithStack(Wrapf(io.EOF, "message"))), 206 | "%+v", 207 | []string{"EOF", 208 | "message", 209 | "github.com/pkg/errors.TestFormatWithStack\n" + 210 | "\t.+/github.com/pkg/errors/format_test.go:205", 211 | "github.com/pkg/errors.TestFormatWithStack\n" + 212 | "\t.+/github.com/pkg/errors/format_test.go:205", 213 | "github.com/pkg/errors.TestFormatWithStack\n" + 214 | "\t.+/github.com/pkg/errors/format_test.go:205"}, 215 | }, { 216 | WithStack(Errorf("error%d", 1)), 217 | "%+v", 218 | []string{"error1", 219 | "github.com/pkg/errors.TestFormatWithStack\n" + 220 | "\t.+/github.com/pkg/errors/format_test.go:216", 221 | "github.com/pkg/errors.TestFormatWithStack\n" + 222 | "\t.+/github.com/pkg/errors/format_test.go:216"}, 223 | }} 224 | 225 | for i, tt := range tests { 226 | testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) 227 | } 228 | } 229 | 230 | func TestFormatWithMessage(t *testing.T) { 231 | tests := []struct { 232 | error 233 | format string 234 | want []string 235 | }{{ 236 | WithMessage(New("error"), "error2"), 237 | "%s", 238 | []string{"error2: error"}, 239 | }, { 240 | WithMessage(New("error"), "error2"), 241 | "%v", 242 | []string{"error2: error"}, 243 | }, { 244 | WithMessage(New("error"), "error2"), 245 | "%+v", 246 | []string{ 247 | "error", 248 | "github.com/pkg/errors.TestFormatWithMessage\n" + 249 | "\t.+/github.com/pkg/errors/format_test.go:244", 250 | "error2"}, 251 | }, { 252 | WithMessage(io.EOF, "addition1"), 253 | "%s", 254 | []string{"addition1: EOF"}, 255 | }, { 256 | WithMessage(io.EOF, "addition1"), 257 | "%v", 258 | []string{"addition1: EOF"}, 259 | }, { 260 | WithMessage(io.EOF, "addition1"), 261 | "%+v", 262 | []string{"EOF", "addition1"}, 263 | }, { 264 | WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), 265 | "%v", 266 | []string{"addition2: addition1: EOF"}, 267 | }, { 268 | WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), 269 | "%+v", 270 | []string{"EOF", "addition1", "addition2"}, 271 | }, { 272 | Wrap(WithMessage(io.EOF, "error1"), "error2"), 273 | "%+v", 274 | []string{"EOF", "error1", "error2", 275 | "github.com/pkg/errors.TestFormatWithMessage\n" + 276 | "\t.+/github.com/pkg/errors/format_test.go:272"}, 277 | }, { 278 | WithMessage(Errorf("error%d", 1), "error2"), 279 | "%+v", 280 | []string{"error1", 281 | "github.com/pkg/errors.TestFormatWithMessage\n" + 282 | "\t.+/github.com/pkg/errors/format_test.go:278", 283 | "error2"}, 284 | }, { 285 | WithMessage(WithStack(io.EOF), "error"), 286 | "%+v", 287 | []string{ 288 | "EOF", 289 | "github.com/pkg/errors.TestFormatWithMessage\n" + 290 | "\t.+/github.com/pkg/errors/format_test.go:285", 291 | "error"}, 292 | }, { 293 | WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"), 294 | "%+v", 295 | []string{ 296 | "EOF", 297 | "github.com/pkg/errors.TestFormatWithMessage\n" + 298 | "\t.+/github.com/pkg/errors/format_test.go:293", 299 | "inside-error", 300 | "github.com/pkg/errors.TestFormatWithMessage\n" + 301 | "\t.+/github.com/pkg/errors/format_test.go:293", 302 | "outside-error"}, 303 | }} 304 | 305 | for i, tt := range tests { 306 | testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) 307 | } 308 | } 309 | 310 | func TestFormatGeneric(t *testing.T) { 311 | starts := []struct { 312 | err error 313 | want []string 314 | }{ 315 | {New("new-error"), []string{ 316 | "new-error", 317 | "github.com/pkg/errors.TestFormatGeneric\n" + 318 | "\t.+/github.com/pkg/errors/format_test.go:315"}, 319 | }, {Errorf("errorf-error"), []string{ 320 | "errorf-error", 321 | "github.com/pkg/errors.TestFormatGeneric\n" + 322 | "\t.+/github.com/pkg/errors/format_test.go:319"}, 323 | }, {errors.New("errors-new-error"), []string{ 324 | "errors-new-error"}, 325 | }, 326 | } 327 | 328 | wrappers := []wrapper{ 329 | { 330 | func(err error) error { return WithMessage(err, "with-message") }, 331 | []string{"with-message"}, 332 | }, { 333 | func(err error) error { return WithStack(err) }, 334 | []string{ 335 | "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" + 336 | ".+/github.com/pkg/errors/format_test.go:333", 337 | }, 338 | }, { 339 | func(err error) error { return Wrap(err, "wrap-error") }, 340 | []string{ 341 | "wrap-error", 342 | "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" + 343 | ".+/github.com/pkg/errors/format_test.go:339", 344 | }, 345 | }, { 346 | func(err error) error { return Wrapf(err, "wrapf-error%d", 1) }, 347 | []string{ 348 | "wrapf-error1", 349 | "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" + 350 | ".+/github.com/pkg/errors/format_test.go:346", 351 | }, 352 | }, 353 | } 354 | 355 | for s := range starts { 356 | err := starts[s].err 357 | want := starts[s].want 358 | testFormatCompleteCompare(t, s, err, "%+v", want, false) 359 | testGenericRecursive(t, err, want, wrappers, 3) 360 | } 361 | } 362 | 363 | func wrappedNew(message string) error { // This function will be mid-stack inlined in go 1.12+ 364 | return New(message) 365 | } 366 | 367 | func TestFormatWrappedNew(t *testing.T) { 368 | tests := []struct { 369 | error 370 | format string 371 | want string 372 | }{{ 373 | wrappedNew("error"), 374 | "%+v", 375 | "error\n" + 376 | "github.com/pkg/errors.wrappedNew\n" + 377 | "\t.+/github.com/pkg/errors/format_test.go:364\n" + 378 | "github.com/pkg/errors.TestFormatWrappedNew\n" + 379 | "\t.+/github.com/pkg/errors/format_test.go:373", 380 | }} 381 | 382 | for i, tt := range tests { 383 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 384 | } 385 | } 386 | 387 | func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { 388 | t.Helper() 389 | got := fmt.Sprintf(format, arg) 390 | gotLines := strings.SplitN(got, "\n", -1) 391 | wantLines := strings.SplitN(want, "\n", -1) 392 | 393 | if len(wantLines) > len(gotLines) { 394 | t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want) 395 | return 396 | } 397 | 398 | for i, w := range wantLines { 399 | match, err := regexp.MatchString(w, gotLines[i]) 400 | if err != nil { 401 | t.Fatal(err) 402 | } 403 | if !match { 404 | t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want) 405 | } 406 | } 407 | } 408 | 409 | var stackLineR = regexp.MustCompile(`\.`) 410 | 411 | // parseBlocks parses input into a slice, where: 412 | // - incase entry contains a newline, its a stacktrace 413 | // - incase entry contains no newline, its a solo line. 414 | // 415 | // Detecting stack boundaries only works incase the WithStack-calls are 416 | // to be found on the same line, thats why it is optionally here. 417 | // 418 | // Example use: 419 | // 420 | // for _, e := range blocks { 421 | // if strings.ContainsAny(e, "\n") { 422 | // // Match as stack 423 | // } else { 424 | // // Match as line 425 | // } 426 | // } 427 | // 428 | func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { 429 | var blocks []string 430 | 431 | stack := "" 432 | wasStack := false 433 | lines := map[string]bool{} // already found lines 434 | 435 | for _, l := range strings.Split(input, "\n") { 436 | isStackLine := stackLineR.MatchString(l) 437 | 438 | switch { 439 | case !isStackLine && wasStack: 440 | blocks = append(blocks, stack, l) 441 | stack = "" 442 | lines = map[string]bool{} 443 | case isStackLine: 444 | if wasStack { 445 | // Detecting two stacks after another, possible cause lines match in 446 | // our tests due to WithStack(WithStack(io.EOF)) on same line. 447 | if detectStackboundaries { 448 | if lines[l] { 449 | if len(stack) == 0 { 450 | return nil, errors.New("len of block must not be zero here") 451 | } 452 | 453 | blocks = append(blocks, stack) 454 | stack = l 455 | lines = map[string]bool{l: true} 456 | continue 457 | } 458 | } 459 | 460 | stack = stack + "\n" + l 461 | } else { 462 | stack = l 463 | } 464 | lines[l] = true 465 | case !isStackLine && !wasStack: 466 | blocks = append(blocks, l) 467 | default: 468 | return nil, errors.New("must not happen") 469 | } 470 | 471 | wasStack = isStackLine 472 | } 473 | 474 | // Use up stack 475 | if stack != "" { 476 | blocks = append(blocks, stack) 477 | } 478 | return blocks, nil 479 | } 480 | 481 | func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) { 482 | gotStr := fmt.Sprintf(format, arg) 483 | 484 | got, err := parseBlocks(gotStr, detectStackBoundaries) 485 | if err != nil { 486 | t.Fatal(err) 487 | } 488 | 489 | if len(got) != len(want) { 490 | t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q", 491 | n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr) 492 | } 493 | 494 | for i := range got { 495 | if strings.ContainsAny(want[i], "\n") { 496 | // Match as stack 497 | match, err := regexp.MatchString(want[i], got[i]) 498 | if err != nil { 499 | t.Fatal(err) 500 | } 501 | if !match { 502 | 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", 503 | n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want)) 504 | } 505 | } else { 506 | // Match as message 507 | if got[i] != want[i] { 508 | 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]) 509 | } 510 | } 511 | } 512 | } 513 | 514 | type wrapper struct { 515 | wrap func(err error) error 516 | want []string 517 | } 518 | 519 | func prettyBlocks(blocks []string) string { 520 | var out []string 521 | 522 | for _, b := range blocks { 523 | out = append(out, fmt.Sprintf("%v", b)) 524 | } 525 | 526 | return " " + strings.Join(out, "\n ") 527 | } 528 | 529 | func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) { 530 | if len(beforeWant) == 0 { 531 | panic("beforeWant must not be empty") 532 | } 533 | for _, w := range list { 534 | if len(w.want) == 0 { 535 | panic("want must not be empty") 536 | } 537 | 538 | err := w.wrap(beforeErr) 539 | 540 | // Copy required cause append(beforeWant, ..) modified beforeWant subtly. 541 | beforeCopy := make([]string, len(beforeWant)) 542 | copy(beforeCopy, beforeWant) 543 | 544 | beforeWant := beforeCopy 545 | last := len(beforeWant) - 1 546 | var want []string 547 | 548 | // Merge two stacks behind each other. 549 | if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") { 550 | want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...) 551 | } else { 552 | want = append(beforeWant, w.want...) 553 | } 554 | 555 | testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false) 556 | if maxDepth > 0 { 557 | testGenericRecursive(t, err, want, list, maxDepth-1) 558 | } 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /go113.go: -------------------------------------------------------------------------------- 1 | // +build go1.13 2 | 3 | package errors 4 | 5 | import ( 6 | stderrors "errors" 7 | ) 8 | 9 | // Is reports whether any error in err's chain matches target. 10 | // 11 | // The chain consists of err itself followed by the sequence of errors obtained by 12 | // repeatedly calling Unwrap. 13 | // 14 | // An error is considered to match a target if it is equal to that target or if 15 | // it implements a method Is(error) bool such that Is(target) returns true. 16 | func Is(err, target error) bool { return stderrors.Is(err, target) } 17 | 18 | // As finds the first error in err's chain that matches target, and if so, sets 19 | // target to that error value and returns true. 20 | // 21 | // The chain consists of err itself followed by the sequence of errors obtained by 22 | // repeatedly calling Unwrap. 23 | // 24 | // An error matches target if the error's concrete value is assignable to the value 25 | // pointed to by target, or if the error has a method As(interface{}) bool such that 26 | // As(target) returns true. In the latter case, the As method is responsible for 27 | // setting target. 28 | // 29 | // As will panic if target is not a non-nil pointer to either a type that implements 30 | // error, or to any interface type. As returns false if err is nil. 31 | func As(err error, target interface{}) bool { return stderrors.As(err, target) } 32 | 33 | // Unwrap returns the result of calling the Unwrap method on err, if err's 34 | // type contains an Unwrap method returning error. 35 | // Otherwise, Unwrap returns nil. 36 | func Unwrap(err error) error { 37 | return stderrors.Unwrap(err) 38 | } 39 | -------------------------------------------------------------------------------- /go113_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.13 2 | 3 | package errors 4 | 5 | import ( 6 | stderrors "errors" 7 | "fmt" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestErrorChainCompat(t *testing.T) { 13 | err := stderrors.New("error that gets wrapped") 14 | wrapped := Wrap(err, "wrapped up") 15 | if !stderrors.Is(wrapped, err) { 16 | t.Errorf("Wrap does not support Go 1.13 error chains") 17 | } 18 | } 19 | 20 | func TestIs(t *testing.T) { 21 | err := New("test") 22 | 23 | type args struct { 24 | err error 25 | target error 26 | } 27 | tests := []struct { 28 | name string 29 | args args 30 | want bool 31 | }{ 32 | { 33 | name: "with stack", 34 | args: args{ 35 | err: WithStack(err), 36 | target: err, 37 | }, 38 | want: true, 39 | }, 40 | { 41 | name: "with message", 42 | args: args{ 43 | err: WithMessage(err, "test"), 44 | target: err, 45 | }, 46 | want: true, 47 | }, 48 | { 49 | name: "with message format", 50 | args: args{ 51 | err: WithMessagef(err, "%s", "test"), 52 | target: err, 53 | }, 54 | want: true, 55 | }, 56 | { 57 | name: "std errors compatibility", 58 | args: args{ 59 | err: fmt.Errorf("wrap it: %w", err), 60 | target: err, 61 | }, 62 | want: true, 63 | }, 64 | } 65 | for _, tt := range tests { 66 | t.Run(tt.name, func(t *testing.T) { 67 | if got := Is(tt.args.err, tt.args.target); got != tt.want { 68 | t.Errorf("Is() = %v, want %v", got, tt.want) 69 | } 70 | }) 71 | } 72 | } 73 | 74 | type customErr struct { 75 | msg string 76 | } 77 | 78 | func (c customErr) Error() string { return c.msg } 79 | 80 | func TestAs(t *testing.T) { 81 | var err = customErr{msg: "test message"} 82 | 83 | type args struct { 84 | err error 85 | target interface{} 86 | } 87 | tests := []struct { 88 | name string 89 | args args 90 | want bool 91 | }{ 92 | { 93 | name: "with stack", 94 | args: args{ 95 | err: WithStack(err), 96 | target: new(customErr), 97 | }, 98 | want: true, 99 | }, 100 | { 101 | name: "with message", 102 | args: args{ 103 | err: WithMessage(err, "test"), 104 | target: new(customErr), 105 | }, 106 | want: true, 107 | }, 108 | { 109 | name: "with message format", 110 | args: args{ 111 | err: WithMessagef(err, "%s", "test"), 112 | target: new(customErr), 113 | }, 114 | want: true, 115 | }, 116 | { 117 | name: "std errors compatibility", 118 | args: args{ 119 | err: fmt.Errorf("wrap it: %w", err), 120 | target: new(customErr), 121 | }, 122 | want: true, 123 | }, 124 | } 125 | for _, tt := range tests { 126 | t.Run(tt.name, func(t *testing.T) { 127 | if got := As(tt.args.err, tt.args.target); got != tt.want { 128 | t.Errorf("As() = %v, want %v", got, tt.want) 129 | } 130 | 131 | ce := tt.args.target.(*customErr) 132 | if !reflect.DeepEqual(err, *ce) { 133 | t.Errorf("set target error failed, target error is %v", *ce) 134 | } 135 | }) 136 | } 137 | } 138 | 139 | func TestUnwrap(t *testing.T) { 140 | err := New("test") 141 | 142 | type args struct { 143 | err error 144 | } 145 | tests := []struct { 146 | name string 147 | args args 148 | want error 149 | }{ 150 | { 151 | name: "with stack", 152 | args: args{err: WithStack(err)}, 153 | want: err, 154 | }, 155 | { 156 | name: "with message", 157 | args: args{err: WithMessage(err, "test")}, 158 | want: err, 159 | }, 160 | { 161 | name: "with message format", 162 | args: args{err: WithMessagef(err, "%s", "test")}, 163 | want: err, 164 | }, 165 | { 166 | name: "std errors compatibility", 167 | args: args{err: fmt.Errorf("wrap: %w", err)}, 168 | want: err, 169 | }, 170 | } 171 | for _, tt := range tests { 172 | t.Run(tt.name, func(t *testing.T) { 173 | if err := Unwrap(tt.args.err); !reflect.DeepEqual(err, tt.want) { 174 | t.Errorf("Unwrap() error = %v, want %v", err, tt.want) 175 | } 176 | }) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /json_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "encoding/json" 5 | "regexp" 6 | "testing" 7 | ) 8 | 9 | func TestFrameMarshalText(t *testing.T) { 10 | var tests = []struct { 11 | Frame 12 | want string 13 | }{{ 14 | initpc, 15 | `^github.com/pkg/errors\.init(\.ializers)? .+/github\.com/pkg/errors/stack_test.go:\d+$`, 16 | }, { 17 | 0, 18 | `^unknown$`, 19 | }} 20 | for i, tt := range tests { 21 | got, err := tt.Frame.MarshalText() 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if !regexp.MustCompile(tt.want).Match(got) { 26 | t.Errorf("test %d: MarshalJSON:\n got %q\n want %q", i+1, string(got), tt.want) 27 | } 28 | } 29 | } 30 | 31 | func TestFrameMarshalJSON(t *testing.T) { 32 | var tests = []struct { 33 | Frame 34 | want string 35 | }{{ 36 | initpc, 37 | `^"github\.com/pkg/errors\.init(\.ializers)? .+/github\.com/pkg/errors/stack_test.go:\d+"$`, 38 | }, { 39 | 0, 40 | `^"unknown"$`, 41 | }} 42 | for i, tt := range tests { 43 | got, err := json.Marshal(tt.Frame) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if !regexp.MustCompile(tt.want).Match(got) { 48 | t.Errorf("test %d: MarshalJSON:\n got %q\n want %q", i+1, string(got), tt.want) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path" 7 | "runtime" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // Frame represents a program counter inside a stack frame. 13 | // For historical reasons if Frame is interpreted as a uintptr 14 | // its value represents the program counter + 1. 15 | type Frame uintptr 16 | 17 | // pc returns the program counter for this frame; 18 | // multiple frames may have the same PC value. 19 | func (f Frame) pc() uintptr { return uintptr(f) - 1 } 20 | 21 | // file returns the full path to the file that contains the 22 | // function for this Frame's pc. 23 | func (f Frame) file() string { 24 | fn := runtime.FuncForPC(f.pc()) 25 | if fn == nil { 26 | return "unknown" 27 | } 28 | file, _ := fn.FileLine(f.pc()) 29 | return file 30 | } 31 | 32 | // line returns the line number of source code of the 33 | // function for this Frame's pc. 34 | func (f Frame) line() int { 35 | fn := runtime.FuncForPC(f.pc()) 36 | if fn == nil { 37 | return 0 38 | } 39 | _, line := fn.FileLine(f.pc()) 40 | return line 41 | } 42 | 43 | // name returns the name of this function, if known. 44 | func (f Frame) name() string { 45 | fn := runtime.FuncForPC(f.pc()) 46 | if fn == nil { 47 | return "unknown" 48 | } 49 | return fn.Name() 50 | } 51 | 52 | // Format formats the frame according to the fmt.Formatter interface. 53 | // 54 | // %s source file 55 | // %d source line 56 | // %n function name 57 | // %v equivalent to %s:%d 58 | // 59 | // Format accepts flags that alter the printing of some verbs, as follows: 60 | // 61 | // %+s function name and path of source file relative to the compile time 62 | // GOPATH separated by \n\t (\n\t) 63 | // %+v equivalent to %+s:%d 64 | func (f Frame) Format(s fmt.State, verb rune) { 65 | switch verb { 66 | case 's': 67 | switch { 68 | case s.Flag('+'): 69 | io.WriteString(s, f.name()) 70 | io.WriteString(s, "\n\t") 71 | io.WriteString(s, f.file()) 72 | default: 73 | io.WriteString(s, path.Base(f.file())) 74 | } 75 | case 'd': 76 | io.WriteString(s, strconv.Itoa(f.line())) 77 | case 'n': 78 | io.WriteString(s, funcname(f.name())) 79 | case 'v': 80 | f.Format(s, 's') 81 | io.WriteString(s, ":") 82 | f.Format(s, 'd') 83 | } 84 | } 85 | 86 | // MarshalText formats a stacktrace Frame as a text string. The output is the 87 | // same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. 88 | func (f Frame) MarshalText() ([]byte, error) { 89 | name := f.name() 90 | if name == "unknown" { 91 | return []byte(name), nil 92 | } 93 | return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil 94 | } 95 | 96 | // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). 97 | type StackTrace []Frame 98 | 99 | // Format formats the stack of Frames according to the fmt.Formatter interface. 100 | // 101 | // %s lists source files for each Frame in the stack 102 | // %v lists the source file and line number for each Frame in the stack 103 | // 104 | // Format accepts flags that alter the printing of some verbs, as follows: 105 | // 106 | // %+v Prints filename, function, and line number for each Frame in the stack. 107 | func (st StackTrace) Format(s fmt.State, verb rune) { 108 | switch verb { 109 | case 'v': 110 | switch { 111 | case s.Flag('+'): 112 | for _, f := range st { 113 | io.WriteString(s, "\n") 114 | f.Format(s, verb) 115 | } 116 | case s.Flag('#'): 117 | fmt.Fprintf(s, "%#v", []Frame(st)) 118 | default: 119 | st.formatSlice(s, verb) 120 | } 121 | case 's': 122 | st.formatSlice(s, verb) 123 | } 124 | } 125 | 126 | // formatSlice will format this StackTrace into the given buffer as a slice of 127 | // Frame, only valid when called with '%s' or '%v'. 128 | func (st StackTrace) formatSlice(s fmt.State, verb rune) { 129 | io.WriteString(s, "[") 130 | for i, f := range st { 131 | if i > 0 { 132 | io.WriteString(s, " ") 133 | } 134 | f.Format(s, verb) 135 | } 136 | io.WriteString(s, "]") 137 | } 138 | 139 | // stack represents a stack of program counters. 140 | type stack []uintptr 141 | 142 | func (s *stack) Format(st fmt.State, verb rune) { 143 | switch verb { 144 | case 'v': 145 | switch { 146 | case st.Flag('+'): 147 | for _, pc := range *s { 148 | f := Frame(pc) 149 | fmt.Fprintf(st, "\n%+v", f) 150 | } 151 | } 152 | } 153 | } 154 | 155 | func (s *stack) StackTrace() StackTrace { 156 | f := make([]Frame, len(*s)) 157 | for i := 0; i < len(f); i++ { 158 | f[i] = Frame((*s)[i]) 159 | } 160 | return f 161 | } 162 | 163 | func callers() *stack { 164 | const depth = 32 165 | var pcs [depth]uintptr 166 | n := runtime.Callers(3, pcs[:]) 167 | var st stack = pcs[0:n] 168 | return &st 169 | } 170 | 171 | // funcname removes the path prefix component of a function's name reported by func.Name(). 172 | func funcname(name string) string { 173 | i := strings.LastIndex(name, "/") 174 | name = name[i+1:] 175 | i = strings.Index(name, ".") 176 | return name[i+1:] 177 | } 178 | -------------------------------------------------------------------------------- /stack_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | var initpc = caller() 10 | 11 | type X struct{} 12 | 13 | // val returns a Frame pointing to itself. 14 | func (x X) val() Frame { 15 | return caller() 16 | } 17 | 18 | // ptr returns a Frame pointing to itself. 19 | func (x *X) ptr() Frame { 20 | return caller() 21 | } 22 | 23 | func TestFrameFormat(t *testing.T) { 24 | var tests = []struct { 25 | Frame 26 | format string 27 | want string 28 | }{{ 29 | initpc, 30 | "%s", 31 | "stack_test.go", 32 | }, { 33 | initpc, 34 | "%+s", 35 | "github.com/pkg/errors.init\n" + 36 | "\t.+/github.com/pkg/errors/stack_test.go", 37 | }, { 38 | 0, 39 | "%s", 40 | "unknown", 41 | }, { 42 | 0, 43 | "%+s", 44 | "unknown", 45 | }, { 46 | initpc, 47 | "%d", 48 | "9", 49 | }, { 50 | 0, 51 | "%d", 52 | "0", 53 | }, { 54 | initpc, 55 | "%n", 56 | "init", 57 | }, { 58 | func() Frame { 59 | var x X 60 | return x.ptr() 61 | }(), 62 | "%n", 63 | `\(\*X\).ptr`, 64 | }, { 65 | func() Frame { 66 | var x X 67 | return x.val() 68 | }(), 69 | "%n", 70 | "X.val", 71 | }, { 72 | 0, 73 | "%n", 74 | "", 75 | }, { 76 | initpc, 77 | "%v", 78 | "stack_test.go:9", 79 | }, { 80 | initpc, 81 | "%+v", 82 | "github.com/pkg/errors.init\n" + 83 | "\t.+/github.com/pkg/errors/stack_test.go:9", 84 | }, { 85 | 0, 86 | "%v", 87 | "unknown:0", 88 | }} 89 | 90 | for i, tt := range tests { 91 | testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) 92 | } 93 | } 94 | 95 | func TestFuncname(t *testing.T) { 96 | tests := []struct { 97 | name, want string 98 | }{ 99 | {"", ""}, 100 | {"runtime.main", "main"}, 101 | {"github.com/pkg/errors.funcname", "funcname"}, 102 | {"funcname", "funcname"}, 103 | {"io.copyBuffer", "copyBuffer"}, 104 | {"main.(*R).Write", "(*R).Write"}, 105 | } 106 | 107 | for _, tt := range tests { 108 | got := funcname(tt.name) 109 | want := tt.want 110 | if got != want { 111 | t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) 112 | } 113 | } 114 | } 115 | 116 | func TestStackTrace(t *testing.T) { 117 | tests := []struct { 118 | err error 119 | want []string 120 | }{{ 121 | New("ooh"), []string{ 122 | "github.com/pkg/errors.TestStackTrace\n" + 123 | "\t.+/github.com/pkg/errors/stack_test.go:121", 124 | }, 125 | }, { 126 | Wrap(New("ooh"), "ahh"), []string{ 127 | "github.com/pkg/errors.TestStackTrace\n" + 128 | "\t.+/github.com/pkg/errors/stack_test.go:126", // this is the stack of Wrap, not New 129 | }, 130 | }, { 131 | Cause(Wrap(New("ooh"), "ahh")), []string{ 132 | "github.com/pkg/errors.TestStackTrace\n" + 133 | "\t.+/github.com/pkg/errors/stack_test.go:131", // this is the stack of New 134 | }, 135 | }, { 136 | func() error { return New("ooh") }(), []string{ 137 | `github.com/pkg/errors.TestStackTrace.func1` + 138 | "\n\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New 139 | "github.com/pkg/errors.TestStackTrace\n" + 140 | "\t.+/github.com/pkg/errors/stack_test.go:136", // this is the stack of New's caller 141 | }, 142 | }, { 143 | Cause(func() error { 144 | return func() error { 145 | return Errorf("hello %s", fmt.Sprintf("world: %s", "ooh")) 146 | }() 147 | }()), []string{ 148 | `github.com/pkg/errors.TestStackTrace.func2.1` + 149 | "\n\t.+/github.com/pkg/errors/stack_test.go:145", // this is the stack of Errorf 150 | `github.com/pkg/errors.TestStackTrace.func2` + 151 | "\n\t.+/github.com/pkg/errors/stack_test.go:146", // this is the stack of Errorf's caller 152 | "github.com/pkg/errors.TestStackTrace\n" + 153 | "\t.+/github.com/pkg/errors/stack_test.go:147", // this is the stack of Errorf's caller's caller 154 | }, 155 | }} 156 | for i, tt := range tests { 157 | x, ok := tt.err.(interface { 158 | StackTrace() StackTrace 159 | }) 160 | if !ok { 161 | t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) 162 | continue 163 | } 164 | st := x.StackTrace() 165 | for j, want := range tt.want { 166 | testFormatRegexp(t, i, st[j], "%+v", want) 167 | } 168 | } 169 | } 170 | 171 | func stackTrace() StackTrace { 172 | const depth = 8 173 | var pcs [depth]uintptr 174 | n := runtime.Callers(1, pcs[:]) 175 | var st stack = pcs[0:n] 176 | return st.StackTrace() 177 | } 178 | 179 | func TestStackTraceFormat(t *testing.T) { 180 | tests := []struct { 181 | StackTrace 182 | format string 183 | want string 184 | }{{ 185 | nil, 186 | "%s", 187 | `\[\]`, 188 | }, { 189 | nil, 190 | "%v", 191 | `\[\]`, 192 | }, { 193 | nil, 194 | "%+v", 195 | "", 196 | }, { 197 | nil, 198 | "%#v", 199 | `\[\]errors.Frame\(nil\)`, 200 | }, { 201 | make(StackTrace, 0), 202 | "%s", 203 | `\[\]`, 204 | }, { 205 | make(StackTrace, 0), 206 | "%v", 207 | `\[\]`, 208 | }, { 209 | make(StackTrace, 0), 210 | "%+v", 211 | "", 212 | }, { 213 | make(StackTrace, 0), 214 | "%#v", 215 | `\[\]errors.Frame{}`, 216 | }, { 217 | stackTrace()[:2], 218 | "%s", 219 | `\[stack_test.go stack_test.go\]`, 220 | }, { 221 | stackTrace()[:2], 222 | "%v", 223 | `\[stack_test.go:174 stack_test.go:221\]`, 224 | }, { 225 | stackTrace()[:2], 226 | "%+v", 227 | "\n" + 228 | "github.com/pkg/errors.stackTrace\n" + 229 | "\t.+/github.com/pkg/errors/stack_test.go:174\n" + 230 | "github.com/pkg/errors.TestStackTraceFormat\n" + 231 | "\t.+/github.com/pkg/errors/stack_test.go:225", 232 | }, { 233 | stackTrace()[:2], 234 | "%#v", 235 | `\[\]errors.Frame{stack_test.go:174, stack_test.go:233}`, 236 | }} 237 | 238 | for i, tt := range tests { 239 | testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) 240 | } 241 | } 242 | 243 | // a version of runtime.Caller that returns a Frame, not a uintptr. 244 | func caller() Frame { 245 | var pcs [3]uintptr 246 | n := runtime.Callers(2, pcs[:]) 247 | frames := runtime.CallersFrames(pcs[:n]) 248 | frame, _ := frames.Next() 249 | return Frame(frame.PC) 250 | } 251 | --------------------------------------------------------------------------------