rclone/fs/fserrors/error_test.go

216 lines
5.3 KiB
Go
Raw Normal View History

package fserrors
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// withMessage wraps an error with a message
//
// This is for backwards compatibility with the now removed github.com/pkg/errors
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
// wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func wrap(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
var errUseOfClosedNetworkConnection = errors.New("use of closed network connection")
// make a plausible network error with the underlying errno
func makeNetErr(errno syscall.Errno) error {
return &net.OpError{
Op: "write",
Net: "tcp",
Source: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 123},
Addr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080},
Err: &os.SyscallError{
Syscall: "write",
Err: errno,
},
}
}
2017-09-16 00:09:20 +08:00
type myError1 struct {
Err error
}
func (e myError1) Error() string { return e.Err.Error() }
type myError2 struct {
Err error
}
func (e *myError2) Error() string {
if e == nil {
return "myError2(nil)"
}
if e.Err == nil {
return "myError2{Err: nil}"
}
return e.Err.Error()
}
2017-09-16 00:09:20 +08:00
type myError3 struct {
Err int
}
func (e *myError3) Error() string { return "hello" }
type myError4 struct {
e error
}
func (e *myError4) Error() string { return e.e.Error() }
2019-10-10 20:35:52 +08:00
type myError5 struct{}
func (e *myError5) Error() string { return "" }
func (e *myError5) Temporary() bool { return true }
type errorCause struct {
e error
}
func (e *errorCause) Error() string { return fmt.Sprintf("%#v", e) }
func (e *errorCause) Cause() error { return e.e }
2017-09-16 00:09:20 +08:00
func TestCause(t *testing.T) {
e3 := &myError3{3}
e4 := &myError4{io.EOF}
2019-10-10 20:35:52 +08:00
e5 := &myError5{}
eNil1 := &myError2{nil}
eNil2 := &myError2{Err: (*myError2)(nil)}
2017-09-16 00:09:20 +08:00
errPotato := errors.New("potato")
nilCause1 := &errorCause{nil}
nilCause2 := &errorCause{(*myError2)(nil)}
for i, test := range []struct {
2017-09-16 00:09:20 +08:00
err error
wantRetriable bool
wantErr error
}{
2017-09-16 00:09:20 +08:00
{nil, false, nil},
{errPotato, false, errPotato},
{fmt.Errorf("potato: %w", errPotato), false, errPotato},
{fmt.Errorf("potato2: %w", wrap(errPotato, "potato")), false, errPotato},
2017-09-16 00:09:20 +08:00
{errUseOfClosedNetworkConnection, false, errUseOfClosedNetworkConnection},
{makeNetErr(syscall.EAGAIN), true, syscall.EAGAIN},
{makeNetErr(syscall.Errno(123123123)), false, syscall.Errno(123123123)},
{eNil1, false, eNil1},
{eNil2, false, eNil2.Err},
2017-09-16 00:09:20 +08:00
{myError1{io.EOF}, false, io.EOF},
{&myError2{io.EOF}, false, io.EOF},
{e3, false, e3},
{e4, false, e4},
2019-10-10 20:35:52 +08:00
{e5, true, e5},
{&errorCause{errPotato}, false, errPotato},
{nilCause1, false, nilCause1},
{nilCause2, false, nilCause2.e},
} {
2017-09-16 00:09:20 +08:00
gotRetriable, gotErr := Cause(test.err)
what := fmt.Sprintf("test #%d: %v", i, test.err)
assert.Equal(t, test.wantErr, gotErr, what)
assert.Equal(t, test.wantRetriable, gotRetriable, what)
}
}
func TestShouldRetry(t *testing.T) {
for i, test := range []struct {
err error
want bool
}{
{nil, false},
{errors.New("potato"), false},
{fmt.Errorf("connection: %w", errUseOfClosedNetworkConnection), true},
{io.EOF, true},
{io.ErrUnexpectedEOF, true},
2017-09-16 00:09:20 +08:00
{makeNetErr(syscall.EAGAIN), true},
{makeNetErr(syscall.Errno(123123123)), false},
{&url.Error{Op: "post", URL: "/", Err: io.EOF}, true},
{&url.Error{Op: "post", URL: "/", Err: errUseOfClosedNetworkConnection}, true},
{&url.Error{Op: "post", URL: "/", Err: fmt.Errorf("net/http: HTTP/1.x transport connection broken: %v", fmt.Errorf("http: ContentLength=%d with Body length %d", 100663336, 99590598))}, true},
{
wrap(&url.Error{
Op: "post",
URL: "http://localhost/",
Err: makeNetErr(syscall.EPIPE),
}, "potato error"),
true,
},
{
wrap(&url.Error{
Op: "post",
URL: "http://localhost/",
Err: makeNetErr(syscall.Errno(123123123)),
}, "listing error"),
false,
},
} {
got := ShouldRetry(test.err)
assert.Equal(t, test.want, got, fmt.Sprintf("test #%d: %v", i, test.err))
}
}
func TestRetryAfter(t *testing.T) {
e := NewErrorRetryAfter(time.Second)
after := e.RetryAfter()
dt := time.Until(after)
assert.True(t, dt >= 900*time.Millisecond && dt <= 1100*time.Millisecond)
assert.True(t, IsRetryAfterError(e))
assert.False(t, IsRetryAfterError(io.EOF))
assert.Equal(t, time.Time{}, RetryAfterErrorTime(io.EOF))
assert.False(t, IsRetryAfterError(nil))
assert.Contains(t, e.Error(), "try again after")
t0 := time.Now()
err := fmt.Errorf("potato: %w", ErrorRetryAfter(t0))
assert.Equal(t, t0, RetryAfterErrorTime(err))
assert.True(t, IsRetryAfterError(err))
assert.Contains(t, e.Error(), "try again after")
}
func TestContextError(t *testing.T) {
var err = io.EOF
ctx, cancel := context.WithCancel(context.Background())
assert.False(t, ContextError(ctx, &err))
assert.Equal(t, io.EOF, err)
cancel()
assert.True(t, ContextError(ctx, &err))
assert.Equal(t, io.EOF, err)
err = nil
assert.True(t, ContextError(ctx, &err))
assert.Equal(t, context.Canceled, err)
}