From 31773ecfbf71eab44bce22476d9101f5c090e738 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 7 Jun 2023 09:16:14 +0100 Subject: [PATCH] union: allow errors to be unwrapped for inspection Before this change the Errors type in the union backend produced errors which could not be Unwrapped to test their type. This adds the (go1.20) Unwrap method to the Errors type which allows errors.Is to work on these errors. It also adds unit tests for the Errors type and fixes a couple of minor bugs thrown up in the process. See: https://forum.rclone.org/t/failed-to-set-modification-time-1-error-pcloud-cant-set-modified-time/38596 --- backend/union/errors.go | 14 ++++-- backend/union/errors_test.go | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 backend/union/errors_test.go diff --git a/backend/union/errors.go b/backend/union/errors.go index 46259b595..ddfd2866d 100644 --- a/backend/union/errors.go +++ b/backend/union/errors.go @@ -49,8 +49,7 @@ func (e Errors) Error() string { if len(e) == 0 { buf.WriteString("no error") - } - if len(e) == 1 { + } else if len(e) == 1 { buf.WriteString("1 error: ") } else { fmt.Fprintf(&buf, "%d errors: ", len(e)) @@ -61,8 +60,17 @@ func (e Errors) Error() string { buf.WriteString("; ") } - buf.WriteString(err.Error()) + if err != nil { + buf.WriteString(err.Error()) + } else { + buf.WriteString("nil error") + } } return buf.String() } + +// Unwrap returns the wrapped errors +func (e Errors) Unwrap() []error { + return e +} diff --git a/backend/union/errors_test.go b/backend/union/errors_test.go new file mode 100644 index 000000000..3d79a69b0 --- /dev/null +++ b/backend/union/errors_test.go @@ -0,0 +1,94 @@ +//go:build go1.20 +// +build go1.20 + +package union + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + err1 = errors.New("Error 1") + err2 = errors.New("Error 2") + err3 = errors.New("Error 3") +) + +func TestErrorsMap(t *testing.T) { + es := Errors{ + nil, + err1, + err2, + } + want := Errors{ + err2, + } + got := es.Map(func(e error) error { + if e == err1 { + return nil + } + return e + }) + assert.Equal(t, want, got) +} + +func TestErrorsFilterNil(t *testing.T) { + es := Errors{ + nil, + err1, + nil, + err2, + nil, + } + want := Errors{ + err1, + err2, + } + got := es.FilterNil() + assert.Equal(t, want, got) +} + +func TestErrorsErr(t *testing.T) { + // Check not all nil case + es := Errors{ + nil, + err1, + nil, + err2, + nil, + } + want := Errors{ + err1, + err2, + } + got := es.Err() + + // Check all nil case + assert.Equal(t, want, got) + es = Errors{ + nil, + nil, + nil, + } + assert.Nil(t, es.Err()) +} + +func TestErrorsError(t *testing.T) { + assert.Equal(t, "no error", Errors{}.Error()) + assert.Equal(t, "1 error: Error 1", Errors{err1}.Error()) + assert.Equal(t, "1 error: nil error", Errors{nil}.Error()) + assert.Equal(t, "2 errors: Error 1; Error 2", Errors{err1, err2}.Error()) +} + +func TestErrorsUnwrap(t *testing.T) { + es := Errors{ + err1, + err2, + } + assert.Equal(t, []error{err1, err2}, es.Unwrap()) + assert.True(t, errors.Is(es, err1)) + assert.True(t, errors.Is(es, err2)) + assert.False(t, errors.Is(es, err3)) +}