Skip to content

Commit

Permalink
Support wrapped errors (#359)
Browse files Browse the repository at this point in the history
Add xerrors.Is() and xerrors.As() to MatchError()

Resolves #334
  • Loading branch information
ansd authored and blgm committed Nov 5, 2019
1 parent f9a5276 commit 0a981cb
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 15 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect
golang.org/x/text v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.2.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUk
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
Expand Down
36 changes: 29 additions & 7 deletions matchers/match_error_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"

"github.com/onsi/gomega/format"
"golang.org/x/xerrors"
)

type MatchErrorMatcher struct {
Expand All @@ -21,25 +22,32 @@ func (matcher *MatchErrorMatcher) Match(actual interface{}) (success bool, err e
}

actualErr := actual.(error)
expected := matcher.Expected

if isError(matcher.Expected) {
return reflect.DeepEqual(actualErr, matcher.Expected), nil
if isPtrToErrorType(expected) {
return xerrors.As(actualErr, expected), nil
}

if isString(matcher.Expected) {
return actualErr.Error() == matcher.Expected, nil
if isError(expected) {
return reflect.DeepEqual(actualErr, expected) || xerrors.Is(actualErr, expected.(error)), nil
}

if isString(expected) {
return actualErr.Error() == expected, nil
}

var subMatcher omegaMatcher
var hasSubMatcher bool
if matcher.Expected != nil {
subMatcher, hasSubMatcher = (matcher.Expected).(omegaMatcher)
if expected != nil {
subMatcher, hasSubMatcher = (expected).(omegaMatcher)
if hasSubMatcher {
return subMatcher.Match(actualErr.Error())
}
}

return false, fmt.Errorf("MatchError must be passed an error, string, or Matcher that can match on strings. Got:\n%s", format.Object(matcher.Expected, 1))
return false, fmt.Errorf(
"MatchError must be passed an error, a pointer to a type that implements error, a string, or a Matcher that can match on strings. Got:\n%s",
format.Object(expected, 1))
}

func (matcher *MatchErrorMatcher) FailureMessage(actual interface{}) (message string) {
Expand All @@ -49,3 +57,17 @@ func (matcher *MatchErrorMatcher) FailureMessage(actual interface{}) (message st
func (matcher *MatchErrorMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to match error", matcher.Expected)
}

func isPtrToErrorType(a interface{}) bool {
if isNil(a) {
return false
}

typeOfA := reflect.TypeOf(a)
if typeOfA.Kind() != reflect.Ptr {
return false
}

errorType := reflect.TypeOf((*error)(nil)).Elem()
return typeOfA.Elem().Implements(errorType)
}
35 changes: 27 additions & 8 deletions matchers/match_error_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/matchers"
"golang.org/x/xerrors"
)

type CustomError struct {
Expand All @@ -18,16 +19,34 @@ func (c CustomError) Error() string {

var _ = Describe("MatchErrorMatcher", func() {
Context("When asserting against an error", func() {
It("should succeed when matching with an error", func() {
err := errors.New("an error")
fmtErr := fmt.Errorf("an error")
customErr := CustomError{}
Context("when passed an error", func() {
It("should succeed when errors are deeply equal", func() {
err := errors.New("an error")
fmtErr := fmt.Errorf("an error")
customErr := CustomError{}

Expect(err).Should(MatchError(errors.New("an error")))
Expect(err).ShouldNot(MatchError(errors.New("another error")))

Expect(fmtErr).Should(MatchError(errors.New("an error")))
Expect(customErr).Should(MatchError(CustomError{}))
})

Expect(err).Should(MatchError(errors.New("an error")))
Expect(err).ShouldNot(MatchError(errors.New("another error")))
It("should succeed when any error in the chain matches the passed error", func() {
innerErr := errors.New("inner error")
outerErr := xerrors.Errorf("outer error wrapping: %w", innerErr)

Expect(fmtErr).Should(MatchError(errors.New("an error")))
Expect(customErr).Should(MatchError(CustomError{}))
Expect(outerErr).Should(MatchError(innerErr))
})
})

Context("when passed non-nil pointer to a type that implements error", func() {
It("should succeed when any error in the chain matches the passed pointer", func() {
err := xerrors.Errorf("outer error wrapping: %w", CustomError{})

var expectedErrType CustomError
Expect(err).Should(MatchError(&expectedErrType))
})
})

It("should succeed when matching with a string", func() {
Expand Down

0 comments on commit 0a981cb

Please sign in to comment.