1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
package errors
import (
"context"
"math/rand"
"testing"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano()) // Ensure jitter randomness
}
// TestExecuteReply_Success tests successful execution after retries with a string result.
func TestExecuteReply_Success(t *testing.T) {
retry := NewRetry(
WithMaxAttempts(3),
WithDelay(50*time.Millisecond),
WithBackoff(LinearBackoff{}),
WithJitter(false),
)
calls := 0
start := time.Now()
result, err := ExecuteReply[string](retry, func() (string, error) {
calls++
if calls < 2 {
return "", New("temporary error").WithRetryable()
}
return "success", nil
})
duration := time.Since(start)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result != "success" {
t.Errorf("Expected 'success', got %q", result)
}
if calls != 2 {
t.Errorf("Expected 2 calls, got %d", calls)
}
if duration < 45*time.Millisecond { // Slightly less than 50ms for execution overhead
t.Errorf("Expected at least 50ms delay, got %v", duration)
}
}
func TestExecuteReply_Failure(t *testing.T) {
retry := NewRetry(
WithMaxAttempts(2),
WithDelay(10*time.Millisecond),
)
calls := 0
result, err := ExecuteReply[int](retry, func() (int, error) {
calls++
return 0, New("persistent error").WithRetryable()
})
if err == nil {
t.Error("Expected error, got nil")
}
if result != 0 {
t.Errorf("Expected zero value (0), got %d", result)
}
if calls != 2 {
t.Errorf("Expected 2 calls, got %d", calls)
}
}
func TestExecuteReply_NonRetryable(t *testing.T) {
retry := NewRetry(WithMaxAttempts(3))
calls := 0
result, err := ExecuteReply[float64](retry, func() (float64, error) {
calls++
return 0.0, New("fatal error") // Not retryable
})
if err == nil {
t.Error("Expected error, got nil")
}
if result != 0.0 {
t.Errorf("Expected zero value (0.0), got %f", result)
}
if calls != 1 {
t.Errorf("Expected 1 call, got %d", calls)
}
}
func TestExecuteReply_ContextCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
retry := NewRetry(
WithMaxAttempts(5),
WithContext(ctx),
WithDelay(50*time.Millisecond),
)
calls := 0
go func() {
time.Sleep(125 * time.Millisecond) // Allow 2 calls (100ms total) before cancel
cancel()
}()
result, err := ExecuteReply[string](retry, func() (string, error) {
calls++
time.Sleep(25 * time.Millisecond) // Simulate work
return "", New("retryable error").WithRetryable()
})
if !Is(err, context.Canceled) {
t.Errorf("Expected context canceled error, got %v", err)
}
if result != "" {
t.Errorf("Expected zero value (\"\"), got %q", result)
}
if calls < 2 {
t.Errorf("Expected at least 2 calls before cancellation, got %d", calls)
}
}
func TestExecuteReply_DifferentTypes(t *testing.T) {
type Result struct {
Value int
}
retry := NewRetry(WithMaxAttempts(3))
calls := 0
result, err := ExecuteReply[Result](retry, func() (Result, error) {
calls++
if calls < 2 {
return Result{}, New("temporary error").WithRetryable()
}
return Result{Value: 42}, nil
})
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result.Value != 42 {
t.Errorf("Expected Value 42, got %d", result.Value)
}
if calls != 2 {
t.Errorf("Expected 2 calls, got %d", calls)
}
}
|