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  }