@@ -2,7 +2,9 @@ package docker
22
33import (
44 "errors"
5+ "fmt"
56 "math"
7+ "net"
68 "net/http"
79 "testing"
810 "time"
@@ -12,6 +14,67 @@ import (
1214 "github.com/stretchr/testify/require"
1315)
1416
17+ // timeoutError implements net.Error with Timeout() returning true.
18+ type timeoutError struct {}
19+
20+ func (e * timeoutError ) Error () string { return "timeout" }
21+ func (e * timeoutError ) Timeout () bool { return true }
22+ func (e * timeoutError ) Temporary () bool { return false }
23+
24+ // nonTimeoutNetError implements net.Error with Timeout() returning false.
25+ type nonTimeoutNetError struct {}
26+
27+ func (e * nonTimeoutNetError ) Error () string { return "non-timeout net error" }
28+ func (e * nonTimeoutNetError ) Timeout () bool { return false }
29+ func (e * nonTimeoutNetError ) Temporary () bool { return false }
30+
31+ func TestIsRetryableNetworkError (t * testing.T ) {
32+ for _ , c := range []struct {
33+ name string
34+ err error
35+ expected bool
36+ }{
37+ {
38+ // A direct timeout error from the network layer should be retryable.
39+ name : "net.Error with Timeout() true" ,
40+ err : & timeoutError {},
41+ expected : true ,
42+ },
43+ {
44+ // Timeout errors wrapped by fmt.Errorf should still be detected
45+ // via errors.As unwrapping.
46+ name : "wrapped net.Error with Timeout() true" ,
47+ err : fmt .Errorf ("read failed: %w" , & timeoutError {}),
48+ expected : true ,
49+ },
50+ {
51+ // A net.Error that is not a timeout (e.g. a connection refused)
52+ // should not be retryable.
53+ name : "net.Error with Timeout() false" ,
54+ err : & nonTimeoutNetError {},
55+ expected : false ,
56+ },
57+ {
58+ // A plain error with no net.Error in the chain should not be retryable.
59+ name : "plain error" ,
60+ err : errors .New ("something broke" ),
61+ expected : false ,
62+ },
63+ {
64+ // net.OpError is the concrete type returned by real socket operations;
65+ // verify that errors.As can unwrap through it to find the timeout.
66+ name : "net.OpError wrapping timeout" ,
67+ err : & net.OpError {Op : "read" , Err : & timeoutError {}},
68+ expected : true ,
69+ },
70+ } {
71+ t .Run (c .name , func (t * testing.T ) {
72+ result := isRetryableNetworkError (c .err )
73+ assert .Equal (t , c .expected , result )
74+ })
75+ }
76+ }
77+
1578func TestParseDecimalInString (t * testing.T ) {
1679 for _ , prefix := range []string {"" , "text" , "0" } {
1780 for _ , suffix := range []string {"" , "text" } {
0 commit comments