mirror of
https://github.com/zap-proto/fiber.git
synced 2026-10-30 15:06:23 +00:00
171 lines
4.0 KiB
Go
171 lines
4.0 KiB
Go
package retry
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func Test_ExponentialBackoff_Retry(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
expErr error
|
|
expBackoff *ExponentialBackoff
|
|
f func() error
|
|
name string
|
|
}{
|
|
{
|
|
name: "With default values - successful",
|
|
expBackoff: NewExponentialBackoff(),
|
|
f: func() error {
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
name: "Successful function",
|
|
expBackoff: &ExponentialBackoff{
|
|
InitialInterval: 1 * time.Millisecond,
|
|
MaxBackoffTime: 100 * time.Millisecond,
|
|
Multiplier: 2.0,
|
|
MaxRetryCount: 5,
|
|
},
|
|
f: func() error {
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
name: "Unsuccessful function",
|
|
expBackoff: &ExponentialBackoff{
|
|
InitialInterval: 2 * time.Millisecond,
|
|
MaxBackoffTime: 100 * time.Millisecond,
|
|
Multiplier: 2.0,
|
|
MaxRetryCount: 5,
|
|
},
|
|
f: func() error {
|
|
return errors.New("failed function")
|
|
},
|
|
expErr: errors.New("failed function"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
err := tt.expBackoff.Retry(tt.f)
|
|
require.Equal(t, tt.expErr, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_ExponentialBackoff_Retry_NoSleepAfterLastAttempt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
largeInterval = 5 * time.Second // would be used for sleep if bug existed
|
|
maxAcceptable = 2 * time.Second // Retry must return well before largeInterval
|
|
)
|
|
|
|
eb := &ExponentialBackoff{
|
|
InitialInterval: largeInterval,
|
|
MaxBackoffTime: largeInterval * 2,
|
|
Multiplier: 2.0,
|
|
MaxRetryCount: 1,
|
|
}
|
|
|
|
start := time.Now()
|
|
err := eb.Retry(func() error { return errors.New("only attempt") })
|
|
elapsed := time.Since(start)
|
|
|
|
require.Error(t, err)
|
|
require.Equal(t, "only attempt", err.Error())
|
|
require.Less(t, elapsed, maxAcceptable,
|
|
"Retry must not sleep after the last failed attempt; took %v (expected < %v)", elapsed, maxAcceptable)
|
|
}
|
|
|
|
func Test_ExponentialBackoff_Next(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
expBackoff *ExponentialBackoff
|
|
expNextTimeIntervals []time.Duration
|
|
}{
|
|
{
|
|
name: "With default values",
|
|
expBackoff: NewExponentialBackoff(),
|
|
expNextTimeIntervals: []time.Duration{
|
|
1 * time.Second,
|
|
2 * time.Second,
|
|
4 * time.Second,
|
|
8 * time.Second,
|
|
16 * time.Second,
|
|
32 * time.Second,
|
|
32 * time.Second,
|
|
32 * time.Second,
|
|
32 * time.Second,
|
|
32 * time.Second,
|
|
},
|
|
},
|
|
{
|
|
name: "Custom values",
|
|
expBackoff: &ExponentialBackoff{
|
|
InitialInterval: 2.0 * time.Second,
|
|
MaxBackoffTime: 64 * time.Second,
|
|
Multiplier: 3.0,
|
|
MaxRetryCount: 8,
|
|
currentInterval: 2.0 * time.Second,
|
|
},
|
|
expNextTimeIntervals: []time.Duration{
|
|
2 * time.Second,
|
|
6 * time.Second,
|
|
18 * time.Second,
|
|
54 * time.Second,
|
|
64 * time.Second,
|
|
64 * time.Second,
|
|
64 * time.Second,
|
|
64 * time.Second,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
for i := range tt.expBackoff.MaxRetryCount {
|
|
next := tt.expBackoff.next()
|
|
if next < tt.expNextTimeIntervals[i] || next > tt.expNextTimeIntervals[i]+1*time.Second {
|
|
t.Errorf("wrong next time:\n"+
|
|
"actual:%v\n"+
|
|
"expected range:%v-%v\n",
|
|
next, tt.expNextTimeIntervals[i], tt.expNextTimeIntervals[i]+1*time.Second)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_ExponentialBackoff_NextRandFailure(t *testing.T) {
|
|
// Backup original reader and restore at the end
|
|
original := rand.Reader
|
|
defer func() { rand.Reader = original }()
|
|
rand.Reader = failingReader{}
|
|
|
|
expBackoff := &ExponentialBackoff{
|
|
InitialInterval: 1 * time.Second,
|
|
MaxBackoffTime: 10 * time.Second,
|
|
Multiplier: 2,
|
|
MaxRetryCount: 3,
|
|
currentInterval: 1 * time.Second,
|
|
}
|
|
next := expBackoff.next()
|
|
require.Equal(t, expBackoff.MaxBackoffTime, next)
|
|
// currentInterval should not change when random fails
|
|
require.Equal(t, 1*time.Second, expBackoff.currentInterval)
|
|
}
|
|
|
|
type failingReader struct{}
|
|
|
|
func (failingReader) Read(_ []byte) (int, error) { return 0, errors.New("fail") }
|