mirror of
https://github.com/rclone/rclone
synced 2024-12-29 22:26:24 +01:00
e43b5ce5e5
This is possible now that we no longer support go1.12 and brings rclone into line with standard practices in the Go world. This also removes errors.New and errors.Errorf from lib/errors and prefers the stdlib errors package over lib/errors.
216 lines
5.3 KiB
Go
216 lines
5.3 KiB
Go
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,
|
|
},
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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() }
|
|
|
|
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 }
|
|
|
|
func TestCause(t *testing.T) {
|
|
e3 := &myError3{3}
|
|
e4 := &myError4{io.EOF}
|
|
e5 := &myError5{}
|
|
eNil1 := &myError2{nil}
|
|
eNil2 := &myError2{Err: (*myError2)(nil)}
|
|
errPotato := errors.New("potato")
|
|
nilCause1 := &errorCause{nil}
|
|
nilCause2 := &errorCause{(*myError2)(nil)}
|
|
|
|
for i, test := range []struct {
|
|
err error
|
|
wantRetriable bool
|
|
wantErr error
|
|
}{
|
|
{nil, false, nil},
|
|
{errPotato, false, errPotato},
|
|
{fmt.Errorf("potato: %w", errPotato), false, errPotato},
|
|
{fmt.Errorf("potato2: %w", wrap(errPotato, "potato")), false, errPotato},
|
|
{errUseOfClosedNetworkConnection, false, errUseOfClosedNetworkConnection},
|
|
{makeNetErr(syscall.EAGAIN), true, syscall.EAGAIN},
|
|
{makeNetErr(syscall.Errno(123123123)), false, syscall.Errno(123123123)},
|
|
{eNil1, false, eNil1},
|
|
{eNil2, false, eNil2.Err},
|
|
{myError1{io.EOF}, false, io.EOF},
|
|
{&myError2{io.EOF}, false, io.EOF},
|
|
{e3, false, e3},
|
|
{e4, false, e4},
|
|
{e5, true, e5},
|
|
{&errorCause{errPotato}, false, errPotato},
|
|
{nilCause1, false, nilCause1},
|
|
{nilCause2, false, nilCause2.e},
|
|
} {
|
|
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},
|
|
{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 := after.Sub(time.Now())
|
|
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)
|
|
}
|