2018-01-13 00:30:54 +08:00
package fserrors
2017-09-14 23:09:48 +08:00
import (
2021-03-11 22:44:01 +08:00
"context"
2017-09-14 23:09:48 +08:00
"fmt"
"io"
"net"
"net/url"
"os"
"syscall"
"testing"
2019-03-21 19:24:13 +08:00
"time"
2017-09-14 23:09:48 +08:00
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
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
}
2018-07-13 17:31:40 +08: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-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 }
2018-07-13 17:31:40 +08: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-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 { }
2018-07-13 17:31:40 +08:00
eNil1 := & myError2 { nil }
eNil2 := & myError2 { Err : ( * myError2 ) ( nil ) }
2017-09-16 00:09:20 +08:00
errPotato := errors . New ( "potato" )
2018-07-13 17:31:40 +08:00
nilCause1 := & errorCause { nil }
nilCause2 := & errorCause { ( * myError2 ) ( nil ) }
2017-09-14 23:09:48 +08:00
for i , test := range [ ] struct {
2017-09-16 00:09:20 +08:00
err error
wantRetriable bool
wantErr error
2017-09-14 23:09:48 +08:00
} {
2017-09-16 00:09:20 +08:00
{ nil , false , nil } ,
{ errPotato , false , errPotato } ,
{ errors . Wrap ( errPotato , "potato" ) , false , errPotato } ,
{ errors . Wrap ( errors . Wrap ( errPotato , "potato2" ) , "potato" ) , false , errPotato } ,
{ errUseOfClosedNetworkConnection , false , errUseOfClosedNetworkConnection } ,
{ makeNetErr ( syscall . EAGAIN ) , true , syscall . EAGAIN } ,
{ makeNetErr ( syscall . Errno ( 123123123 ) ) , false , syscall . Errno ( 123123123 ) } ,
2018-07-13 17:31:40 +08:00
{ 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 } ,
2018-07-13 17:31:40 +08:00
{ & errorCause { errPotato } , false , errPotato } ,
{ nilCause1 , false , nilCause1 } ,
{ nilCause2 , false , nilCause2 . e } ,
2017-09-14 23:09:48 +08:00
} {
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 )
2017-09-14 23:09:48 +08:00
}
}
func TestShouldRetry ( t * testing . T ) {
for i , test := range [ ] struct {
err error
want bool
} {
{ nil , false } ,
{ errors . New ( "potato" ) , false } ,
{ errors . Wrap ( errUseOfClosedNetworkConnection , "connection" ) , true } ,
{ io . EOF , true } ,
{ io . ErrUnexpectedEOF , true } ,
2017-09-16 00:09:20 +08:00
{ makeNetErr ( syscall . EAGAIN ) , true } ,
{ makeNetErr ( syscall . Errno ( 123123123 ) ) , false } ,
2017-09-14 23:09:48 +08:00
{ & url . Error { Op : "post" , URL : "/" , Err : io . EOF } , true } ,
{ & url . Error { Op : "post" , URL : "/" , Err : errUseOfClosedNetworkConnection } , true } ,
2018-01-20 01:06:49 +08: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 23:09:48 +08:00
{
errors . Wrap ( & url . Error {
Op : "post" ,
URL : "http://localhost/" ,
Err : makeNetErr ( syscall . EPIPE ) ,
} , "potato error" ) ,
true ,
} ,
{
errors . 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 ) )
}
}
2019-03-21 19:24:13 +08: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 ( )
err := errors . Wrap ( ErrorRetryAfter ( t0 ) , "potato" )
assert . Equal ( t , t0 , RetryAfterErrorTime ( err ) )
assert . True ( t , IsRetryAfterError ( err ) )
assert . Contains ( t , e . Error ( ) , "try again after" )
}
2021-03-11 22:44:01 +08: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 )
}