1package waiter 2 3import ( 4 mathrand "math/rand" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/aws/smithy-go/rand" 10) 11 12func TestComputeDelay(t *testing.T) { 13 cases := map[string]struct { 14 totalAttempts int64 15 minDelay time.Duration 16 maxDelay time.Duration 17 maxWaitTime time.Duration 18 expectedMaxDelays []time.Duration 19 expectedError string 20 expectedMinAttempts int 21 }{ 22 "standard": { 23 totalAttempts: 8, 24 minDelay: 2 * time.Second, 25 maxDelay: 120 * time.Second, 26 maxWaitTime: 300 * time.Second, 27 expectedMaxDelays: []time.Duration{2, 4, 8, 16, 32, 64, 120, 120}, 28 expectedMinAttempts: 8, 29 }, 30 "zero minDelay": { 31 totalAttempts: 3, 32 minDelay: 0, 33 maxDelay: 120 * time.Second, 34 maxWaitTime: 300 * time.Second, 35 expectedError: "minDelay must be greater than zero", 36 }, 37 "zero maxDelay": { 38 totalAttempts: 3, 39 minDelay: 10 * time.Second, 40 maxDelay: 0, 41 maxWaitTime: 300 * time.Second, 42 expectedError: "maxDelay must be greater than zero", 43 }, 44 "zero remaining time": { 45 totalAttempts: 3, 46 minDelay: 10 * time.Second, 47 maxDelay: 20 * time.Second, 48 maxWaitTime: 0, 49 expectedMaxDelays: []time.Duration{0}, 50 expectedMinAttempts: 1, 51 }, 52 "max wait time is less than min delay": { 53 totalAttempts: 3, 54 minDelay: 10 * time.Second, 55 maxDelay: 20 * time.Second, 56 maxWaitTime: 5 * time.Second, 57 expectedMaxDelays: []time.Duration{0}, 58 expectedMinAttempts: 1, 59 }, 60 "large minDelay": { 61 totalAttempts: 80, 62 minDelay: 150 * time.Minute, 63 maxDelay: 200 * time.Minute, 64 maxWaitTime: 250 * time.Minute, 65 expectedMinAttempts: 1, 66 }, 67 "large maxDelay": { 68 totalAttempts: 80, 69 minDelay: 15 * time.Minute, 70 maxDelay: 2000 * time.Minute, 71 maxWaitTime: 250 * time.Minute, 72 expectedMinAttempts: 5, 73 }, 74 } 75 76 for name, c := range cases { 77 t.Run(name, func(t *testing.T) { 78 // mock smithy-go rand/#Reader 79 r := rand.Reader 80 defer func() { 81 rand.Reader = r 82 }() 83 rand.Reader = mathrand.New(mathrand.NewSource(1)) 84 85 // mock waiter call 86 delays, err := mockwait(c.totalAttempts, c.minDelay, c.maxDelay, c.maxWaitTime) 87 88 if len(c.expectedError) != 0 { 89 if err == nil { 90 t.Fatalf("expected error, got none") 91 } 92 if e, a := c.expectedError, err.Error(); !strings.Contains(a, e) { 93 t.Fatalf("expected error %v, got %v instead", e, a) 94 } 95 } else if err != nil { 96 t.Fatalf("expected no error, got %v", err) 97 } 98 99 if e, a := c.expectedMinAttempts, len(delays); e > a { 100 t.Logf("%v", delays) 101 t.Fatalf("expected minimum attempts to be %v, got %v", e, a) 102 } 103 104 for i, expectedDelay := range c.expectedMaxDelays { 105 if e, a := expectedDelay*time.Second, delays[i]; e < a { 106 t.Fatalf("attempt %d : expected delay to be less than %v, got %v", i+1, e, a) 107 } 108 109 if e, a := c.minDelay, delays[i]; e > a && c.maxWaitTime > c.minDelay { 110 t.Fatalf("attempt %d : expected delay to be more than %v, got %v", i+1, e, a) 111 } 112 } 113 t.Logf("delays : %v", delays) 114 }) 115 } 116} 117 118func mockwait(maxAttempts int64, minDelay, maxDelay, maxWaitTime time.Duration) ([]time.Duration, error) { 119 delays := make([]time.Duration, 0) 120 remainingTime := maxWaitTime 121 var attempt int64 122 123 for { 124 attempt++ 125 126 if maxAttempts < attempt { 127 break 128 } 129 130 delay, err := ComputeDelay(attempt, minDelay, maxDelay, remainingTime) 131 if err != nil { 132 return delays, err 133 } 134 135 delays = append(delays, delay) 136 137 remainingTime -= delay 138 if remainingTime < minDelay { 139 break 140 } 141 } 142 143 return delays, nil 144} 145