go.uber.org/cadence@v1.2.9/internal/common/backoff/retrypolicy_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package backoff
    22  
    23  import (
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  )
    29  
    30  type TestClock struct {
    31  	currentTime time.Time
    32  }
    33  
    34  func TestExponentialBackoff(t *testing.T) {
    35  	t.Parallel()
    36  	policy := createPolicy(time.Second)
    37  	policy.SetMaximumInterval(10 * time.Second)
    38  
    39  	expectedResult := []time.Duration{1, 2, 4, 8, 10}
    40  	for i, d := range expectedResult {
    41  		expectedResult[i] = d * time.Second
    42  	}
    43  
    44  	r, _ := createRetrier(policy)
    45  	for _, expected := range expectedResult {
    46  		min, max := getNextBackoffRange(expected)
    47  		next := r.NextBackOff()
    48  		assert.True(t, next >= min, "NextBackoff too low")
    49  		assert.True(t, next < max, "NextBackoff too high")
    50  	}
    51  }
    52  
    53  func TestNumberOfAttempts(t *testing.T) {
    54  	t.Parallel()
    55  	policy := createPolicy(time.Second)
    56  	policy.SetMaximumAttempts(5)
    57  
    58  	r, _ := createRetrier(policy)
    59  	var next time.Duration
    60  	for i := 0; i < 6; i++ {
    61  		next = r.NextBackOff()
    62  	}
    63  
    64  	assert.Equal(t, done, next)
    65  }
    66  
    67  // Test to make sure relative maximum interval for each retry is honoured
    68  func TestMaximumInterval(t *testing.T) {
    69  	t.Parallel()
    70  	policy := createPolicy(time.Second)
    71  	policy.SetMaximumInterval(10 * time.Second)
    72  
    73  	expectedResult := []time.Duration{1, 2, 4, 8, 10, 10, 10, 10, 10, 10}
    74  	for i, d := range expectedResult {
    75  		expectedResult[i] = d * time.Second
    76  	}
    77  
    78  	r, _ := createRetrier(policy)
    79  	for _, expected := range expectedResult {
    80  		min, max := getNextBackoffRange(expected)
    81  		next := r.NextBackOff()
    82  		assert.True(t, next >= min, "NextBackoff too low")
    83  		assert.True(t, next < max, "NextBackoff too high")
    84  	}
    85  }
    86  
    87  func TestBackoffCoefficient(t *testing.T) {
    88  	t.Parallel()
    89  	policy := createPolicy(2 * time.Second)
    90  	policy.SetBackoffCoefficient(1.0)
    91  
    92  	r, _ := createRetrier(policy)
    93  	min, max := getNextBackoffRange(2 * time.Second)
    94  	for i := 0; i < 10; i++ {
    95  		next := r.NextBackOff()
    96  		assert.True(t, next >= min, "NextBackoff too low")
    97  		assert.True(t, next < max, "NextBackoff too high")
    98  	}
    99  }
   100  
   101  func TestExpirationInterval(t *testing.T) {
   102  	t.Parallel()
   103  	policy := createPolicy(2 * time.Second)
   104  	policy.SetExpirationInterval(5 * time.Minute)
   105  
   106  	r, clock := createRetrier(policy)
   107  	clock.moveClock(6 * time.Minute)
   108  	next := r.NextBackOff()
   109  
   110  	assert.Equal(t, done, next)
   111  }
   112  
   113  func TestExpirationOverflow(t *testing.T) {
   114  	t.Parallel()
   115  	policy := createPolicy(2 * time.Second)
   116  	policy.SetExpirationInterval(5 * time.Second)
   117  
   118  	r, clock := createRetrier(policy)
   119  	next := r.NextBackOff()
   120  	min, max := getNextBackoffRange(2 * time.Second)
   121  	assert.True(t, next >= min, "NextBackoff too low")
   122  	assert.True(t, next < max, "NextBackoff too high")
   123  
   124  	clock.moveClock(2 * time.Second)
   125  
   126  	next = r.NextBackOff()
   127  	min, max = getNextBackoffRange(3 * time.Second)
   128  	assert.True(t, next >= min, "NextBackoff too low")
   129  	assert.True(t, next < max, "NextBackoff too high")
   130  }
   131  
   132  func TestDefaultPublishRetryPolicy(t *testing.T) {
   133  	t.Parallel()
   134  	policy := NewExponentialRetryPolicy(50 * time.Millisecond)
   135  	policy.SetExpirationInterval(time.Minute)
   136  	policy.SetMaximumInterval(10 * time.Second)
   137  
   138  	r, clock := createRetrier(policy)
   139  	expectedResult := []time.Duration{
   140  		50 * time.Millisecond,
   141  		100 * time.Millisecond,
   142  		200 * time.Millisecond,
   143  		400 * time.Millisecond,
   144  		800 * time.Millisecond,
   145  		1600 * time.Millisecond,
   146  		3200 * time.Millisecond,
   147  		6400 * time.Millisecond,
   148  		10000 * time.Millisecond,
   149  		10000 * time.Millisecond,
   150  		10000 * time.Millisecond,
   151  		10000 * time.Millisecond,
   152  		7250 * time.Millisecond,
   153  		done,
   154  	}
   155  
   156  	for _, expected := range expectedResult {
   157  		next := r.NextBackOff()
   158  		if expected == done {
   159  			assert.Equal(t, done, next, "backoff not done yet!!!")
   160  		} else {
   161  			min, _ := getNextBackoffRange(expected)
   162  			assert.True(t, next >= min, "NextBackoff too low: actual: %v, expected: %v", next, expected)
   163  			// s.True(next < max, "NextBackoff too high: actual: %v, expected: %v", next, expected)
   164  			clock.moveClock(expected)
   165  		}
   166  	}
   167  }
   168  
   169  func TestNoMaxAttempts(t *testing.T) {
   170  	t.Parallel()
   171  	policy := createPolicy(50 * time.Millisecond)
   172  	policy.SetExpirationInterval(time.Minute)
   173  	policy.SetMaximumInterval(10 * time.Second)
   174  
   175  	r, clock := createRetrier(policy)
   176  	for i := 0; i < 100; i++ {
   177  		next := r.NextBackOff()
   178  		assert.True(t, next > 0 || next == done, "Unexpected value for next retry duration: %v", next)
   179  		clock.moveClock(next)
   180  	}
   181  }
   182  
   183  func TestUnbounded(t *testing.T) {
   184  	t.Parallel()
   185  	policy := createPolicy(50 * time.Millisecond)
   186  
   187  	r, clock := createRetrier(policy)
   188  	for i := 0; i < 100; i++ {
   189  		next := r.NextBackOff()
   190  		assert.True(t, next > 0 || next == done, "Unexpected value for next retry duration: %v", next)
   191  		clock.moveClock(next)
   192  	}
   193  }
   194  
   195  func (c *TestClock) Now() time.Time {
   196  	return c.currentTime
   197  }
   198  
   199  func (c *TestClock) moveClock(duration time.Duration) {
   200  	c.currentTime = c.currentTime.Add(duration)
   201  }
   202  
   203  func createPolicy(initialInterval time.Duration) *ExponentialRetryPolicy {
   204  	policy := NewExponentialRetryPolicy(initialInterval)
   205  	policy.SetBackoffCoefficient(2)
   206  	policy.SetMaximumInterval(NoInterval)
   207  	policy.SetExpirationInterval(NoInterval)
   208  	policy.SetMaximumAttempts(noMaximumAttempts)
   209  
   210  	return policy
   211  }
   212  
   213  func createRetrier(policy RetryPolicy) (Retrier, *TestClock) {
   214  	clock := &TestClock{currentTime: time.Time{}}
   215  	return NewRetrier(policy, clock), clock
   216  }
   217  
   218  func getNextBackoffRange(duration time.Duration) (time.Duration, time.Duration) {
   219  	rangeMin := time.Duration(0.8 * float64(duration))
   220  	return rangeMin, duration
   221  }