go.uber.org/cadence@v1.2.9/internal/common/backoff/retry_test.go (about) 1 // Copyright (c) 2017-2020 Uber Technologies Inc. 2 // Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 package backoff 23 24 import ( 25 "context" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/assert" 30 31 "go.uber.org/cadence/.gen/go/shared" 32 ) 33 34 type errCategory int 35 36 const ( 37 noErr errCategory = iota 38 anyErr 39 serviceBusyErr 40 ) 41 42 func TestRetry(t *testing.T) { 43 t.Parallel() 44 45 always := func(err error) bool { 46 return true 47 } 48 never := func(err error) bool { 49 return false 50 } 51 52 succeedOnAttemptNum := 5 53 tests := []struct { 54 name string 55 maxAttempts int 56 maxTime time.Duration // context timeout 57 isRetryable func(error) bool 58 59 err errCategory 60 expectedCalls int 61 }{ 62 {"success", 2 * succeedOnAttemptNum, time.Second, always, noErr, succeedOnAttemptNum}, 63 {"too many tries", 3, time.Second, always, anyErr, 4}, // max 3 retries == 4 calls. must be < succeedOnAttemptNum to work. 64 {"success with always custom retry", 2 * succeedOnAttemptNum, time.Second, always, noErr, succeedOnAttemptNum}, 65 {"success with never custom retry", 2 * succeedOnAttemptNum, time.Second, never, anyErr, 1}, 66 67 // elapsed-time-sensitive tests below. 68 // consider raising time granularity if flaky, or we could set up a more complete mock 69 // to resolve flakiness for real, but that's a fair bit more complex. 70 71 // try -> sleep(10ms) -> try -> sleep(20ms) -> try -> sleep(40ms) -> timeout == 3 calls. 72 {"timed out eventually", 5, 50 * time.Millisecond, always, anyErr, 3}, 73 74 // try -> sleep(longer than context timeout due to busy err) -> timeout == 1 call. 75 {"timed out due to long minimum delay", 5, 10 * time.Millisecond, always, serviceBusyErr, 1}, 76 } 77 78 for _, test := range tests { 79 test := test 80 t.Run(test.name, func(t *testing.T) { 81 t.Parallel() 82 i := 0 83 op := func() error { 84 i++ 85 86 if i == succeedOnAttemptNum { // prevent infinite loops, and lets max-attempts > 5 eventually succeed 87 return nil 88 } 89 90 switch test.err { 91 case noErr: 92 return &someError{} // non-erroring tests should not reach this branch 93 case anyErr: 94 return &someError{} 95 case serviceBusyErr: 96 return &shared.ServiceBusyError{} 97 } 98 panic("unreachable") 99 } 100 101 policy := NewExponentialRetryPolicy(10 * time.Millisecond) 102 policy.SetMaximumInterval(50 * time.Millisecond) 103 policy.SetMaximumAttempts(test.maxAttempts) 104 105 ctx, cancel := context.WithTimeout(context.Background(), test.maxTime) 106 defer cancel() 107 err := Retry(ctx, op, policy, test.isRetryable) 108 if test.err == noErr { 109 assert.NoError(t, err, "Retry count: %v", i) 110 } else { 111 assert.Error(t, err) 112 } 113 assert.Equal(t, test.expectedCalls, i, "wrong number of calls") 114 }) 115 } 116 } 117 118 func TestConcurrentRetrier(t *testing.T) { 119 t.Parallel() 120 a := assert.New(t) 121 policy := NewExponentialRetryPolicy(1 * time.Millisecond) 122 policy.SetMaximumInterval(10 * time.Millisecond) 123 policy.SetMaximumAttempts(4) 124 125 // Basic checks 126 retrier := NewConcurrentRetrier(policy) 127 retrier.Failed() 128 a.Equal(int64(1), retrier.failureCount) 129 retrier.Succeeded() 130 a.Equal(int64(0), retrier.failureCount) 131 sleepDuration := retrier.throttleInternal() 132 a.Equal(done, sleepDuration) 133 134 // Multiple count check. 135 retrier.Failed() 136 retrier.Failed() 137 a.Equal(int64(2), retrier.failureCount) 138 // Verify valid sleep times. 139 ch := make(chan time.Duration, 3) 140 go func() { 141 for i := 0; i < 3; i++ { 142 ch <- retrier.throttleInternal() 143 } 144 }() 145 for i := 0; i < 3; i++ { 146 val := <-ch 147 t.Logf("Duration: %d\n", val) 148 a.True(val > 0) 149 } 150 retrier.Succeeded() 151 a.Equal(int64(0), retrier.failureCount) 152 // Verify we don't have any sleep times. 153 go func() { 154 for i := 0; i < 3; i++ { 155 ch <- retrier.throttleInternal() 156 } 157 }() 158 for i := 0; i < 3; i++ { 159 val := <-ch 160 t.Logf("Duration: %d\n", val) 161 a.Equal(done, val) 162 } 163 } 164 165 type someError struct{} 166 167 func (e *someError) Error() string { 168 return "Some Error" 169 }