rclone/fs/fserrors/error_test.go
Nick Craig-Wood e43b5ce5e5 Remove github.com/pkg/errors and replace with std library version
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.
2021-11-07 11:53:30 +00:00

215 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)
}