New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: cmpopts: add convenience functions for errors #89
Comments
\cc @gnahckire @shurcooL (from #24) |
@dsnet What you've described sounds quite positive and has nice properties. I just want to point out, at least with the example given, I don't see why I could see the code above simplified to just: type CheckError func(error) bool
func Test(t *testing.T) {
// nonNilError equates any non-nil error.
// This is usual for just checking that the result failed, but not how it failed.
nonNilError := CheckError(func(error) bool {
return true
})
// notExistError equates any error based on a function predicate.
notExistError := CheckError(os.IsNotExist)
// timeoutError equates any error that is a timeout error.
timeoutError := CheckError(func(e error) bool {
ne, ok := e.(net.Error)
return ok && ne.Timeout()
})
// containsEOF equates any error with EOF in the string.
// NOTE: string matching on error messages is heavily frowned upon.
containsEOF := CheckError(func(e error) bool {
return strings.Contains(e.Error(), "EOF")
})
tests := []struct {
err error
check CheckError
want bool
}{
{io.EOF, nonNilError, true},
{nil, nonNilError, false},
{io.EOF, notExistError, false},
{os.ErrNotExist, notExistError, true},
{os.ErrPermission, notExistError, false},
{io.EOF, timeoutError, false},
{&net.AddrError{}, timeoutError, false},
{syscall.ETIMEDOUT, timeoutError, true},
{io.EOF, containsEOF, true},
{io.ErrUnexpectedEOF, containsEOF, true},
{&net.ParseError{}, containsEOF, false},
}
for _, tt := range tests {
got := tt.check(tt.err)
if got != tt.want {
t.Errorf("check(%v) = %v, want %v", tt.err, got, tt.want)
}
}
} Perhaps the mechanism you've described is useful when deep nested structs are being compared with I hope this feedback is helpful, let me know if not. I might be missing some context. |
Instead of an
Now type CheckError func(err error) bool
func (c CheckError) Error() string {
return fmt.Sprintf("%#v", c)
}
func (c CheckError) CmpEqual(err error) bool {
return c(err)
} This general approach can then be applied to other cases where we may want to embed comparison information in the object being compared. |
On hold for golang/go#29934 Presumably we would add: func EquateErrors() cmp.Option {
return cmp.Comparerer(func (x, y error) bool {
return errors.Is(x, y) || errors.Is(y, x)
})
} |
@dsnet is this still on hold? Wasn't I wouldn't mind picking this one up. |
The EquateErrors helper equates errors according to errors.Is. We also declare a sentinel AnyError value that matches any non-nil error value. This adds a dependency on golang.org/x/xerrors so that we can continue to suppport go1.8, which is our current minimally supported version of Go. Fixes #89
I've been frustrated lately with the number of tests that do error checking by performing string equality on the error message. As it stands,
cmp
is better thanreflect.DeepEqual
in that it usually fails when comparing errors since many errors have an unexported field somewhere in it forcing users to think about how to properly compare them. I believecmp
(or rathercmpopts
) can go one further and assist users with comparing errors.Consider the following:
It seem odd at first that
CheckError
is both a function and also an error, but it gives flexibility in being able to control how comparisons with errors works, by configuring the comparison as data, rather than through an option.Consider the following example usage:
Thus,
EquateErrors
has the following properties:cmp.Comparer
. That is, the result the same regardless of whetherCheckError
is on the left or right side.os.IsNotExist
).Related to #24
\cc @neild
The text was updated successfully, but these errors were encountered: