Files
httpx/retry/backoff_test.go
Aleksey Shakhmatov 505c7b8c4f Add retry transport with configurable backoff and Retry-After support
Implements retry middleware as a RoundTripper wrapper:
- Exponential and constant backoff strategies with jitter
- RFC 7231 Retry-After header parsing (seconds and HTTP-date)
- Default policy retries idempotent methods on 429/5xx and network errors
- Body restoration via GetBody, context cancellation, response body cleanup
2026-03-20 14:21:53 +03:00

78 lines
2.1 KiB
Go

package retry
import (
"testing"
"time"
)
func TestExponentialBackoff(t *testing.T) {
t.Run("doubles each attempt", func(t *testing.T) {
b := ExponentialBackoff(100*time.Millisecond, 10*time.Second, false)
want := []time.Duration{
100 * time.Millisecond, // attempt 0: base
200 * time.Millisecond, // attempt 1: base*2
400 * time.Millisecond, // attempt 2: base*4
800 * time.Millisecond, // attempt 3: base*8
1600 * time.Millisecond, // attempt 4: base*16
}
for i, expected := range want {
got := b.Delay(i)
if got != expected {
t.Errorf("attempt %d: expected %v, got %v", i, expected, got)
}
}
})
t.Run("caps at max", func(t *testing.T) {
b := ExponentialBackoff(100*time.Millisecond, 500*time.Millisecond, false)
// attempt 0: 100ms, 1: 200ms, 2: 400ms, 3: 500ms (capped), 4: 500ms
for _, attempt := range []int{3, 4, 10} {
got := b.Delay(attempt)
if got != 500*time.Millisecond {
t.Errorf("attempt %d: expected cap at 500ms, got %v", attempt, got)
}
}
})
t.Run("with jitter adds randomness", func(t *testing.T) {
base := 100 * time.Millisecond
b := ExponentialBackoff(base, 10*time.Second, true)
// Run multiple times; with jitter, delay >= base for attempt 0.
// Also verify not all values are identical (randomness).
seen := make(map[time.Duration]bool)
for range 20 {
d := b.Delay(0)
if d < base {
t.Fatalf("delay %v is less than base %v", d, base)
}
// With jitter: delay = base + rand in [0, base/2), so max is base*1.5
maxExpected := base + base/2
if d > maxExpected {
t.Fatalf("delay %v exceeds expected max %v", d, maxExpected)
}
seen[d] = true
}
if len(seen) < 2 {
t.Errorf("expected jitter to produce varying delays, got %d unique values", len(seen))
}
})
}
func TestConstantBackoff(t *testing.T) {
t.Run("always returns same value", func(t *testing.T) {
d := 250 * time.Millisecond
b := ConstantBackoff(d)
for _, attempt := range []int{0, 1, 2, 5, 100} {
got := b.Delay(attempt)
if got != d {
t.Errorf("attempt %d: expected %v, got %v", attempt, d, got)
}
}
})
}