github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/retry/retry_test.go (about) 1 // Copyright 2021 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package retry 15 16 import ( 17 "context" 18 "math" 19 "testing" 20 "time" 21 22 "github.com/pingcap/errors" 23 "github.com/stretchr/testify/require" 24 ) 25 26 func TestDoShouldRetryAtMostSpecifiedTimes(t *testing.T) { 27 t.Parallel() 28 29 var callCount int 30 f := func() error { 31 callCount++ 32 return errors.New("test") 33 } 34 35 err := Do(context.Background(), f, WithMaxTries(3)) 36 require.Regexp(t, "test", errors.Cause(err)) 37 require.Equal(t, callCount, 3) 38 } 39 40 func TestDoShouldStopOnSuccess(t *testing.T) { 41 t.Parallel() 42 43 var callCount int 44 f := func() error { 45 callCount++ 46 if callCount == 2 { 47 return nil 48 } 49 return errors.New("test") 50 } 51 52 err := Do(context.Background(), f, WithMaxTries(3)) 53 require.Nil(t, err) 54 require.Equal(t, callCount, 2) 55 } 56 57 func TestIsRetryable(t *testing.T) { 58 t.Parallel() 59 60 var callCount int 61 f := func() error { 62 callCount++ 63 return errors.Annotate(context.Canceled, "test") 64 } 65 66 err := Do(context.Background(), f, WithMaxTries(3), WithIsRetryableErr(func(err error) bool { 67 switch errors.Cause(err) { 68 case context.Canceled: 69 return false 70 } 71 return true 72 })) 73 74 require.Equal(t, errors.Cause(err), context.Canceled) 75 require.Equal(t, callCount, 1) 76 77 callCount = 0 78 err = Do(context.Background(), f, WithMaxTries(3)) 79 80 require.Equal(t, errors.Cause(err), context.Canceled) 81 require.Equal(t, callCount, 3) 82 } 83 84 func TestDoCancelInfiniteRetry(t *testing.T) { 85 t.Parallel() 86 87 callCount := 0 88 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*20) 89 defer cancel() 90 f := func() error { 91 select { 92 case <-ctx.Done(): 93 return ctx.Err() 94 default: 95 } 96 callCount++ 97 return errors.New("test") 98 } 99 100 err := Do(ctx, f, WithBackoffBaseDelay(2), WithBackoffMaxDelay(10)) 101 require.Equal(t, errors.Cause(err), context.DeadlineExceeded) 102 require.GreaterOrEqual(t, callCount, 1, "tries: %d", callCount) 103 require.Less(t, callCount, math.MaxInt64) 104 } 105 106 func TestDoCancelAtBeginning(t *testing.T) { 107 t.Parallel() 108 109 callCount := 0 110 ctx, cancel := context.WithCancel(context.Background()) 111 cancel() 112 f := func() error { 113 callCount++ 114 return errors.New("test") 115 } 116 117 err := Do(ctx, f, WithBackoffBaseDelay(2), WithBackoffMaxDelay(10)) 118 require.Equal(t, errors.Cause(err), context.Canceled) 119 require.Equal(t, callCount, 0, "tries:%d", callCount) 120 } 121 122 func TestDoCornerCases(t *testing.T) { 123 t.Parallel() 124 125 var callCount int 126 f := func() error { 127 callCount++ 128 return errors.New("test") 129 } 130 131 err := Do(context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithBackoffMaxDelay(math.MaxInt64), WithMaxTries(2)) 132 require.Regexp(t, "test", errors.Cause(err)) 133 require.Equal(t, callCount, 2) 134 135 callCount = 0 136 err = Do(context.Background(), f, WithBackoffBaseDelay(math.MaxInt64), WithBackoffMaxDelay(math.MinInt64), WithMaxTries(2)) 137 require.Regexp(t, "test", errors.Cause(err)) 138 require.Equal(t, callCount, 2) 139 140 callCount = 0 141 err = Do(context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithBackoffMaxDelay(math.MinInt64), WithMaxTries(2)) 142 require.Regexp(t, "test", errors.Cause(err)) 143 require.Equal(t, callCount, 2) 144 145 callCount = 0 146 err = Do(context.Background(), f, WithBackoffBaseDelay(math.MaxInt64), WithBackoffMaxDelay(math.MaxInt64), WithMaxTries(2)) 147 require.Regexp(t, "test", errors.Cause(err)) 148 require.Equal(t, callCount, 2) 149 150 var i uint64 151 for i = 0; i < 10; i++ { 152 callCount = 0 153 err = Do(context.Background(), f, 154 WithBackoffBaseDelay(int64(i)), WithBackoffMaxDelay(int64(i)), WithMaxTries(i)) 155 require.Regexp(t, "test", errors.Cause(err)) 156 require.Regexp(t, ".*CDC:ErrReachMaxTry.*", err) 157 if i == 0 { 158 require.Equal(t, 1, callCount) 159 } else { 160 require.Equal(t, int(i), callCount) 161 } 162 } 163 } 164 165 func TestTotalRetryDuration(t *testing.T) { 166 t.Parallel() 167 168 f := func() error { 169 return errors.New("test") 170 } 171 172 start := time.Now() 173 err := Do( 174 context.Background(), f, 175 WithBackoffBaseDelay(math.MinInt64), 176 WithTotalRetryDuratoin(time.Second), 177 ) 178 require.Regexp(t, "test", errors.Cause(err)) 179 require.LessOrEqual(t, 1, int(math.Round(time.Since(start).Seconds()))) 180 181 start = time.Now() 182 err = Do( 183 context.Background(), f, 184 WithBackoffBaseDelay(math.MinInt64), 185 WithTotalRetryDuratoin(2*time.Second), 186 ) 187 require.Regexp(t, "test", errors.Cause(err)) 188 require.LessOrEqual(t, 2, int(math.Round(time.Since(start).Seconds()))) 189 } 190 191 func TestRetryError(t *testing.T) { 192 t.Parallel() 193 194 f := func() error { 195 return errors.New("some error info") 196 } 197 198 err := Do( 199 context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithMaxTries(2), 200 ) 201 require.Regexp(t, "some error info", errors.Cause(err)) 202 require.Regexp(t, ".*some error info.*", err.Error()) 203 require.Regexp(t, ".*CDC:ErrReachMaxTry.*", err.Error()) 204 }