github.com/sandwich-go/boost@v1.3.29/retry/retry_test.go (about) 1 package retry 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "log" 8 "testing" 9 "time" 10 11 "github.com/sandwich-go/boost/xerror" 12 13 . "github.com/smartystreets/goconvey/convey" 14 ) 15 16 func TestDoFirstOk(t *testing.T) { 17 Convey(`first call return ok`, t, func() { 18 var retrySum uint 19 err := Do( 20 func(uint) error { return nil }, 21 WithOnRetry(func(n uint, err error) { retrySum += n }), 22 ) 23 So(err, ShouldBeNil) 24 So(retrySum, ShouldEqual, 0) 25 }) 26 Convey(`do once when limit is zero`, t, func() { 27 var doSum uint 28 err := Do( 29 func(uint) error { 30 doSum += 1 31 return nil 32 }, 33 WithLimit(0), 34 ) 35 36 So(err, ShouldBeNil) 37 So(doSum, ShouldEqual, 1) 38 }) 39 40 Convey(`retry if`, t, func() { 41 var retryCount uint 42 err := Do( 43 func(uint) error { 44 if retryCount >= 2 { 45 return errors.New("special") 46 } else { 47 return errors.New("test") 48 } 49 }, 50 WithOnRetry(func(n uint, err error) { retryCount++ }), 51 WithRetryIf(func(err error) bool { 52 return err.Error() != "special" 53 }), 54 WithDelay(time.Nanosecond), 55 ) 56 So(err, ShouldNotBeNil) 57 var errWillBe xerror.Array 58 errWillBe.Push(errors.New("test")) 59 errWillBe.Push(errors.New("test")) 60 errWillBe.Push(errors.New("special")) 61 So(err.Error(), ShouldEqual, errWillBe.Error()) 62 }) 63 64 Convey(`default sleep`, t, func() { 65 start := time.Now() 66 err := Do( 67 func(uint) error { return errors.New("test") }, 68 WithLimit(3), 69 ) 70 So(err, ShouldNotBeNil) 71 dur := time.Since(start) 72 So(dur, ShouldBeGreaterThan, 3*newDefaultOptions().Delay) 73 }) 74 Convey(`fixed sleep`, t, func() { 75 start := time.Now() 76 err := Do( 77 func(uint) error { return errors.New("test") }, 78 WithLimit(3), 79 WithDelayType(FixedDelay), 80 ) 81 So(err, ShouldNotBeNil) 82 dur := time.Since(start) 83 So(dur, ShouldBeLessThan, 4*newDefaultOptions().Delay) 84 }) 85 Convey(`last error only`, t, func() { 86 var retrySum uint 87 err := Do( 88 func(uint) error { return fmt.Errorf("%d", retrySum) }, 89 WithOnRetry(func(n uint, err error) { retrySum += 1 }), 90 WithDelay(time.Nanosecond), 91 WithLastErrorOnly(true), 92 ) 93 So(err, ShouldNotBeNil) 94 So(err.Error(), ShouldEqual, "9") 95 }) 96 Convey(`unrecoverable error`, t, func() { 97 attempts := 0 98 expectedErr := errors.New("error") 99 err := Do( 100 func(uint) error { 101 attempts++ 102 return Unrecoverable(expectedErr) 103 }, 104 WithLimit(2), 105 WithLastErrorOnly(true), 106 ) 107 So(err, ShouldNotBeNil) 108 So(attempts, ShouldEqual, 1) 109 }) 110 111 Convey(`max delay`, t, func() { 112 start := time.Now() 113 err := Do( 114 func(uint) error { return errors.New("test") }, 115 WithLimit(5), 116 WithDelay(10*time.Millisecond), 117 WithMaxDelay(50*time.Millisecond), 118 ) 119 dur := time.Since(start) 120 So(err, ShouldNotBeNil) 121 So(dur, ShouldBeLessThan, 205*time.Millisecond) // 重试5次,4个间隔*50ms 122 So(dur, ShouldBeGreaterThan, 150*time.Millisecond) 123 }) 124 125 Convey(`with context`, t, func() { 126 ctx, cancel := context.WithCancel(context.Background()) 127 cancel() 128 retrySum := 0 129 start := time.Now() 130 err := Do( 131 func(uint) error { return errors.New("test") }, 132 WithOnRetry(func(n uint, err error) { retrySum += 1 }), 133 WithContext(ctx), 134 ) 135 dur := time.Since(start) 136 So(err, ShouldNotBeNil) 137 So(dur, ShouldBeLessThan, newDefaultOptions().Delay) 138 }) 139 140 Convey(`with context cancel in retry progress`, t, func() { 141 ctx, cancel := context.WithCancel(context.Background()) 142 143 retrySum := 0 144 err := Do( 145 func(uint) error { return errors.New("test") }, 146 WithOnRetry(func(n uint, err error) { 147 retrySum += 1 148 if retrySum > 1 { 149 cancel() 150 } 151 }), 152 WithContext(ctx), 153 ) 154 So(err, ShouldNotBeNil) 155 So(retrySum, ShouldEqual, 2) 156 }) 157 158 Convey(`just run`, t, func() { 159 var ( 160 ClientPickerRetryLimit uint = 10 161 ClientPickerRetryMaxDelay = time.Duration(500) * time.Millisecond 162 ) 163 last := time.Now() 164 _ = Do( 165 func(attempt uint) (errPick error) { 166 fmt.Println("attempt ", attempt, time.Since(last)) 167 last = time.Now() 168 return fmt.Errorf("attempt %d", attempt) 169 }, 170 WithLimit(ClientPickerRetryLimit), 171 WithMaxDelay(ClientPickerRetryMaxDelay)) 172 }) 173 174 Convey(`backoff delay`, t, func() { 175 for _, c := range []struct { 176 label string 177 delay time.Duration 178 expectedMaxN int 179 n uint 180 expectedDelay time.Duration 181 }{ 182 { 183 label: "negative-delay", 184 delay: -1, 185 expectedMaxN: 62, 186 n: 2, 187 expectedDelay: 4, 188 }, 189 { 190 label: "zero-delay", 191 delay: 0, 192 expectedMaxN: 62, 193 n: 65, 194 expectedDelay: 1 << 62, 195 }, 196 { 197 label: "one-second", 198 delay: time.Second, 199 expectedMaxN: 33, 200 n: 62, 201 expectedDelay: time.Second << 33, 202 }, 203 } { 204 cc := Options{ 205 Delay: c.delay, 206 } 207 delay := BackOffDelay(c.n, nil, &cc) 208 So(c.expectedMaxN, ShouldEqual, cc.MaxBackOffNInner) 209 So(c.expectedDelay, ShouldEqual, delay) 210 } 211 }) 212 } 213 214 func TestRetryDelay(t *testing.T) { 215 log.Println("TestRetryDelay ==> ") 216 lastMilli := time.Now().UnixMilli() 217 _ = Do(func(attempt uint) error { 218 tt := time.Now().UnixMilli() 219 log.Println(tt, tt-lastMilli) 220 lastMilli = tt 221 return errors.New("some error") 222 }, WithDelay(time.Millisecond*100), WithLimit(3)) 223 } 224 func TestBackoffDelay(t *testing.T) { 225 log.Println("TestBackoffDelay ==> ") 226 start := time.Now() 227 last := start 228 ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(5)*time.Second) 229 defer cancelFunc() 230 _ = Do( 231 func(attempt uint) (errPick error) { 232 tt := time.Now() 233 log.Println(fmt.Sprintf("last:%s start:", time.Now().Sub(last)), time.Now().Sub(start)) 234 last = tt 235 return errors.New("some error") 236 }, 237 WithLimit(30), 238 WithDelay(time.Duration(30)*time.Millisecond), 239 WithMaxDelay(time.Second), 240 WithContext(ctx), 241 WithLastErrorOnly(true), 242 ) 243 log.Println("TestBackoffDelay ==> ", fmt.Sprint(time.Now().Sub(start))) 244 }