@@ -2,17 +2,21 @@ package retry
2
2
3
3
import java .util .concurrent .TimeUnit
4
4
5
- import retry .RetryPolicies .*
6
5
import cats .Id
6
+ import cats .effect .IO
7
+ import cats .effect .std .Random
8
+ import cats .syntax .all .*
7
9
import org .scalacheck .{Arbitrary , Gen }
8
10
import org .scalacheck .Prop .forAll
9
- import munit .ScalaCheckSuite
11
+ import org .scalacheck .effect .PropF
12
+ import munit .{CatsEffectSuite , ScalaCheckSuite }
10
13
import retry .PolicyDecision .{DelayAndRetry , GiveUp }
14
+ import retry .RetryPolicies .*
11
15
12
16
import scala .concurrent .duration .*
13
17
import munit .Location
14
18
15
- class RetryPoliciesSuite extends ScalaCheckSuite :
19
+ class RetryPoliciesSuite extends CatsEffectSuite with ScalaCheckSuite :
16
20
17
21
given Arbitrary [RetryStatus ] = Arbitrary {
18
22
for
@@ -29,14 +33,14 @@ class RetryPoliciesSuite extends ScalaCheckSuite:
29
33
val genFiniteDuration : Gen [FiniteDuration ] =
30
34
Gen .posNum[Long ].map(FiniteDuration (_, TimeUnit .NANOSECONDS ))
31
35
32
- given Arbitrary [RetryPolicy [Id , Any ]] = Arbitrary {
36
+ given ( using Random [ IO ]) : Arbitrary [RetryPolicy [IO , Any ]] = Arbitrary {
33
37
Gen .oneOf(
34
- Gen .const(alwaysGiveUp[Id ]),
35
- genFiniteDuration.map(delay => constantDelay[Id ](delay)),
36
- genFiniteDuration.map(baseDelay => exponentialBackoff[Id ](baseDelay)),
37
- Gen .posNum[Int ].map(maxRetries => limitRetries[Id ](maxRetries)),
38
- genFiniteDuration.map(baseDelay => fibonacciBackoff[Id ](baseDelay)),
39
- genFiniteDuration.map(baseDelay => fullJitter[Id ](baseDelay))
38
+ Gen .const(alwaysGiveUp[IO ]),
39
+ genFiniteDuration.map(delay => constantDelay[IO ](delay)),
40
+ genFiniteDuration.map(baseDelay => exponentialBackoff[IO ](baseDelay)),
41
+ Gen .posNum[Int ].map(maxRetries => limitRetries[IO ](maxRetries)),
42
+ genFiniteDuration.map(baseDelay => fibonacciBackoff[IO ](baseDelay)),
43
+ genFiniteDuration.map(baseDelay => fullJitter[IO ](baseDelay))
40
44
)
41
45
}
42
46
@@ -94,39 +98,57 @@ class RetryPoliciesSuite extends ScalaCheckSuite:
94
98
}
95
99
96
100
test(" fullJitter - implement the AWS Full Jitter backoff algorithm" ) {
97
- val policy = fullJitter[Id ](100 .milliseconds)
101
+ val mkPolicy : IO [RetryPolicy [IO , Any ]] = Random .scalaUtilRandom[IO ].map { rnd =>
102
+ given Random [IO ] = rnd
103
+ fullJitter[IO ](100 .milliseconds)
104
+ }
98
105
val arbitraryCumulativeDelay = 999 .milliseconds
99
106
val arbitraryPreviousDelay = Some (999 .milliseconds)
100
107
101
- def check (retriesSoFar : Int , expectedMaximumDelay : FiniteDuration ): Unit =
108
+ case class TestCase (retriesSoFar : Int , expectedMaximumDelay : FiniteDuration )
109
+
110
+ def check (testCase : TestCase ): IO [Unit ] =
102
111
val status = RetryStatus (
103
- retriesSoFar,
112
+ testCase. retriesSoFar,
104
113
arbitraryCumulativeDelay,
105
114
arbitraryPreviousDelay
106
115
)
107
- for _ <- 1 to 1000 do
108
- val verdict = policy.decideNextRetry((), status)
109
- val delay = verdict.asInstanceOf [PolicyDecision .DelayAndRetry ].delay
110
- assert(delay >= Duration .Zero )
111
- assert(delay < expectedMaximumDelay)
116
+ (1 to 1000 ).toList.traverse_ { i =>
117
+ for
118
+ policy <- mkPolicy
119
+ verdict <- policy.decideNextRetry((), status)
120
+ yield
121
+ val delay = verdict.asInstanceOf [PolicyDecision .DelayAndRetry ].delay
122
+ assert(clue(delay) >= Duration .Zero )
123
+ assert(clue(delay) < clue(testCase.expectedMaximumDelay))
124
+ }
112
125
113
- check(0 , 100 .milliseconds)
114
- check(1 , 200 .milliseconds)
115
- check(2 , 400 .milliseconds)
116
- check(3 , 800 .milliseconds)
117
- check(4 , 1600 .milliseconds)
118
- check(5 , 3200 .milliseconds)
126
+ val cases = List (
127
+ TestCase (0 , 100 .milliseconds),
128
+ TestCase (1 , 200 .milliseconds),
129
+ TestCase (2 , 400 .milliseconds),
130
+ TestCase (3 , 800 .milliseconds),
131
+ TestCase (4 , 1600 .milliseconds),
132
+ TestCase (5 , 3200 .milliseconds)
133
+ )
134
+
135
+ cases.traverse_(check)
119
136
}
120
137
121
- property (
138
+ test (
122
139
" all built-in policies - never try to create a FiniteDuration of more than Long.MaxValue nanoseconds"
123
140
) {
124
- forAll((policy : RetryPolicy [Id , Any ], status : RetryStatus ) =>
125
- policy.decideNextRetry((), status) match
126
- case PolicyDecision .DelayAndRetry (nextDelay) =>
127
- nextDelay.toNanos <= Long .MaxValue
128
- case PolicyDecision .GiveUp => true
129
- )
141
+ Random .scalaUtilRandom[IO ].map { rnd =>
142
+ given Random [IO ] = rnd
143
+ PropF .forAllF((policy : RetryPolicy [IO , Any ], status : RetryStatus ) =>
144
+ policy.decideNextRetry((), status).map {
145
+ case PolicyDecision .DelayAndRetry (nextDelay) =>
146
+ assert(nextDelay.toNanos <= Long .MaxValue )
147
+ case PolicyDecision .GiveUp =>
148
+ assert(true )
149
+ }
150
+ )
151
+ }
130
152
}
131
153
132
154
property(" limitRetries - retry with no delay until the limit is reached" ) {
0 commit comments