2018-01-12 17:30:54 +01:00
package fserrors
2017-09-14 17:09:48 +02:00
import (
2021-03-11 15:44:01 +01:00
"context"
2021-11-04 11:12:57 +01:00
"errors"
2017-09-14 17:09:48 +02:00
"fmt"
"io"
"net"
"net/url"
"os"
"syscall"
"testing"
2019-03-21 12:24:13 +01:00
"time"
2017-09-14 17:09:48 +02:00
"github.com/stretchr/testify/assert"
)
2021-11-04 11:12:57 +01:00
// 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 ,
}
}
2017-09-14 17:09:48 +02:00
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-15 18:09:20 +02:00
type myError1 struct {
Err error
}
func ( e myError1 ) Error ( ) string { return e . Err . Error ( ) }
type myError2 struct {
Err error
}
2018-07-13 11:31:40 +02:00
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-15 18:09:20 +02: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 14:35:52 +02:00
type myError5 struct { }
func ( e * myError5 ) Error ( ) string { return "" }
func ( e * myError5 ) Temporary ( ) bool { return true }
2018-07-13 11:31:40 +02:00
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-15 18:09:20 +02:00
func TestCause ( t * testing . T ) {
e3 := & myError3 { 3 }
e4 := & myError4 { io . EOF }
2019-10-10 14:35:52 +02:00
e5 := & myError5 { }
2018-07-13 11:31:40 +02:00
eNil1 := & myError2 { nil }
eNil2 := & myError2 { Err : ( * myError2 ) ( nil ) }
2017-09-15 18:09:20 +02:00
errPotato := errors . New ( "potato" )
2018-07-13 11:31:40 +02:00
nilCause1 := & errorCause { nil }
nilCause2 := & errorCause { ( * myError2 ) ( nil ) }
2017-09-14 17:09:48 +02:00
for i , test := range [ ] struct {
2017-09-15 18:09:20 +02:00
err error
wantRetriable bool
wantErr error
2017-09-14 17:09:48 +02:00
} {
2017-09-15 18:09:20 +02:00
{ nil , false , nil } ,
{ errPotato , false , errPotato } ,
2021-11-04 11:12:57 +01:00
{ fmt . Errorf ( "potato: %w" , errPotato ) , false , errPotato } ,
{ fmt . Errorf ( "potato2: %w" , wrap ( errPotato , "potato" ) ) , false , errPotato } ,
2017-09-15 18:09:20 +02:00
{ errUseOfClosedNetworkConnection , false , errUseOfClosedNetworkConnection } ,
{ makeNetErr ( syscall . EAGAIN ) , true , syscall . EAGAIN } ,
{ makeNetErr ( syscall . Errno ( 123123123 ) ) , false , syscall . Errno ( 123123123 ) } ,
2018-07-13 11:31:40 +02:00
{ eNil1 , false , eNil1 } ,
{ eNil2 , false , eNil2 . Err } ,
2017-09-15 18:09:20 +02:00
{ myError1 { io . EOF } , false , io . EOF } ,
{ & myError2 { io . EOF } , false , io . EOF } ,
{ e3 , false , e3 } ,
{ e4 , false , e4 } ,
2019-10-10 14:35:52 +02:00
{ e5 , true , e5 } ,
2018-07-13 11:31:40 +02:00
{ & errorCause { errPotato } , false , errPotato } ,
{ nilCause1 , false , nilCause1 } ,
{ nilCause2 , false , nilCause2 . e } ,
2017-09-14 17:09:48 +02:00
} {
2017-09-15 18:09:20 +02: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 )
2017-09-14 17:09:48 +02:00
}
}
func TestShouldRetry ( t * testing . T ) {
for i , test := range [ ] struct {
err error
want bool
} {
{ nil , false } ,
{ errors . New ( "potato" ) , false } ,
2021-11-04 11:12:57 +01:00
{ fmt . Errorf ( "connection: %w" , errUseOfClosedNetworkConnection ) , true } ,
2017-09-14 17:09:48 +02:00
{ io . EOF , true } ,
{ io . ErrUnexpectedEOF , true } ,
2017-09-15 18:09:20 +02:00
{ makeNetErr ( syscall . EAGAIN ) , true } ,
{ makeNetErr ( syscall . Errno ( 123123123 ) ) , false } ,
2017-09-14 17:09:48 +02:00
{ & url . Error { Op : "post" , URL : "/" , Err : io . EOF } , true } ,
{ & url . Error { Op : "post" , URL : "/" , Err : errUseOfClosedNetworkConnection } , true } ,
2018-01-19 18:06:49 +01:00
{ & 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 } ,
2017-09-14 17:09:48 +02:00
{
2021-11-04 11:12:57 +01:00
wrap ( & url . Error {
2017-09-14 17:09:48 +02:00
Op : "post" ,
URL : "http://localhost/" ,
Err : makeNetErr ( syscall . EPIPE ) ,
} , "potato error" ) ,
true ,
} ,
{
2021-11-04 11:12:57 +01:00
wrap ( & url . Error {
2017-09-14 17:09:48 +02:00
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 ) )
}
}
2019-03-21 12:24:13 +01:00
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 ( )
2021-11-04 11:12:57 +01:00
err := fmt . Errorf ( "potato: %w" , ErrorRetryAfter ( t0 ) )
2019-03-21 12:24:13 +01:00
assert . Equal ( t , t0 , RetryAfterErrorTime ( err ) )
assert . True ( t , IsRetryAfterError ( err ) )
assert . Contains ( t , e . Error ( ) , "try again after" )
}
2021-03-11 15:44:01 +01:00
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 )
}