Skip to content

Commit b8eed1b

Browse files
committed
Retry: migrate tests to munit
1 parent a137204 commit b8eed1b

3 files changed

Lines changed: 585 additions & 584 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package cats.effect.std
2+
3+
import cats.{Hash, Show}
4+
import cats.data.EitherT
5+
import cats.effect.{BaseSuite, IO, Temporal}
6+
import cats.mtl.Handle
7+
import cats.syntax.all._
8+
9+
import scala.concurrent.duration._
10+
11+
class RetryMTLSuite extends BaseSuite {
12+
import Retry.{Decision, Status}
13+
14+
sealed trait Errors
15+
final class Error1 extends Errors
16+
final class Error2 extends Errors
17+
18+
type RetryAttempt = (Status, Decision, Errors)
19+
20+
ticked("give up on mismatched errors") { implicit ticker =>
21+
type F[A] = EitherT[IO, Errors, A]
22+
23+
val maxRetries = 2
24+
val delay = 1.second
25+
26+
val error = new Error2
27+
val policy = Retry
28+
.constantDelay[F, Errors](delay)
29+
.withMaxRetries(maxRetries)
30+
.withErrorMatcher(Retry.ErrorMatcher[F, Errors].only[Error1])
31+
32+
val expected: List[RetryAttempt] = List(
33+
(Status(0, Duration.Zero), Decision.giveUp, error)
34+
)
35+
36+
val io: F[Unit] = Handle[F, Errors].raise[Errors, Unit](error)
37+
38+
val run =
39+
for {
40+
ref <- IO.ref(List.empty[RetryAttempt])
41+
result <- mtlRetry[F, Errors, Unit](
42+
io,
43+
policy,
44+
(s, e: Errors, d) => EitherT.liftF(ref.update(_ :+ ((s, d, e))))
45+
).value
46+
attempts <- ref.get
47+
} yield (result, attempts)
48+
49+
assertCompleteAs(run, (Left(error), expected))
50+
}
51+
52+
ticked("retry only on matching errors") { implicit ticker =>
53+
type F[A] = EitherT[IO, Errors, A]
54+
55+
val maxRetries = 2
56+
val delay = 1.second
57+
58+
val error = new Error1
59+
val policy = Retry
60+
.constantDelay[F, Errors](delay)
61+
.withMaxRetries(maxRetries)
62+
.withErrorMatcher(Retry.ErrorMatcher[F, Errors].only[Error1])
63+
64+
val expected: List[RetryAttempt] = List(
65+
(Status(0, Duration.Zero), Decision.retry(delay), error),
66+
(Status(1, 1.second), Decision.retry(delay), error),
67+
(Status(2, 2.seconds), Decision.giveUp, error)
68+
)
69+
70+
val io: F[Unit] = Handle[F, Errors].raise[Errors, Unit](error)
71+
72+
val run =
73+
for {
74+
ref <- IO.ref(List.empty[RetryAttempt])
75+
result <- mtlRetry[F, Errors, Unit](
76+
io,
77+
policy,
78+
(s, e: Errors, d) => EitherT.liftF(ref.update(_ :+ ((s, d, e))))
79+
).value
80+
attempts <- ref.get
81+
} yield (result, attempts)
82+
83+
assertCompleteAs(run, (Left(error), expected))
84+
}
85+
86+
private def mtlRetry[F[_], E, A](
87+
action: F[A],
88+
policy: Retry[F, E],
89+
onRetry: (Status, E, Decision) => F[Unit]
90+
)(implicit F: Temporal[F], H: Handle[F, E]): F[A] =
91+
F.tailRecM(Status.initial) { status =>
92+
H.attempt(action).flatMap {
93+
case Left(error) =>
94+
policy
95+
.decide(status, error)
96+
.flatTap(decision => onRetry(status, error, decision))
97+
.flatMap {
98+
case retry: Decision.Retry =>
99+
F.delayBy(F.pure(Left(status.withRetry(retry.delay))), retry.delay)
100+
101+
case _: Decision.GiveUp =>
102+
H.raise(error)
103+
}
104+
105+
case Right(success) =>
106+
F.pure(Right(success))
107+
}
108+
}
109+
110+
private implicit val outputHash: Hash[(Either[Errors, Unit], List[RetryAttempt])] =
111+
Hash.fromUniversalHashCode
112+
113+
private implicit val outputShow: Show[(Either[Errors, Unit], List[RetryAttempt])] =
114+
Show.fromToString
115+
116+
}

0 commit comments

Comments
 (0)