package errors import ( "bytes" "errors" "fmt" "io" "reflect" "testing" ) func TestNew(t *testing.T) { tests := []struct { err string want error }{ {"", fmt.Errorf("")}, {"foo", fmt.Errorf("foo")}, {"foo", New("foo")}, {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, } for _, tt := range tests { got := New(tt.err) if got.Error() != tt.want.Error() { t.Errorf("New.Error(): got: %q, want %q", got, tt.want) } } } func TestWrapNil(t *testing.T) { got := Wrap(nil, "no error") if got != nil { t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) } } func TestWrap(t *testing.T) { tests := []struct { err error message string want string }{ {io.EOF, "read error", "read error: EOF"}, {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, } for _, tt := range tests { got := Wrap(tt.err, tt.message).Error() if got != tt.want { t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) } } } type nilError struct{} func (nilError) Error() string { return "nil error" } type causeError struct { cause error } func (e *causeError) Error() string { return "cause error" } func (e *causeError) Cause() error { return e.cause } func TestCause(t *testing.T) { x := New("error") tests := []struct { err error want error }{{ // nil error is nil err: nil, want: nil, }, { // explicit nil error is nil err: (error)(nil), want: nil, }, { // typed nil is nil err: (*nilError)(nil), want: (*nilError)(nil), }, { // uncaused error is unaffected err: io.EOF, want: io.EOF, }, { // caused error returns cause err: &causeError{cause: io.EOF}, want: io.EOF, }, { err: x, // return from errors.New want: x, }} for i, tt := range tests { got := Cause(tt.err) if !reflect.DeepEqual(got, tt.want) { t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) } } } func TestFprintError(t *testing.T) { x := New("error") tests := []struct { err error want string }{{ // nil error is nil err: nil, }, { // explicit nil error is nil err: (error)(nil), }, { // uncaused error is unaffected err: io.EOF, want: "EOF\n", }, { // caused error returns cause err: &causeError{cause: io.EOF}, want: "EOF\n" + "cause error\n", }, { err: x, // return from errors.New want: "github.com/pkg/errors/errors_test.go:106: error\n", }, { err: Wrap(x, "message"), want: "github.com/pkg/errors/errors_test.go:106: error\n" + "github.com/pkg/errors/errors_test.go:129: message\n", }, { err: Wrap(io.EOF, "message"), want: "EOF\n" + "github.com/pkg/errors/errors_test.go:133: message\n", }, { err: Wrap(Wrap(x, "message"), "another message"), want: "github.com/pkg/errors/errors_test.go:106: error\n" + "github.com/pkg/errors/errors_test.go:137: message\n" + "github.com/pkg/errors/errors_test.go:137: another message\n", }, { err: Wrapf(x, "message"), want: "github.com/pkg/errors/errors_test.go:106: error\n" + "github.com/pkg/errors/errors_test.go:142: message\n", }} for i, tt := range tests { var w bytes.Buffer Fprint(&w, tt.err) got := w.String() if got != tt.want { t.Errorf("test %d: Fprint(w, %q): got %q, want %q", i+1, tt.err, got, tt.want) } } } func TestWrapfNil(t *testing.T) { got := Wrapf(nil, "no error") if got != nil { t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) } } func TestWrapf(t *testing.T) { tests := []struct { err error message string want string }{ {io.EOF, "read error", "read error: EOF"}, {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, } for _, tt := range tests { got := Wrapf(tt.err, tt.message).Error() if got != tt.want { t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) } } } func TestErrorf(t *testing.T) { tests := []struct { err error want string }{ {Errorf("read error without format specifiers"), "read error without format specifiers"}, {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, } for _, tt := range tests { got := tt.err.Error() if got != tt.want { t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) } } } func TestStack(t *testing.T) { type fileline struct { file string line int } tests := []struct { err error want []fileline }{{ New("ooh"), []fileline{ {"github.com/pkg/errors/errors_test.go", 209}, }, }, { Wrap(New("ooh"), "ahh"), []fileline{ {"github.com/pkg/errors/errors_test.go", 213}, // this is the stack of Wrap, not New }, }, { Cause(Wrap(New("ooh"), "ahh")), []fileline{ {"github.com/pkg/errors/errors_test.go", 217}, // this is the stack of New }, }, { func() error { return New("ooh") }(), []fileline{ {"github.com/pkg/errors/errors_test.go", 221}, // this is the stack of New {"github.com/pkg/errors/errors_test.go", 221}, // this is the stack of New's caller }, }, { Cause(func() error { return func() error { return Errorf("hello %s", fmt.Sprintf("world")) }() }()), []fileline{ {"github.com/pkg/errors/errors_test.go", 228}, // this is the stack of Errorf {"github.com/pkg/errors/errors_test.go", 229}, // this is the stack of Errorf's caller {"github.com/pkg/errors/errors_test.go", 230}, // this is the stack of Errorf's caller's caller }, }} for _, tt := range tests { x, ok := tt.err.(interface { Stack() []uintptr }) if !ok { t.Errorf("expected %#v to implement Stack()", tt.err) continue } st := x.Stack() for i, want := range tt.want { frame := Frame(st[i]) file, line := fmt.Sprintf("%+s", frame), frame.line() if file != want.file || line != want.line { t.Errorf("frame %d: expected %s:%d, got %s:%d", i, want.file, want.line, file, line) } } } } // errors.New, etc values are not expected to be compared by value // but the change in errors#27 made them incomparable. Assert that // various kinds of errors have a functional equality operator, even // if the result of that equality is always false. func TestErrorEquality(t *testing.T) { tests := []struct { err1, err2 error }{ {io.EOF, io.EOF}, {io.EOF, nil}, {io.EOF, errors.New("EOF")}, {io.EOF, New("EOF")}, {New("EOF"), New("EOF")}, {New("EOF"), Errorf("EOF")}, {New("EOF"), Wrap(io.EOF, "EOF")}, } for _, tt := range tests { _ = tt.err1 == tt.err2 // mustn't panic } }