pkg/utils/retry_test.go
Rama master · réplica de lectura del repositorio original.
package utils
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRetryWithBackoffSucceedsAfterRetry(t *testing.T) {
errTemporary := errors.New("temporary")
calls := 0
err := RetryWithBackoff(context.Background(), RetryOptions{
Attempts: 3,
}, func() error {
calls++
if calls < 3 {
return errTemporary
}
return nil
})
require.NoError(t, err)
assert.Equal(t, 3, calls)
}
func TestRetryWithBackoffValueReturnsSuccessfulValue(t *testing.T) {
errTemporary := errors.New("temporary")
calls := 0
value, err := RetryWithBackoffValue(context.Background(), RetryOptions{
Attempts: 3,
}, func() (string, error) {
calls++
if calls < 2 {
return "", errTemporary
}
return "done", nil
})
require.NoError(t, err)
assert.Equal(t, "done", value)
assert.Equal(t, 2, calls)
}
func TestRetryWithBackoffStopsOnNonRetryableError(t *testing.T) {
errRetryable := errors.New("retryable")
errFatal := errors.New("fatal")
calls := 0
err := RetryWithBackoff(context.Background(), RetryOptions{
Attempts: 5,
ShouldRetry: func(err error) bool {
return errors.Is(err, errRetryable)
},
}, func() error {
calls++
if calls == 1 {
return errRetryable
}
return errFatal
})
require.ErrorIs(t, err, errFatal)
assert.Equal(t, 2, calls)
}
func TestRetryWithBackoffCapsDelay(t *testing.T) {
errTemporary := errors.New("temporary")
var delays []time.Duration
err := RetryWithBackoff(context.Background(), RetryOptions{
Attempts: 4,
Delay: time.Nanosecond,
MaxDelay: 2 * time.Nanosecond,
OnRetry: func(_ int, _ error, delay time.Duration) {
delays = append(delays, delay)
},
}, func() error {
return errTemporary
})
require.ErrorIs(t, err, errTemporary)
assert.Equal(t, []time.Duration{
time.Nanosecond,
2 * time.Nanosecond,
2 * time.Nanosecond,
}, delays)
}
func TestRetryDelaySaturatesOnOverflow(t *testing.T) {
assert.Equal(t, maxRetryDelay, backoffDelay(maxRetryDelay, 1, 0))
assert.Equal(t, time.Hour, backoffDelay(maxRetryDelay, 1, time.Hour))
}
func TestRetryWithBackoffStopsWhenContextIsCanceled(t *testing.T) {
errTemporary := errors.New("temporary")
ctx, cancel := context.WithCancel(context.Background())
calls := 0
err := RetryWithBackoff(ctx, RetryOptions{
Attempts: 3,
Delay: time.Hour,
OnRetry: func(_ int, _ error, _ time.Duration) {
cancel()
},
}, func() error {
calls++
return errTemporary
})
require.ErrorIs(t, err, context.Canceled)
assert.Equal(t, 1, calls)
}
func TestRetryDelayWithJitter(t *testing.T) {
base := 4 * time.Second
hasJitter := false
for i := 0; i < 100; i++ {
delay := retryDelay(base, 0, 0, 0.25)
assert.GreaterOrEqual(t, delay, base)
assert.Less(t, delay, base+time.Second)
hasJitter = hasJitter || delay > base
}
assert.True(t, hasJitter)
}
func TestRetryWithExpBackoffRunsAtLeastOnce(t *testing.T) {
errTemporary := errors.New("temporary")
calls := 0
err := RetryWithExpBackoff(0, time.Nanosecond, func() error {
calls++
return errTemporary
})
require.ErrorIs(t, err, errTemporary)
assert.Equal(t, 1, calls)
}