github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/retryutils/retry_test.go (about)

     1  /*
     2  Copyright 2021-2022 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package retryutils
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  // TestLinear tests retry logic
    27  func TestLinear(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	r, err := NewLinear(LinearConfig{
    31  		Step: time.Second,
    32  		Max:  3 * time.Second,
    33  	})
    34  	require.NoError(t, err)
    35  	testLinear(t, r)
    36  }
    37  
    38  func TestLinearV2(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	r2, err := NewRetryV2(RetryV2Config{
    42  		Driver: NewLinearDriver(time.Second),
    43  		Max:    3 * time.Second,
    44  	})
    45  	require.NoError(t, err)
    46  	testLinear(t, r2)
    47  }
    48  
    49  func testLinear(t *testing.T, r Retry) {
    50  	require.Equal(t, time.Duration(0), r.Duration())
    51  	r.Inc()
    52  	require.Equal(t, time.Second, r.Duration())
    53  	r.Inc()
    54  	require.Equal(t, 2*time.Second, r.Duration())
    55  	r.Inc()
    56  	require.Equal(t, 3*time.Second, r.Duration())
    57  	r.Inc()
    58  	require.Equal(t, 3*time.Second, r.Duration())
    59  	r.Reset()
    60  	require.Equal(t, time.Duration(0), r.Duration())
    61  }
    62  
    63  func TestExponential(t *testing.T) {
    64  	t.Parallel()
    65  
    66  	r, err := NewRetryV2(RetryV2Config{
    67  		Driver: NewExponentialDriver(time.Second),
    68  		Max:    12 * time.Second,
    69  	})
    70  	require.NoError(t, err)
    71  
    72  	require.Equal(t, time.Duration(0), r.Duration())
    73  	r.Inc()
    74  	require.Equal(t, time.Second, r.Duration())
    75  	r.Inc()
    76  	require.Equal(t, 2*time.Second, r.Duration())
    77  	r.Inc()
    78  	require.Equal(t, 4*time.Second, r.Duration())
    79  	r.Inc()
    80  	require.Equal(t, 8*time.Second, r.Duration())
    81  	r.Inc()
    82  	// should hit configured maximum
    83  	require.Equal(t, 12*time.Second, r.Duration())
    84  	r.Reset()
    85  	require.Equal(t, time.Duration(0), r.Duration())
    86  
    87  	// verify that exponentiation is capped s.t. we don't wrap
    88  	for i := 0; i < 128; i++ {
    89  		r.Inc()
    90  		require.True(t, r.Duration() > 0 && r.Duration() <= time.Second*12)
    91  	}
    92  }
    93  
    94  func TestLinearRetryMax(t *testing.T) {
    95  	t.Parallel()
    96  
    97  	cases := []struct {
    98  		desc              string
    99  		config            LinearConfig
   100  		previousCompareFn require.ComparisonAssertionFunc
   101  	}{
   102  		{
   103  			desc: "FullJitter",
   104  			config: LinearConfig{
   105  				First:  time.Second * 45,
   106  				Step:   time.Second * 30,
   107  				Max:    time.Minute,
   108  				Jitter: NewFullJitter(),
   109  			},
   110  			previousCompareFn: require.NotEqual,
   111  		},
   112  		{
   113  			desc: "HalfJitter",
   114  			config: LinearConfig{
   115  				First:  time.Second * 45,
   116  				Step:   time.Second * 30,
   117  				Max:    time.Minute,
   118  				Jitter: NewHalfJitter(),
   119  			},
   120  			previousCompareFn: require.NotEqual,
   121  		},
   122  		{
   123  			desc: "SeventhJitter",
   124  			config: LinearConfig{
   125  				First:  time.Second * 45,
   126  				Step:   time.Second * 30,
   127  				Max:    time.Minute,
   128  				Jitter: NewSeventhJitter(),
   129  			},
   130  			previousCompareFn: require.NotEqual,
   131  		},
   132  
   133  		{
   134  			desc: "NoJitter",
   135  			config: LinearConfig{
   136  				First: time.Second * 45,
   137  				Step:  time.Second * 30,
   138  				Max:   time.Minute,
   139  			},
   140  			previousCompareFn: require.Equal,
   141  		},
   142  	}
   143  
   144  	for _, tc := range cases {
   145  		tc := tc
   146  		t.Run(tc.desc, func(t *testing.T) {
   147  			t.Parallel()
   148  			linear, err := NewLinear(tc.config)
   149  			require.NoError(t, err)
   150  
   151  			// artificially spike the attempts to get to max
   152  			linear.attempt = 100
   153  
   154  			// get the initial previous value to compare with
   155  			previous := linear.Duration()
   156  			linear.Inc()
   157  
   158  			for i := 0; i < 50; i++ {
   159  				duration := linear.Duration()
   160  				linear.Inc()
   161  
   162  				// ensure duration does not exceed maximum
   163  				require.LessOrEqual(t, duration, tc.config.Max)
   164  
   165  				// ensure duration comparison to previous is satisfied
   166  				tc.previousCompareFn(t, duration, previous)
   167  			}
   168  		})
   169  	}
   170  }