github.com/sandwich-go/boost@v1.3.29/retry/retry_test.go (about)

     1  package retry
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/sandwich-go/boost/xerror"
    12  
    13  	. "github.com/smartystreets/goconvey/convey"
    14  )
    15  
    16  func TestDoFirstOk(t *testing.T) {
    17  	Convey(`first call return ok`, t, func() {
    18  		var retrySum uint
    19  		err := Do(
    20  			func(uint) error { return nil },
    21  			WithOnRetry(func(n uint, err error) { retrySum += n }),
    22  		)
    23  		So(err, ShouldBeNil)
    24  		So(retrySum, ShouldEqual, 0)
    25  	})
    26  	Convey(`do once when limit is zero`, t, func() {
    27  		var doSum uint
    28  		err := Do(
    29  			func(uint) error {
    30  				doSum += 1
    31  				return nil
    32  			},
    33  			WithLimit(0),
    34  		)
    35  
    36  		So(err, ShouldBeNil)
    37  		So(doSum, ShouldEqual, 1)
    38  	})
    39  
    40  	Convey(`retry if`, t, func() {
    41  		var retryCount uint
    42  		err := Do(
    43  			func(uint) error {
    44  				if retryCount >= 2 {
    45  					return errors.New("special")
    46  				} else {
    47  					return errors.New("test")
    48  				}
    49  			},
    50  			WithOnRetry(func(n uint, err error) { retryCount++ }),
    51  			WithRetryIf(func(err error) bool {
    52  				return err.Error() != "special"
    53  			}),
    54  			WithDelay(time.Nanosecond),
    55  		)
    56  		So(err, ShouldNotBeNil)
    57  		var errWillBe xerror.Array
    58  		errWillBe.Push(errors.New("test"))
    59  		errWillBe.Push(errors.New("test"))
    60  		errWillBe.Push(errors.New("special"))
    61  		So(err.Error(), ShouldEqual, errWillBe.Error())
    62  	})
    63  
    64  	Convey(`default sleep`, t, func() {
    65  		start := time.Now()
    66  		err := Do(
    67  			func(uint) error { return errors.New("test") },
    68  			WithLimit(3),
    69  		)
    70  		So(err, ShouldNotBeNil)
    71  		dur := time.Since(start)
    72  		So(dur, ShouldBeGreaterThan, 3*newDefaultOptions().Delay)
    73  	})
    74  	Convey(`fixed sleep`, t, func() {
    75  		start := time.Now()
    76  		err := Do(
    77  			func(uint) error { return errors.New("test") },
    78  			WithLimit(3),
    79  			WithDelayType(FixedDelay),
    80  		)
    81  		So(err, ShouldNotBeNil)
    82  		dur := time.Since(start)
    83  		So(dur, ShouldBeLessThan, 4*newDefaultOptions().Delay)
    84  	})
    85  	Convey(`last error only`, t, func() {
    86  		var retrySum uint
    87  		err := Do(
    88  			func(uint) error { return fmt.Errorf("%d", retrySum) },
    89  			WithOnRetry(func(n uint, err error) { retrySum += 1 }),
    90  			WithDelay(time.Nanosecond),
    91  			WithLastErrorOnly(true),
    92  		)
    93  		So(err, ShouldNotBeNil)
    94  		So(err.Error(), ShouldEqual, "9")
    95  	})
    96  	Convey(`unrecoverable error`, t, func() {
    97  		attempts := 0
    98  		expectedErr := errors.New("error")
    99  		err := Do(
   100  			func(uint) error {
   101  				attempts++
   102  				return Unrecoverable(expectedErr)
   103  			},
   104  			WithLimit(2),
   105  			WithLastErrorOnly(true),
   106  		)
   107  		So(err, ShouldNotBeNil)
   108  		So(attempts, ShouldEqual, 1)
   109  	})
   110  
   111  	Convey(`max delay`, t, func() {
   112  		start := time.Now()
   113  		err := Do(
   114  			func(uint) error { return errors.New("test") },
   115  			WithLimit(5),
   116  			WithDelay(10*time.Millisecond),
   117  			WithMaxDelay(50*time.Millisecond),
   118  		)
   119  		dur := time.Since(start)
   120  		So(err, ShouldNotBeNil)
   121  		So(dur, ShouldBeLessThan, 205*time.Millisecond) // 重试5次,4个间隔*50ms
   122  		So(dur, ShouldBeGreaterThan, 150*time.Millisecond)
   123  	})
   124  
   125  	Convey(`with context`, t, func() {
   126  		ctx, cancel := context.WithCancel(context.Background())
   127  		cancel()
   128  		retrySum := 0
   129  		start := time.Now()
   130  		err := Do(
   131  			func(uint) error { return errors.New("test") },
   132  			WithOnRetry(func(n uint, err error) { retrySum += 1 }),
   133  			WithContext(ctx),
   134  		)
   135  		dur := time.Since(start)
   136  		So(err, ShouldNotBeNil)
   137  		So(dur, ShouldBeLessThan, newDefaultOptions().Delay)
   138  	})
   139  
   140  	Convey(`with context cancel in retry progress`, t, func() {
   141  		ctx, cancel := context.WithCancel(context.Background())
   142  
   143  		retrySum := 0
   144  		err := Do(
   145  			func(uint) error { return errors.New("test") },
   146  			WithOnRetry(func(n uint, err error) {
   147  				retrySum += 1
   148  				if retrySum > 1 {
   149  					cancel()
   150  				}
   151  			}),
   152  			WithContext(ctx),
   153  		)
   154  		So(err, ShouldNotBeNil)
   155  		So(retrySum, ShouldEqual, 2)
   156  	})
   157  
   158  	Convey(`just run`, t, func() {
   159  		var (
   160  			ClientPickerRetryLimit    uint = 10
   161  			ClientPickerRetryMaxDelay      = time.Duration(500) * time.Millisecond
   162  		)
   163  		last := time.Now()
   164  		_ = Do(
   165  			func(attempt uint) (errPick error) {
   166  				fmt.Println("attempt ", attempt, time.Since(last))
   167  				last = time.Now()
   168  				return fmt.Errorf("attempt %d", attempt)
   169  			},
   170  			WithLimit(ClientPickerRetryLimit),
   171  			WithMaxDelay(ClientPickerRetryMaxDelay))
   172  	})
   173  
   174  	Convey(`backoff delay`, t, func() {
   175  		for _, c := range []struct {
   176  			label         string
   177  			delay         time.Duration
   178  			expectedMaxN  int
   179  			n             uint
   180  			expectedDelay time.Duration
   181  		}{
   182  			{
   183  				label:         "negative-delay",
   184  				delay:         -1,
   185  				expectedMaxN:  62,
   186  				n:             2,
   187  				expectedDelay: 4,
   188  			},
   189  			{
   190  				label:         "zero-delay",
   191  				delay:         0,
   192  				expectedMaxN:  62,
   193  				n:             65,
   194  				expectedDelay: 1 << 62,
   195  			},
   196  			{
   197  				label:         "one-second",
   198  				delay:         time.Second,
   199  				expectedMaxN:  33,
   200  				n:             62,
   201  				expectedDelay: time.Second << 33,
   202  			},
   203  		} {
   204  			cc := Options{
   205  				Delay: c.delay,
   206  			}
   207  			delay := BackOffDelay(c.n, nil, &cc)
   208  			So(c.expectedMaxN, ShouldEqual, cc.MaxBackOffNInner)
   209  			So(c.expectedDelay, ShouldEqual, delay)
   210  		}
   211  	})
   212  }
   213  
   214  func TestRetryDelay(t *testing.T) {
   215  	log.Println("TestRetryDelay ==> ")
   216  	lastMilli := time.Now().UnixMilli()
   217  	_ = Do(func(attempt uint) error {
   218  		tt := time.Now().UnixMilli()
   219  		log.Println(tt, tt-lastMilli)
   220  		lastMilli = tt
   221  		return errors.New("some error")
   222  	}, WithDelay(time.Millisecond*100), WithLimit(3))
   223  }
   224  func TestBackoffDelay(t *testing.T) {
   225  	log.Println("TestBackoffDelay ==> ")
   226  	start := time.Now()
   227  	last := start
   228  	ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
   229  	defer cancelFunc()
   230  	_ = Do(
   231  		func(attempt uint) (errPick error) {
   232  			tt := time.Now()
   233  			log.Println(fmt.Sprintf("last:%s start:", time.Now().Sub(last)), time.Now().Sub(start))
   234  			last = tt
   235  			return errors.New("some error")
   236  		},
   237  		WithLimit(30),
   238  		WithDelay(time.Duration(30)*time.Millisecond),
   239  		WithMaxDelay(time.Second),
   240  		WithContext(ctx),
   241  		WithLastErrorOnly(true),
   242  	)
   243  	log.Println("TestBackoffDelay ==> ", fmt.Sprint(time.Now().Sub(start)))
   244  }