Skip to content

Commit d09461f

Browse files
committed
Merge avast#128 (FullJitterBackoffDelay)
1 parent cbf4dbf commit d09461f

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

retry_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"math"
78
"os"
89
"testing"
910
"time"
@@ -642,3 +643,71 @@ func TestIsRecoverable(t *testing.T) {
642643
err = fmt.Errorf("wrapping: %w", err)
643644
assert.False(t, IsRecoverable(err))
644645
}
646+
647+
func TestFullJitterBackoffDelay(t *testing.T) {
648+
// Seed for predictable randomness in tests
649+
// In real usage, math/rand is auto-seeded in Go 1.20+ or should be seeded once at program start.
650+
// For library test predictability, local seeding is fine.
651+
// However, retry-go's RandomDelay uses global math/rand without explicit seeding in tests.
652+
// Let's follow the existing pattern of not explicitly seeding in each test for now,
653+
// assuming test runs are isolated enough or that exact delay values aren't asserted,
654+
// but rather ranges or properties.
655+
656+
baseDelay := 50 * time.Millisecond
657+
maxDelay := 500 * time.Millisecond
658+
659+
config := &Config{
660+
delay: baseDelay,
661+
maxDelay: maxDelay,
662+
// other fields can be zero/default for this test
663+
}
664+
665+
attempts := []uint{0, 1, 2, 3, 4, 5, 6, 10}
666+
667+
for _, n := range attempts {
668+
delay := FullJitterBackoffDelay(n, errors.New("test error"), config)
669+
670+
expectedMaxCeiling := float64(baseDelay) * math.Pow(2, float64(n))
671+
if expectedMaxCeiling > float64(maxDelay) {
672+
expectedMaxCeiling = float64(maxDelay)
673+
}
674+
675+
assert.True(t, delay >= 0, "Delay should be non-negative. Got: %v for attempt %d", delay, n)
676+
assert.True(t, delay <= time.Duration(expectedMaxCeiling),
677+
"Delay %v should be less than or equal to current backoff ceiling %v for attempt %d", delay, time.Duration(expectedMaxCeiling), n)
678+
679+
t.Logf("Attempt %d: BaseDelay=%v, MaxDelay=%v, Calculated Ceiling=~%v, Actual Delay=%v",
680+
n, baseDelay, maxDelay, time.Duration(expectedMaxCeiling), delay)
681+
682+
// Test with MaxDelay disabled (0)
683+
configNoMax := &Config{delay: baseDelay, maxDelay: 0}
684+
delayNoMax := FullJitterBackoffDelay(n, errors.New("test error"), configNoMax)
685+
expectedCeilingNoMax := float64(baseDelay) * math.Pow(2, float64(n))
686+
if expectedCeilingNoMax > float64(10*time.Minute) { // Avoid overflow for very large N
687+
expectedCeilingNoMax = float64(10 * time.Minute)
688+
}
689+
assert.True(t, delayNoMax >= 0, "Delay (no max) should be non-negative. Got: %v for attempt %d", delayNoMax, n)
690+
assert.True(t, delayNoMax <= time.Duration(expectedCeilingNoMax),
691+
"Delay (no max) %v should be less than or equal to current backoff ceiling %v for attempt %d", delayNoMax, time.Duration(expectedCeilingNoMax), n)
692+
}
693+
694+
// Test case where baseDelay might be zero
695+
configZeroBase := &Config{delay: 0, maxDelay: maxDelay}
696+
delayZeroBase := FullJitterBackoffDelay(0, errors.New("test error"), configZeroBase)
697+
assert.Equal(t, time.Duration(0), delayZeroBase, "Delay with zero base delay should be 0")
698+
699+
delayZeroBaseAttempt1 := FullJitterBackoffDelay(1, errors.New("test error"), configZeroBase)
700+
assert.Equal(t, time.Duration(0), delayZeroBaseAttempt1, "Delay with zero base delay (attempt > 0) should be 0")
701+
702+
// Test with very small base delay
703+
smallBaseDelay := 1 * time.Nanosecond
704+
configSmallBase := &Config{delay: smallBaseDelay, maxDelay: 100 * time.Nanosecond}
705+
for i := uint(0); i < 5; i++ {
706+
d := FullJitterBackoffDelay(i, errors.New("test"), configSmallBase)
707+
ceil := float64(smallBaseDelay) * math.Pow(2, float64(i))
708+
if ceil > 100 {
709+
ceil = 100
710+
}
711+
assert.True(t, d <= time.Duration(ceil))
712+
}
713+
}

0 commit comments

Comments
 (0)