github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/fserrors/error_test.go (about) 1 package fserrors 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net/url" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 ) 14 15 // withMessage wraps an error with a message 16 // 17 // This is for backwards compatibility with the now removed github.com/pkg/errors 18 type withMessage struct { 19 cause error 20 msg string 21 } 22 23 func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } 24 func (w *withMessage) Cause() error { return w.cause } 25 26 // wrap returns an error annotating err with a stack trace 27 // at the point Wrap is called, and the supplied message. 28 // If err is nil, Wrap returns nil. 29 func wrap(err error, message string) error { 30 if err == nil { 31 return nil 32 } 33 return &withMessage{ 34 cause: err, 35 msg: message, 36 } 37 } 38 39 var errUseOfClosedNetworkConnection = errors.New("use of closed network connection") 40 41 type myError1 struct { 42 Err error 43 } 44 45 func (e myError1) Error() string { return e.Err.Error() } 46 47 type myError2 struct { 48 Err error 49 } 50 51 func (e *myError2) Error() string { 52 if e == nil { 53 return "myError2(nil)" 54 } 55 if e.Err == nil { 56 return "myError2{Err: nil}" 57 } 58 return e.Err.Error() 59 } 60 61 type myError3 struct { 62 Err int 63 } 64 65 func (e *myError3) Error() string { return "hello" } 66 67 type myError4 struct { 68 e error 69 } 70 71 func (e *myError4) Error() string { return e.e.Error() } 72 73 type myError5 struct{} 74 75 func (e *myError5) Error() string { return "" } 76 77 func (e *myError5) Temporary() bool { return true } 78 79 type errorCause struct { 80 e error 81 } 82 83 func (e *errorCause) Error() string { return fmt.Sprintf("%#v", e) } 84 85 func (e *errorCause) Cause() error { return e.e } 86 87 func TestCause(t *testing.T) { 88 e3 := &myError3{3} 89 e4 := &myError4{io.EOF} 90 e5 := &myError5{} 91 eNil1 := &myError2{nil} 92 eNil2 := &myError2{Err: (*myError2)(nil)} 93 errPotato := errors.New("potato") 94 nilCause1 := &errorCause{nil} 95 nilCause2 := &errorCause{(*myError2)(nil)} 96 97 for i, test := range []struct { 98 err error 99 wantRetriable bool 100 wantErr error 101 }{ 102 {nil, false, nil}, 103 {errPotato, false, errPotato}, 104 {fmt.Errorf("potato: %w", errPotato), false, errPotato}, 105 {fmt.Errorf("potato2: %w", wrap(errPotato, "potato")), false, errPotato}, 106 {errUseOfClosedNetworkConnection, false, errUseOfClosedNetworkConnection}, 107 {eNil1, false, eNil1}, 108 {eNil2, false, eNil2.Err}, 109 {myError1{io.EOF}, false, io.EOF}, 110 {&myError2{io.EOF}, false, io.EOF}, 111 {e3, false, e3}, 112 {e4, false, e4}, 113 {e5, true, e5}, 114 {&errorCause{errPotato}, false, errPotato}, 115 {nilCause1, false, nilCause1}, 116 {nilCause2, false, nilCause2.e}, 117 } { 118 gotRetriable, gotErr := Cause(test.err) 119 what := fmt.Sprintf("test #%d: %v", i, test.err) 120 assert.Equal(t, test.wantErr, gotErr, what) 121 assert.Equal(t, test.wantRetriable, gotRetriable, what) 122 } 123 } 124 125 func TestShouldRetry(t *testing.T) { 126 for i, test := range []struct { 127 err error 128 want bool 129 }{ 130 {nil, false}, 131 {errors.New("potato"), false}, 132 {fmt.Errorf("connection: %w", errUseOfClosedNetworkConnection), true}, 133 {io.EOF, true}, 134 {io.ErrUnexpectedEOF, true}, 135 {&url.Error{Op: "post", URL: "/", Err: io.EOF}, true}, 136 {&url.Error{Op: "post", URL: "/", Err: errUseOfClosedNetworkConnection}, true}, 137 {&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}, 138 } { 139 got := ShouldRetry(test.err) 140 assert.Equal(t, test.want, got, fmt.Sprintf("test #%d: %v", i, test.err)) 141 } 142 } 143 144 func TestRetryAfter(t *testing.T) { 145 e := NewErrorRetryAfter(time.Second) 146 after := e.RetryAfter() 147 dt := time.Until(after) 148 assert.True(t, dt >= 900*time.Millisecond && dt <= 1100*time.Millisecond) 149 assert.True(t, IsRetryAfterError(e)) 150 assert.False(t, IsRetryAfterError(io.EOF)) 151 assert.Equal(t, time.Time{}, RetryAfterErrorTime(io.EOF)) 152 assert.False(t, IsRetryAfterError(nil)) 153 assert.Contains(t, e.Error(), "try again after") 154 155 t0 := time.Now() 156 err := fmt.Errorf("potato: %w", ErrorRetryAfter(t0)) 157 assert.Equal(t, t0, RetryAfterErrorTime(err)) 158 assert.True(t, IsRetryAfterError(err)) 159 assert.Contains(t, e.Error(), "try again after") 160 } 161 162 func TestContextError(t *testing.T) { 163 var err = io.EOF 164 ctx, cancel := context.WithCancel(context.Background()) 165 166 assert.False(t, ContextError(ctx, &err)) 167 assert.Equal(t, io.EOF, err) 168 169 cancel() 170 171 assert.True(t, ContextError(ctx, &err)) 172 assert.Equal(t, io.EOF, err) 173 174 err = nil 175 176 assert.True(t, ContextError(ctx, &err)) 177 assert.Equal(t, context.Canceled, err) 178 }