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

     1  // Copyright (c) 2017-2020 Uber Technologies Inc.
     2  // Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc.
     3  //
     4  // Permission is hereby granted, free of charge, to any person obtaining a copy
     5  // of this software and associated documentation files (the "Software"), to deal
     6  // in the Software without restriction, including without limitation the rights
     7  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  // copies of the Software, and to permit persons to whom the Software is
     9  // furnished to do so, subject to the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be included in
    12  // all copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20  // THE SOFTWARE.
    21  
    22  package backoff
    23  
    24  import (
    25  	"context"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  
    31  	"go.uber.org/cadence/.gen/go/shared"
    32  )
    33  
    34  type errCategory int
    35  
    36  const (
    37  	noErr errCategory = iota
    38  	anyErr
    39  	serviceBusyErr
    40  )
    41  
    42  func TestRetry(t *testing.T) {
    43  	t.Parallel()
    44  
    45  	always := func(err error) bool {
    46  		return true
    47  	}
    48  	never := func(err error) bool {
    49  		return false
    50  	}
    51  
    52  	succeedOnAttemptNum := 5
    53  	tests := []struct {
    54  		name        string
    55  		maxAttempts int
    56  		maxTime     time.Duration // context timeout
    57  		isRetryable func(error) bool
    58  
    59  		err           errCategory
    60  		expectedCalls int
    61  	}{
    62  		{"success", 2 * succeedOnAttemptNum, time.Second, always, noErr, succeedOnAttemptNum},
    63  		{"too many tries", 3, time.Second, always, anyErr, 4}, // max 3 retries == 4 calls.  must be < succeedOnAttemptNum to work.
    64  		{"success with always custom retry", 2 * succeedOnAttemptNum, time.Second, always, noErr, succeedOnAttemptNum},
    65  		{"success with never custom retry", 2 * succeedOnAttemptNum, time.Second, never, anyErr, 1},
    66  
    67  		// elapsed-time-sensitive tests below.
    68  		// consider raising time granularity if flaky, or we could set up a more complete mock
    69  		// to resolve flakiness for real, but that's a fair bit more complex.
    70  
    71  		// try -> sleep(10ms) -> try -> sleep(20ms) -> try -> sleep(40ms) -> timeout == 3 calls.
    72  		{"timed out eventually", 5, 50 * time.Millisecond, always, anyErr, 3},
    73  
    74  		// try -> sleep(longer than context timeout due to busy err) -> timeout == 1 call.
    75  		{"timed out due to long minimum delay", 5, 10 * time.Millisecond, always, serviceBusyErr, 1},
    76  	}
    77  
    78  	for _, test := range tests {
    79  		test := test
    80  		t.Run(test.name, func(t *testing.T) {
    81  			t.Parallel()
    82  			i := 0
    83  			op := func() error {
    84  				i++
    85  
    86  				if i == succeedOnAttemptNum { // prevent infinite loops, and lets max-attempts > 5 eventually succeed
    87  					return nil
    88  				}
    89  
    90  				switch test.err {
    91  				case noErr:
    92  					return &someError{} // non-erroring tests should not reach this branch
    93  				case anyErr:
    94  					return &someError{}
    95  				case serviceBusyErr:
    96  					return &shared.ServiceBusyError{}
    97  				}
    98  				panic("unreachable")
    99  			}
   100  
   101  			policy := NewExponentialRetryPolicy(10 * time.Millisecond)
   102  			policy.SetMaximumInterval(50 * time.Millisecond)
   103  			policy.SetMaximumAttempts(test.maxAttempts)
   104  
   105  			ctx, cancel := context.WithTimeout(context.Background(), test.maxTime)
   106  			defer cancel()
   107  			err := Retry(ctx, op, policy, test.isRetryable)
   108  			if test.err == noErr {
   109  				assert.NoError(t, err, "Retry count: %v", i)
   110  			} else {
   111  				assert.Error(t, err)
   112  			}
   113  			assert.Equal(t, test.expectedCalls, i, "wrong number of calls")
   114  		})
   115  	}
   116  }
   117  
   118  func TestConcurrentRetrier(t *testing.T) {
   119  	t.Parallel()
   120  	a := assert.New(t)
   121  	policy := NewExponentialRetryPolicy(1 * time.Millisecond)
   122  	policy.SetMaximumInterval(10 * time.Millisecond)
   123  	policy.SetMaximumAttempts(4)
   124  
   125  	// Basic checks
   126  	retrier := NewConcurrentRetrier(policy)
   127  	retrier.Failed()
   128  	a.Equal(int64(1), retrier.failureCount)
   129  	retrier.Succeeded()
   130  	a.Equal(int64(0), retrier.failureCount)
   131  	sleepDuration := retrier.throttleInternal()
   132  	a.Equal(done, sleepDuration)
   133  
   134  	// Multiple count check.
   135  	retrier.Failed()
   136  	retrier.Failed()
   137  	a.Equal(int64(2), retrier.failureCount)
   138  	// Verify valid sleep times.
   139  	ch := make(chan time.Duration, 3)
   140  	go func() {
   141  		for i := 0; i < 3; i++ {
   142  			ch <- retrier.throttleInternal()
   143  		}
   144  	}()
   145  	for i := 0; i < 3; i++ {
   146  		val := <-ch
   147  		t.Logf("Duration: %d\n", val)
   148  		a.True(val > 0)
   149  	}
   150  	retrier.Succeeded()
   151  	a.Equal(int64(0), retrier.failureCount)
   152  	// Verify we don't have any sleep times.
   153  	go func() {
   154  		for i := 0; i < 3; i++ {
   155  			ch <- retrier.throttleInternal()
   156  		}
   157  	}()
   158  	for i := 0; i < 3; i++ {
   159  		val := <-ch
   160  		t.Logf("Duration: %d\n", val)
   161  		a.Equal(done, val)
   162  	}
   163  }
   164  
   165  type someError struct{}
   166  
   167  func (e *someError) Error() string {
   168  	return "Some Error"
   169  }