github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/pacer/pacer_test.go (about) 1 package pacer 2 3 import ( 4 "sync" 5 "testing" 6 "time" 7 8 "github.com/pkg/errors" 9 "github.com/stretchr/testify/assert" 10 ) 11 12 func TestNew(t *testing.T) { 13 const expectedRetries = 7 14 const expectedConnections = 9 15 p := New(RetriesOption(expectedRetries), MaxConnectionsOption(expectedConnections)) 16 if d, ok := p.calculator.(*Default); ok { 17 assert.Equal(t, 10*time.Millisecond, d.minSleep) 18 assert.Equal(t, 2*time.Second, d.maxSleep) 19 assert.Equal(t, d.minSleep, p.state.SleepTime) 20 assert.Equal(t, uint(2), d.decayConstant) 21 assert.Equal(t, uint(1), d.attackConstant) 22 } else { 23 t.Errorf("calculator") 24 } 25 assert.Equal(t, expectedRetries, p.retries) 26 assert.Equal(t, 1, cap(p.pacer)) 27 assert.Equal(t, 1, len(p.pacer)) 28 assert.Equal(t, expectedConnections, p.maxConnections) 29 assert.Equal(t, expectedConnections, cap(p.connTokens)) 30 assert.Equal(t, 0, p.state.ConsecutiveRetries) 31 } 32 33 func TestMaxConnections(t *testing.T) { 34 p := New() 35 p.SetMaxConnections(20) 36 assert.Equal(t, 20, p.maxConnections) 37 assert.Equal(t, 20, cap(p.connTokens)) 38 p.SetMaxConnections(0) 39 assert.Equal(t, 0, p.maxConnections) 40 assert.Nil(t, p.connTokens) 41 } 42 43 func TestDecay(t *testing.T) { 44 c := NewDefault(MinSleep(1*time.Microsecond), MaxSleep(1*time.Second)) 45 for _, test := range []struct { 46 in State 47 attackConstant uint 48 want time.Duration 49 }{ 50 {State{SleepTime: 8 * time.Millisecond}, 1, 4 * time.Millisecond}, 51 {State{SleepTime: 1 * time.Millisecond}, 0, 1 * time.Microsecond}, 52 {State{SleepTime: 1 * time.Millisecond}, 2, (3 * time.Millisecond) / 4}, 53 {State{SleepTime: 1 * time.Millisecond}, 3, (7 * time.Millisecond) / 8}, 54 } { 55 c.decayConstant = test.attackConstant 56 got := c.Calculate(test.in) 57 assert.Equal(t, test.want, got, "test: %+v", test) 58 } 59 } 60 61 func TestAttack(t *testing.T) { 62 c := NewDefault(MinSleep(1*time.Microsecond), MaxSleep(1*time.Second)) 63 for _, test := range []struct { 64 in State 65 attackConstant uint 66 want time.Duration 67 }{ 68 {State{SleepTime: 1 * time.Millisecond, ConsecutiveRetries: 1}, 1, 2 * time.Millisecond}, 69 {State{SleepTime: 1 * time.Millisecond, ConsecutiveRetries: 1}, 0, 1 * time.Second}, 70 {State{SleepTime: 1 * time.Millisecond, ConsecutiveRetries: 1}, 2, (4 * time.Millisecond) / 3}, 71 {State{SleepTime: 1 * time.Millisecond, ConsecutiveRetries: 1}, 3, (8 * time.Millisecond) / 7}, 72 } { 73 c.attackConstant = test.attackConstant 74 got := c.Calculate(test.in) 75 assert.Equal(t, test.want, got, "test: %+v", test) 76 } 77 } 78 79 func TestSetRetries(t *testing.T) { 80 p := New() 81 p.SetRetries(18) 82 assert.Equal(t, 18, p.retries) 83 } 84 85 // emptyTokens empties the pacer of all its tokens 86 func emptyTokens(p *Pacer) { 87 for len(p.pacer) != 0 { 88 <-p.pacer 89 } 90 for len(p.connTokens) != 0 { 91 <-p.connTokens 92 } 93 } 94 95 // waitForPace waits for duration for the pace to arrive 96 // returns the time that it arrived or a zero time 97 func waitForPace(p *Pacer, duration time.Duration) (when time.Time) { 98 select { 99 case <-time.After(duration): 100 return 101 case <-p.pacer: 102 return time.Now() 103 } 104 } 105 106 func TestBeginCall(t *testing.T) { 107 p := New(MaxConnectionsOption(10), CalculatorOption(NewDefault(MinSleep(1*time.Millisecond)))) 108 emptyTokens(p) 109 go p.beginCall() 110 if !waitForPace(p, 10*time.Millisecond).IsZero() { 111 t.Errorf("beginSleep fired too early #1") 112 } 113 startTime := time.Now() 114 p.pacer <- struct{}{} 115 time.Sleep(1 * time.Millisecond) 116 connTime := time.Now() 117 p.connTokens <- struct{}{} 118 time.Sleep(1 * time.Millisecond) 119 paceTime := waitForPace(p, 1000*time.Millisecond) 120 if paceTime.IsZero() { 121 t.Errorf("beginSleep didn't fire") 122 } else if paceTime.Sub(startTime) < 0 { 123 t.Errorf("pace arrived before returning pace token") 124 } else if paceTime.Sub(connTime) < 0 { 125 t.Errorf("pace arrived before sending conn token") 126 } 127 } 128 129 func TestBeginCallZeroConnections(t *testing.T) { 130 p := New(MaxConnectionsOption(0), CalculatorOption(NewDefault(MinSleep(1*time.Millisecond)))) 131 emptyTokens(p) 132 go p.beginCall() 133 if !waitForPace(p, 10*time.Millisecond).IsZero() { 134 t.Errorf("beginSleep fired too early #1") 135 } 136 startTime := time.Now() 137 p.pacer <- struct{}{} 138 time.Sleep(1 * time.Millisecond) 139 paceTime := waitForPace(p, 1000*time.Millisecond) 140 if paceTime.IsZero() { 141 t.Errorf("beginSleep didn't fire") 142 } else if paceTime.Sub(startTime) < 0 { 143 t.Errorf("pace arrived before returning pace token") 144 } 145 } 146 147 func TestDefaultPacer(t *testing.T) { 148 c := NewDefault(MinSleep(1*time.Millisecond), MaxSleep(1*time.Second), DecayConstant(2)) 149 for _, test := range []struct { 150 state State 151 want time.Duration 152 }{ 153 {State{SleepTime: 1 * time.Millisecond, ConsecutiveRetries: 1}, 2 * time.Millisecond}, 154 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 1}, 1 * time.Second}, 155 {State{SleepTime: (3 * time.Second) / 4, ConsecutiveRetries: 1}, 1 * time.Second}, 156 {State{SleepTime: 1 * time.Second}, 750 * time.Millisecond}, 157 {State{SleepTime: 1000 * time.Microsecond}, 1 * time.Millisecond}, 158 {State{SleepTime: 1200 * time.Microsecond}, 1 * time.Millisecond}, 159 } { 160 got := c.Calculate(test.state) 161 assert.Equal(t, test.want, got, "test: %+v", test) 162 } 163 164 } 165 166 func TestAmazonCloudDrivePacer(t *testing.T) { 167 c := NewAmazonCloudDrive(MinSleep(1 * time.Millisecond)) 168 // Do lots of times because of the random number! 169 for _, test := range []struct { 170 state State 171 want time.Duration 172 }{ 173 {State{SleepTime: 1 * time.Millisecond, ConsecutiveRetries: 0}, 1 * time.Millisecond}, 174 {State{SleepTime: 10 * time.Millisecond, ConsecutiveRetries: 0}, 1 * time.Millisecond}, 175 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 1}, 500 * time.Millisecond}, 176 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 2}, 1 * time.Second}, 177 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 3}, 2 * time.Second}, 178 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 4}, 4 * time.Second}, 179 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 5}, 8 * time.Second}, 180 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 6}, 16 * time.Second}, 181 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 7}, 32 * time.Second}, 182 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 8}, 64 * time.Second}, 183 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 9}, 128 * time.Second}, 184 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 10}, 128 * time.Second}, 185 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 11}, 128 * time.Second}, 186 } { 187 const n = 1000 188 var sum time.Duration 189 // measure average time over n cycles 190 for i := 0; i < n; i++ { 191 sum += c.Calculate(test.state) 192 } 193 got := sum / n 194 assert.False(t, got < (test.want*9)/10 || got > (test.want*11)/10, "test: %+v", test) 195 } 196 } 197 198 func TestGoogleDrivePacer(t *testing.T) { 199 // Do lots of times because of the random number! 200 for _, test := range []struct { 201 state State 202 want time.Duration 203 }{ 204 {State{SleepTime: 1 * time.Millisecond}, 0}, 205 {State{SleepTime: 10 * time.Millisecond}, 0}, 206 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 1}, 1*time.Second + 500*time.Millisecond}, 207 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 2}, 2*time.Second + 500*time.Millisecond}, 208 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 3}, 4*time.Second + 500*time.Millisecond}, 209 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 4}, 8*time.Second + 500*time.Millisecond}, 210 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 5}, 16*time.Second + 500*time.Millisecond}, 211 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 6}, 16*time.Second + 500*time.Millisecond}, 212 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 7}, 16*time.Second + 500*time.Millisecond}, 213 } { 214 const n = 1000 215 var sum time.Duration 216 // measure average time over n cycles 217 for i := 0; i < n; i++ { 218 c := NewGoogleDrive(MinSleep(1 * time.Millisecond)) 219 sum += c.Calculate(test.state) 220 } 221 got := sum / n 222 assert.False(t, got < (test.want*9)/10 || got > (test.want*11)/10, "test: %+v, got: %v", test, got) 223 } 224 225 const minSleep = 2 * time.Millisecond 226 for _, test := range []struct { 227 calls int 228 want int 229 }{ 230 {1, 0}, 231 {9, 0}, 232 {10, 0}, 233 {11, 1}, 234 {12, 2}, 235 } { 236 c := NewGoogleDrive(MinSleep(minSleep), Burst(10)) 237 count := 0 238 for i := 0; i < test.calls; i++ { 239 sleep := c.Calculate(State{}) 240 if sleep != 0 { 241 count++ 242 } 243 } 244 assert.Equalf(t, test.want, count, "test: %+v, got: %v", test, count) 245 } 246 } 247 248 func TestS3Pacer(t *testing.T) { 249 c := NewS3(MinSleep(10*time.Millisecond), MaxSleep(1*time.Second), DecayConstant(2)) 250 for _, test := range []struct { 251 state State 252 want time.Duration 253 }{ 254 {State{SleepTime: 0, ConsecutiveRetries: 1}, 10 * time.Millisecond}, //Things were going ok, we failed once, back off to minSleep 255 {State{SleepTime: 10 * time.Millisecond, ConsecutiveRetries: 1}, 20 * time.Millisecond}, //Another fail, double the backoff 256 {State{SleepTime: 10 * time.Millisecond}, 0}, //Things start going ok when we're at minSleep; should result in no sleep 257 {State{SleepTime: 12 * time.Millisecond}, 0}, //*near* minsleep and going ok, decay would take below minSleep, should go to 0 258 {State{SleepTime: 0}, 0}, //Things have been going ok; not retrying should keep sleep at 0 259 {State{SleepTime: 1 * time.Second, ConsecutiveRetries: 1}, 1 * time.Second}, //Check maxSleep is enforced 260 {State{SleepTime: (3 * time.Second) / 4, ConsecutiveRetries: 1}, 1 * time.Second}, //Check attack heading to maxSleep doesn't exceed maxSleep 261 {State{SleepTime: 1 * time.Second}, 750 * time.Millisecond}, //Check decay from maxSleep 262 {State{SleepTime: 48 * time.Millisecond}, 36 * time.Millisecond}, //Check simple decay above minSleep 263 } { 264 got := c.Calculate(test.state) 265 assert.Equal(t, test.want, got, "test: %+v", test) 266 } 267 } 268 269 func TestEndCall(t *testing.T) { 270 p := New(MaxConnectionsOption(5)) 271 emptyTokens(p) 272 p.state.ConsecutiveRetries = 1 273 p.endCall(true, nil) 274 assert.Equal(t, 1, len(p.connTokens)) 275 assert.Equal(t, 2, p.state.ConsecutiveRetries) 276 } 277 278 func TestEndCallZeroConnections(t *testing.T) { 279 p := New(MaxConnectionsOption(0)) 280 emptyTokens(p) 281 p.state.ConsecutiveRetries = 1 282 p.endCall(false, nil) 283 assert.Equal(t, 0, len(p.connTokens)) 284 assert.Equal(t, 0, p.state.ConsecutiveRetries) 285 } 286 287 var errFoo = errors.New("foo") 288 289 type dummyPaced struct { 290 retry bool 291 called int 292 wait *sync.Cond 293 } 294 295 func (dp *dummyPaced) fn() (bool, error) { 296 if dp.wait != nil { 297 dp.wait.L.Lock() 298 dp.called++ 299 dp.wait.Wait() 300 dp.wait.L.Unlock() 301 } else { 302 dp.called++ 303 } 304 return dp.retry, errFoo 305 } 306 307 func TestCallFixed(t *testing.T) { 308 p := New(CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond)))) 309 310 dp := &dummyPaced{retry: false} 311 err := p.call(dp.fn, 10) 312 assert.Equal(t, 1, dp.called) 313 assert.Equal(t, errFoo, err) 314 } 315 316 func Test_callRetry(t *testing.T) { 317 p := New(CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond)))) 318 319 dp := &dummyPaced{retry: true} 320 err := p.call(dp.fn, 10) 321 assert.Equal(t, 10, dp.called) 322 assert.Equal(t, errFoo, err) 323 } 324 325 func TestCall(t *testing.T) { 326 p := New(RetriesOption(20), CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond)))) 327 328 dp := &dummyPaced{retry: true} 329 err := p.Call(dp.fn) 330 assert.Equal(t, 20, dp.called) 331 assert.Equal(t, errFoo, err) 332 } 333 334 func TestCallParallel(t *testing.T) { 335 p := New(MaxConnectionsOption(3), RetriesOption(1), CalculatorOption(NewDefault(MinSleep(100*time.Microsecond), MaxSleep(1*time.Millisecond)))) 336 337 wait := sync.NewCond(&sync.Mutex{}) 338 funcs := make([]*dummyPaced, 5) 339 for i := range funcs { 340 dp := &dummyPaced{wait: wait} 341 funcs[i] = dp 342 go func() { 343 assert.Equal(t, errFoo, p.CallNoRetry(dp.fn)) 344 }() 345 } 346 time.Sleep(250 * time.Millisecond) 347 called := 0 348 wait.L.Lock() 349 for _, dp := range funcs { 350 called += dp.called 351 } 352 wait.L.Unlock() 353 354 assert.Equal(t, 3, called) 355 wait.Broadcast() 356 time.Sleep(250 * time.Millisecond) 357 358 called = 0 359 wait.L.Lock() 360 for _, dp := range funcs { 361 called += dp.called 362 } 363 wait.L.Unlock() 364 365 assert.Equal(t, 5, called) 366 wait.Broadcast() 367 }