File: retry_test.go

package info (click to toggle)
golang-github-olekukonko-errors 1.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid
  • size: 500 kB
  • sloc: makefile: 2
file content (146 lines) | stat: -rw-r--r-- 3,293 bytes parent folder | download | duplicates (2)
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)
	}
}