Skip to content

Commit

Permalink
fixed tests, add coverage for Wrap and Fprintf
Browse files Browse the repository at this point in the history
  • Loading branch information
davecheney committed Apr 22, 2016
2 parents c978824 + 7b7bdcf commit 5a0856f
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 102 deletions.
141 changes: 72 additions & 69 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,107 +3,110 @@ package errors

import (
"fmt"
"io"
"os"
"runtime"
)

// New returns an error that formats as the given text.
func New(message string) error {
pc, _, _, _ := runtime.Caller(1) // the caller of New
func New(text string) error {
return struct {
error
pc uintptr
}{
fmt.Errorf(message),
pc,
fmt.Errorf(text),
pc(),
}
}

// Errorf returns a formatted error.
func Errorf(format string, args ...interface{}) error {
pc, _, _, _ := runtime.Caller(1) // the caller of Errorf
return struct {
error
pc uintptr
}{
fmt.Errorf(format, args...),
pc,
type e struct {
cause error
message string
pc uintptr
}

func (e *e) Error() string {
return e.message + ": " + e.cause.Error()
}

func (e *e) Cause() error {
return e.cause
}

// Wrap returns an error annotating the cause with message.
// If cause is nil, Wrap returns nil.
func Wrap(cause error, message string) error {
if cause == nil {
return nil
}
return &e{
cause: cause,
message: message,
pc: pc(),
}
}

type causer interface {
Cause() error
}

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// method:
//
// Cause() error
// interface:
//
// type Causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
if err == nil {
return nil
}
type causer interface {
Cause() error
}
if err, ok := err.(causer); ok {
return err.Cause()
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}

func underlying(err error) (error, bool) {
if err == nil {
return nil, false
}
type underlying interface {
underlying() error
}
if err, ok := err.(underlying); ok {
return err.underlying(), true
}
return nil, false
type locationer interface {
Location() (string, int)
}

type traced struct {
error // underlying error
pc uintptr
// Print prints the error to Stderr.
func Print(err error) {
Fprint(os.Stderr, err)
}

func (t *traced) underlying() error { return t.error }
// Fprint prints the error to the supplied writer.
// The format of the output is the same as Print.
// If err is nil, nothing is printed.
func Fprint(w io.Writer, err error) {
for err != nil {
location, ok := err.(locationer)
if ok {
file, line := location.Location()
fmt.Fprint(w, "%s:%d: ", file, line)
}
switch err := err.(type) {
case *e:
fmt.Fprintln(w, err.message)
default:
fmt.Fprintln(w, err.Error())
}

// Trace adds caller information to the error.
// If error is nil, nil will be returned.
func Trace(err error) error {
if err == nil {
return nil
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
pc, _, _, _ := runtime.Caller(1) // the caller of Trace
return traced{
error: err,
pc: pc,
}
}

type annotated struct {
error // underlying error
pc uintptr
}

func (a *annotated) Cause() error { return a.error }

// Annotate returns a new error annotating the error provided
// with the message, and the location of the caller of Annotate.
// The underlying error can be recovered by calling Cause.
// If err is nil, nil will be returned.
func Annotate(err error, message string) error {
if err == nil {
return nil
}
pc, _, _, _ := runtime.Caller(1) // the caller of Annotate
return annotated{
error: err,
pc: pc,
}
func pc() uintptr {
pc, _, _, _ := runtime.Caller(2)
return pc
}
91 changes: 58 additions & 33 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package errors

import (
"bytes"
"fmt"
"io"
"reflect"
"testing"
)

func TestNewError(t *testing.T) {
func TestNew(t *testing.T) {
tests := []struct {
err string
want error
Expand All @@ -25,23 +26,28 @@ func TestNewError(t *testing.T) {
}
}

func TestNewEqualNew(t *testing.T) {
// test that two calls to New return the same error when called from the same location
var errs []error
for i := 0; i < 2; i++ {
errs = append(errs, New("error"))
}
a, b := errs[0], errs[1]
if !reflect.DeepEqual(a, b) {
t.Errorf("Expected two calls to New from the same location to give the same error: %#v, %#v", a, b)
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 TestNewNotEqualNew(t *testing.T) {
// test that two calls to New return different errors when called from different locations
a, b := New("error"), New("error")
if reflect.DeepEqual(a, b) {
t.Errorf("Expected two calls to New from the different locations give the same error: %#v, %#v", a, b)
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)
}
}
}

Expand Down Expand Up @@ -94,23 +100,42 @@ func TestCause(t *testing.T) {
}
}

func TestTraceNotEqual(t *testing.T) {
// test that two calls to trace do not return identical errors
err := New("error")
a := err
var errs []error
for i := 0; i < 2; i++ {
err = Trace(err)
errs = append(errs, err)
}
b, c := errs[0], errs[1]
if reflect.DeepEqual(a, b) {
t.Errorf("a and b equal: %#v, %#v", a, b)
}
if reflect.DeepEqual(b, c) {
t.Errorf("b and c equal: %#v, %#v", b, c)
}
if reflect.DeepEqual(a, c) {
t.Errorf("a and c equal: %#v, %#v", a, c)
func TestFprint(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: "cause error\nEOF\n",
}, {
err: x, // return from errors.New
want: "error\n",
}, {
err: Wrap(x, "message"),
want: "message\nerror\n",
}, {
err: Wrap(Wrap(x, "message"), "another message"),
want: "another message\nmessage\nerror\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)
}
}
}

0 comments on commit 5a0856f

Please sign in to comment.