@@ -251,7 +251,7 @@ func testClientDo(t *testing.T, body interface{}) {
251
251
}
252
252
253
253
if resp .StatusCode != 200 {
254
- t .Fatalf ("exected 200, got: %d" , resp .StatusCode )
254
+ t .Fatalf ("expected 200, got: %d" , resp .StatusCode )
255
255
}
256
256
257
257
if retryCount < 0 {
@@ -896,6 +896,156 @@ func TestClient_DefaultBackoff(t *testing.T) {
896
896
}
897
897
}
898
898
899
+ func TestClient_ExponentialJitterBackoff (t * testing.T ) {
900
+ const retriableStatusCode int = http .StatusServiceUnavailable
901
+
902
+ t .Run ("with non-empty first value of Retry-After header in response" , func (t * testing.T ) {
903
+ response := & http.Response {
904
+ StatusCode : retriableStatusCode ,
905
+ Header : http.Header {
906
+ "Content-Type" : []string {"application/json" },
907
+ "Retry-After" : []string {"42" },
908
+ },
909
+ }
910
+ backoff := ExponentialJitterBackoff (retryWaitMin , retryWaitMax , 3 , response )
911
+ expectedBackoff := 42 * time .Second
912
+
913
+ if backoff != expectedBackoff {
914
+ t .Fatalf ("expected default backoff from Retry-After header (%s), got %s" , expectedBackoff , backoff )
915
+ }
916
+ })
917
+
918
+ invalidRetryAfterHeaderCases := []struct {
919
+ name string
920
+ makeResponse func () * http.Response
921
+ }{
922
+ {
923
+ name : "with empty first value of Retry-After header in response" ,
924
+ makeResponse : func () * http.Response {
925
+ return & http.Response {
926
+ StatusCode : retriableStatusCode ,
927
+ Header : http.Header {
928
+ "Content-Type" : []string {"application/json" },
929
+ "Retry-After" : []string {"" },
930
+ },
931
+ }
932
+ },
933
+ },
934
+ {
935
+ name : "without Retry-After header in response" ,
936
+ makeResponse : func () * http.Response {
937
+ return & http.Response {
938
+ StatusCode : retriableStatusCode ,
939
+ Header : http.Header {"Content-Type" : []string {"application/json" }},
940
+ }
941
+ },
942
+ },
943
+ {
944
+ name : "with nil response" ,
945
+ makeResponse : func () * http.Response {
946
+ return nil
947
+ },
948
+ },
949
+ }
950
+
951
+ for _ , irahc := range invalidRetryAfterHeaderCases {
952
+ t .Run (irahc .name , func (t * testing.T ) {
953
+ attemptNumCases := []struct {
954
+ name string
955
+ attemptNum int
956
+ expectedBackoffWithoutJitter time.Duration
957
+ }{
958
+ {
959
+ name : "with first attempt" ,
960
+ attemptNum : 0 ,
961
+ expectedBackoffWithoutJitter : retryWaitMin ,
962
+ },
963
+ {
964
+ name : "with low attempt number" ,
965
+ attemptNum : 3 ,
966
+ expectedBackoffWithoutJitter : 16 * time .Second ,
967
+ },
968
+ {
969
+ name : "with high attempt number" ,
970
+ attemptNum : 10 ,
971
+ expectedBackoffWithoutJitter : retryWaitMax ,
972
+ },
973
+ }
974
+
975
+ for _ , anc := range attemptNumCases {
976
+ t .Run (anc .name , func (t * testing.T ) {
977
+ backoff := ExponentialJitterBackoff (defaultRetryWaitMin , defaultRetryWaitMax , anc .attemptNum , irahc .makeResponse ())
978
+ expectedJitterDelta := float64 (anc .expectedBackoffWithoutJitter ) * 0.25
979
+ expectedMinTime := anc .expectedBackoffWithoutJitter - time .Duration (expectedJitterDelta )
980
+ expectedMaxTime := anc .expectedBackoffWithoutJitter + time .Duration (expectedJitterDelta )
981
+ expectedBackoffLowerLimit := max (expectedMinTime , retryWaitMin )
982
+ expectedBackoffUpperLimit := min (expectedMaxTime , retryWaitMax )
983
+
984
+ t .Run ("returns exponential backoff with jitter, clamped within min and max limits" , func (t * testing.T ) {
985
+ if backoff < expectedBackoffLowerLimit || backoff > expectedBackoffUpperLimit {
986
+ t .Fatalf ("expected backoff to be within range [%s, %s], got %s" , expectedBackoffLowerLimit , expectedBackoffUpperLimit , backoff )
987
+ }
988
+ })
989
+ })
990
+ }
991
+ })
992
+ }
993
+ }
994
+
995
+ func Test_clampDuration (t * testing.T ) {
996
+ const (
997
+ minDuration time.Duration = 500 * time .Millisecond
998
+ maxDuration time.Duration = 10 * time .Minute
999
+ )
1000
+
1001
+ testCases := []struct {
1002
+ name string
1003
+ errorMessage string
1004
+ duration time.Duration
1005
+ expectedClampedDuration time.Duration
1006
+ }{
1007
+ {
1008
+ name : "with duration below min value" ,
1009
+ errorMessage : "should return the min value" ,
1010
+ duration : 60 * time .Microsecond ,
1011
+ expectedClampedDuration : minDuration ,
1012
+ },
1013
+ {
1014
+ name : "with duration equal to min value" ,
1015
+ errorMessage : "should return the min value" ,
1016
+ duration : minDuration ,
1017
+ expectedClampedDuration : minDuration ,
1018
+ },
1019
+ {
1020
+ name : "with duration strictly within min and max range" ,
1021
+ errorMessage : "should return the given value" ,
1022
+ duration : 45 * time .Second ,
1023
+ expectedClampedDuration : 45 * time .Second ,
1024
+ },
1025
+ {
1026
+ name : "with duration equal to max value" ,
1027
+ errorMessage : "should return the max value" ,
1028
+ duration : maxDuration ,
1029
+ expectedClampedDuration : maxDuration ,
1030
+ },
1031
+ {
1032
+ name : "with duration above max value" ,
1033
+ errorMessage : "should return the max value" ,
1034
+ duration : 2 * time .Hour ,
1035
+ expectedClampedDuration : maxDuration ,
1036
+ },
1037
+ }
1038
+
1039
+ for _ , tc := range testCases {
1040
+ t .Run (tc .name , func (t * testing.T ) {
1041
+ duration := clampDuration (tc .duration , minDuration , maxDuration )
1042
+ if duration != tc .expectedClampedDuration {
1043
+ t .Fatalf ("expected duration %s, got %s" , expectedBackoff , backoff )
1044
+ }
1045
+ })
1046
+ }
1047
+ }
1048
+
899
1049
func TestClient_DefaultRetryPolicy_TLS (t * testing.T ) {
900
1050
ts := httptest .NewTLSServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
901
1051
w .WriteHeader (200 )
0 commit comments