@@ -35,28 +35,137 @@ final class RetrySequenceTests: XCTestCase {
3535
3636 func test_thatRetrySequenceCreatesASequence_whenStrategyIsExponentialWithJitter( ) {
3737 // given
38+ let durationSeconds = 1.0
39+ let multiplier = 2.0
40+ let jitterFactor = 0.1
41+
3842 let sequence = RetrySequence (
3943 strategy: . exponentialWithJitter(
40- retry: . retry,
41- jitterFactor: . jitterFactor,
42- maxInterval: . maxInterval,
43- duration: . nanosecond
44+ retry: 5 ,
45+ jitterFactor: jitterFactor,
46+ maxInterval: nil ,
47+ multiplier: multiplier,
48+ duration: . seconds( Int ( durationSeconds) )
4449 )
4550 )
4651
4752 // when
4853 let result : [ UInt64 ] = sequence. map { $0 }
4954
5055 // then
51- XCTAssertEqual ( result. count, 8 )
52- XCTAssertEqual ( result [ 0 ] , 1 , accuracy: 1 )
53- XCTAssertEqual ( result [ 1 ] , 2 , accuracy: 1 )
54- XCTAssertEqual ( result [ 2 ] , 4 , accuracy: 1 )
55- XCTAssertEqual ( result [ 3 ] , 8 , accuracy: 1 )
56- XCTAssertEqual ( result [ 4 ] , 16 , accuracy: 2 )
57- XCTAssertEqual ( result [ 5 ] , 32 , accuracy: 4 )
58- XCTAssertEqual ( result [ 6 ] , 64 , accuracy: 7 )
59- XCTAssertEqual ( result [ 7 ] , . maxInterval)
56+ XCTAssertEqual ( result. count, 5 )
57+
58+ for (i, valueNanos) in result. enumerated ( ) {
59+ let seconds = toSeconds ( valueNanos)
60+
61+ let expectedBase = durationSeconds * pow( multiplier, Double ( i) )
62+
63+ let lowerBound = expectedBase * ( 1.0 - jitterFactor)
64+ let upperBound = expectedBase * ( 1.0 + jitterFactor)
65+
66+ XCTAssertTrue (
67+ seconds >= lowerBound && seconds <= upperBound,
68+ " Attempt \( i) : \( seconds) s should be between \( lowerBound) s and \( upperBound) s "
69+ )
70+ }
71+ }
72+
73+ func test_thatRetrySequenceRespectsMaxInterval_whenStrategyIsExponentialWithJitter( ) {
74+ // given
75+ let maxIntervalDuration : DispatchTimeInterval = . seconds( 10 )
76+ let maxIntervalNanos : UInt64 = 10 * 1_000_000_000
77+
78+ let sequence = RetrySequence (
79+ strategy: . exponentialWithJitter(
80+ retry: 10 ,
81+ jitterFactor: 0.1 ,
82+ maxInterval: maxIntervalDuration,
83+ multiplier: 2.0 ,
84+ duration: . seconds( 1 )
85+ )
86+ )
87+
88+ // when
89+ let result : [ UInt64 ] = sequence. map { $0 }
90+
91+ // then
92+ XCTAssertEqual ( result. count, 10 )
93+
94+ for (i, val) in result. enumerated ( ) {
95+ XCTAssertLessThanOrEqual ( val, maxIntervalNanos, " Attempt \( i) exceeded maxInterval " )
96+
97+ let expectedBaseSeconds = 1.0 * pow( 2.0 , Double ( i) )
98+
99+ if expectedBaseSeconds * ( 1.0 - 0.1 ) > 10.0 {
100+ XCTAssertEqual ( val, maxIntervalNanos, " Attempt \( i) should be capped at maxInterval " )
101+ }
102+ }
103+ }
104+
105+ func test_thatRetrySequenceAppliesJitter_whenStrategyIsExponentialWithJitter( ) {
106+ // given
107+ let strategy = RetryPolicyStrategy . exponentialWithJitter (
108+ retry: 30 ,
109+ jitterFactor: 0.5 ,
110+ maxInterval: nil ,
111+ multiplier: 2.0 ,
112+ duration: . milliseconds( 10 )
113+ )
114+
115+ let sequence1 = RetrySequence ( strategy: strategy)
116+ let sequence2 = RetrySequence ( strategy: strategy)
117+
118+ // when
119+ let result1 = sequence1. map { $0 }
120+ let result2 = sequence2. map { $0 }
121+
122+ // then
123+ XCTAssertEqual ( result1. count, 30 )
124+
125+ XCTAssertNotEqual ( result1, result2, " Two sequences with jitter should produce different values " )
126+
127+ for (i, val) in result1. enumerated ( ) {
128+ let seconds = toSeconds ( val)
129+
130+ let base = 0.01 * pow( 2.0 , Double ( i) )
131+
132+ let lower = base * 0.5
133+ let upper = base * 1.5
134+
135+ XCTAssertTrue (
136+ seconds >= lower && seconds <= upper,
137+ " Attempt \( i) : Value \( seconds) is out of bounds [ \( lower) , \( upper) ] "
138+ )
139+ }
140+ }
141+
142+ func test_thatRetrySequenceWorksWithoutMaxInterval_whenStrategyIsExponentialWithJitter( ) {
143+ // given
144+ let sequence = RetrySequence (
145+ strategy: . exponentialWithJitter(
146+ retry: 5 ,
147+ jitterFactor: 0.1 ,
148+ maxInterval: nil ,
149+ multiplier: 2.0 ,
150+ duration: . seconds( 1 )
151+ )
152+ )
153+
154+ // when
155+ let result : [ UInt64 ] = sequence. map { $0 }
156+
157+ // then
158+ XCTAssertEqual ( result. count, 5 )
159+
160+ for i in 1 ..< result. count {
161+ XCTAssertGreaterThan (
162+ result [ i] ,
163+ result [ i - 1 ] ,
164+ " Each delay should be greater than previous (exponential growth) "
165+ )
166+ }
167+
168+ XCTAssertGreaterThan ( result [ 4 ] , result [ 0 ] * 10 )
60169 }
61170
62171 func test_thatRetrySequenceDoesNotLimitASequence_whenStrategyIsExponentialWithJitterAndMaxIntervalIsNil( ) {
@@ -84,6 +193,12 @@ final class RetrySequenceTests: XCTestCase {
84193 XCTAssertEqual ( result [ 6 ] , 64 , accuracy: 8 )
85194 XCTAssertEqual ( result [ 7 ] , 128 , accuracy: 13 )
86195 }
196+
197+ // MARK: Helpers
198+
199+ private func toSeconds( _ nanos: UInt64 ) -> Double {
200+ Double ( nanos) / 1_000_000_000
201+ }
87202}
88203
89204// MARK: - Constant
0 commit comments