github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/retry/retry_test.go (about)

     1  // Copyright 2021 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package retry
    15  
    16  import (
    17  	"context"
    18  	"math"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/errors"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func TestDoShouldRetryAtMostSpecifiedTimes(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	var callCount int
    30  	f := func() error {
    31  		callCount++
    32  		return errors.New("test")
    33  	}
    34  
    35  	err := Do(context.Background(), f, WithMaxTries(3))
    36  	require.Regexp(t, "test", errors.Cause(err))
    37  	require.Equal(t, callCount, 3)
    38  }
    39  
    40  func TestDoShouldStopOnSuccess(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	var callCount int
    44  	f := func() error {
    45  		callCount++
    46  		if callCount == 2 {
    47  			return nil
    48  		}
    49  		return errors.New("test")
    50  	}
    51  
    52  	err := Do(context.Background(), f, WithMaxTries(3))
    53  	require.Nil(t, err)
    54  	require.Equal(t, callCount, 2)
    55  }
    56  
    57  func TestIsRetryable(t *testing.T) {
    58  	t.Parallel()
    59  
    60  	var callCount int
    61  	f := func() error {
    62  		callCount++
    63  		return errors.Annotate(context.Canceled, "test")
    64  	}
    65  
    66  	err := Do(context.Background(), f, WithMaxTries(3), WithIsRetryableErr(func(err error) bool {
    67  		switch errors.Cause(err) {
    68  		case context.Canceled:
    69  			return false
    70  		}
    71  		return true
    72  	}))
    73  
    74  	require.Equal(t, errors.Cause(err), context.Canceled)
    75  	require.Equal(t, callCount, 1)
    76  
    77  	callCount = 0
    78  	err = Do(context.Background(), f, WithMaxTries(3))
    79  
    80  	require.Equal(t, errors.Cause(err), context.Canceled)
    81  	require.Equal(t, callCount, 3)
    82  }
    83  
    84  func TestDoCancelInfiniteRetry(t *testing.T) {
    85  	t.Parallel()
    86  
    87  	callCount := 0
    88  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*20)
    89  	defer cancel()
    90  	f := func() error {
    91  		select {
    92  		case <-ctx.Done():
    93  			return ctx.Err()
    94  		default:
    95  		}
    96  		callCount++
    97  		return errors.New("test")
    98  	}
    99  
   100  	err := Do(ctx, f, WithBackoffBaseDelay(2), WithBackoffMaxDelay(10))
   101  	require.Equal(t, errors.Cause(err), context.DeadlineExceeded)
   102  	require.GreaterOrEqual(t, callCount, 1, "tries: %d", callCount)
   103  	require.Less(t, callCount, math.MaxInt64)
   104  }
   105  
   106  func TestDoCancelAtBeginning(t *testing.T) {
   107  	t.Parallel()
   108  
   109  	callCount := 0
   110  	ctx, cancel := context.WithCancel(context.Background())
   111  	cancel()
   112  	f := func() error {
   113  		callCount++
   114  		return errors.New("test")
   115  	}
   116  
   117  	err := Do(ctx, f, WithBackoffBaseDelay(2), WithBackoffMaxDelay(10))
   118  	require.Equal(t, errors.Cause(err), context.Canceled)
   119  	require.Equal(t, callCount, 0, "tries:%d", callCount)
   120  }
   121  
   122  func TestDoCornerCases(t *testing.T) {
   123  	t.Parallel()
   124  
   125  	var callCount int
   126  	f := func() error {
   127  		callCount++
   128  		return errors.New("test")
   129  	}
   130  
   131  	err := Do(context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithBackoffMaxDelay(math.MaxInt64), WithMaxTries(2))
   132  	require.Regexp(t, "test", errors.Cause(err))
   133  	require.Equal(t, callCount, 2)
   134  
   135  	callCount = 0
   136  	err = Do(context.Background(), f, WithBackoffBaseDelay(math.MaxInt64), WithBackoffMaxDelay(math.MinInt64), WithMaxTries(2))
   137  	require.Regexp(t, "test", errors.Cause(err))
   138  	require.Equal(t, callCount, 2)
   139  
   140  	callCount = 0
   141  	err = Do(context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithBackoffMaxDelay(math.MinInt64), WithMaxTries(2))
   142  	require.Regexp(t, "test", errors.Cause(err))
   143  	require.Equal(t, callCount, 2)
   144  
   145  	callCount = 0
   146  	err = Do(context.Background(), f, WithBackoffBaseDelay(math.MaxInt64), WithBackoffMaxDelay(math.MaxInt64), WithMaxTries(2))
   147  	require.Regexp(t, "test", errors.Cause(err))
   148  	require.Equal(t, callCount, 2)
   149  
   150  	var i uint64
   151  	for i = 0; i < 10; i++ {
   152  		callCount = 0
   153  		err = Do(context.Background(), f,
   154  			WithBackoffBaseDelay(int64(i)), WithBackoffMaxDelay(int64(i)), WithMaxTries(i))
   155  		require.Regexp(t, "test", errors.Cause(err))
   156  		require.Regexp(t, ".*CDC:ErrReachMaxTry.*", err)
   157  		if i == 0 {
   158  			require.Equal(t, 1, callCount)
   159  		} else {
   160  			require.Equal(t, int(i), callCount)
   161  		}
   162  	}
   163  }
   164  
   165  func TestTotalRetryDuration(t *testing.T) {
   166  	t.Parallel()
   167  
   168  	f := func() error {
   169  		return errors.New("test")
   170  	}
   171  
   172  	start := time.Now()
   173  	err := Do(
   174  		context.Background(), f,
   175  		WithBackoffBaseDelay(math.MinInt64),
   176  		WithTotalRetryDuratoin(time.Second),
   177  	)
   178  	require.Regexp(t, "test", errors.Cause(err))
   179  	require.LessOrEqual(t, 1, int(math.Round(time.Since(start).Seconds())))
   180  
   181  	start = time.Now()
   182  	err = Do(
   183  		context.Background(), f,
   184  		WithBackoffBaseDelay(math.MinInt64),
   185  		WithTotalRetryDuratoin(2*time.Second),
   186  	)
   187  	require.Regexp(t, "test", errors.Cause(err))
   188  	require.LessOrEqual(t, 2, int(math.Round(time.Since(start).Seconds())))
   189  }
   190  
   191  func TestRetryError(t *testing.T) {
   192  	t.Parallel()
   193  
   194  	f := func() error {
   195  		return errors.New("some error info")
   196  	}
   197  
   198  	err := Do(
   199  		context.Background(), f, WithBackoffBaseDelay(math.MinInt64), WithMaxTries(2),
   200  	)
   201  	require.Regexp(t, "some error info", errors.Cause(err))
   202  	require.Regexp(t, ".*some error info.*", err.Error())
   203  	require.Regexp(t, ".*CDC:ErrReachMaxTry.*", err.Error())
   204  }