-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathpolicy_composition_test.go
More file actions
145 lines (120 loc) · 3.21 KB
/
Copy pathpolicy_composition_test.go
File metadata and controls
145 lines (120 loc) · 3.21 KB
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
// Copyright (c) 2026 Onur Cinar.
// The source code is provided under MIT License.
// https://github.com/cinar/resile
package resile
import (
"context"
"errors"
"github.com/cinar/resile/circuit"
"testing"
)
func TestPolicyComposition_Order(t *testing.T) {
t.Run("Retry wraps CircuitBreaker", func(t *testing.T) {
cb := circuit.New(circuit.Config{
MinimumCalls: 10,
FailureRateThreshold: 100,
})
// Retry(3) wraps CircuitBreaker.
// Each retry attempt goes through CB.
policy := NewPolicy(
WithRetry(3),
WithCircuitBreaker(cb),
)
ctx := context.Background()
var calls int
err := policy.DoErr(ctx, func(ctx context.Context) error {
calls++
return errors.New("fail")
})
if err == nil {
t.Fatal("expected error")
}
if calls != 3 {
t.Errorf("expected 3 calls, got %d", calls)
}
// CB should have seen 3 failures.
// We can't easily check internal failure count, but we can check if it's still closed.
if cb.State() != circuit.StateClosed {
t.Errorf("expected CB to be Closed, got %v", cb.State())
}
})
t.Run("CircuitBreaker wraps Retry", func(t *testing.T) {
cb := circuit.New(circuit.Config{
MinimumCalls: 2,
FailureRateThreshold: 100,
})
// CircuitBreaker wraps Retry(3).
// The whole retry loop is one CB execution.
policy := NewPolicy(
WithCircuitBreaker(cb),
WithRetry(3),
)
ctx := context.Background()
var calls int
err := policy.DoErr(ctx, func(ctx context.Context) error {
calls++
return errors.New("fail")
})
if err == nil {
t.Fatal("expected error")
}
if calls != 3 {
t.Errorf("expected 3 calls, got %d", calls)
}
// CB should have seen only 1 failure (the whole retry loop failing).
// Since threshold is 2, it should still be Closed.
if cb.State() != circuit.StateClosed {
t.Errorf("expected CB to be Closed, got %v", cb.State())
}
// Run it again to trip it.
_ = policy.DoErr(ctx, func(ctx context.Context) error {
return errors.New("fail")
})
if cb.State() != circuit.StateOpen {
t.Errorf("expected CB to be Open after 2nd failed retry loop, got %v", cb.State())
}
})
}
func TestPolicyComposition_Bulkhead(t *testing.T) {
policy := NewPolicy(
WithBulkhead(1),
)
ctx := context.Background()
start := make(chan struct{})
done := make(chan struct{})
go func() {
_ = policy.DoErr(ctx, func(ctx context.Context) error {
close(start)
<-done
return nil
})
}()
<-start
// Second call should fail due to bulkhead
err := policy.DoErr(ctx, func(ctx context.Context) error {
return nil
})
if !errors.Is(err, ErrBulkheadFull) {
t.Errorf("expected ErrBulkheadFull, got %v", err)
}
close(done)
}
func TestLegacyNew_DefaultOrder(t *testing.T) {
cb := circuit.New(circuit.Config{
MinimumCalls: 1,
FailureRateThreshold: 100,
})
// Legacy New should use default order: Retry wraps CB.
retryer := New(
WithMaxAttempts(3),
WithCircuitBreaker(cb),
)
ctx := context.Background()
_ = retryer.DoErr(ctx, func(ctx context.Context) error {
return errors.New("fail")
})
// If it was default order, CB should be Open after 3 attempts.
if cb.State() != circuit.StateOpen {
t.Errorf("expected CB to be Open with legacy New, got %v", cb.State())
}
}