@@ -6,15 +6,17 @@ import zio.*
6
6
7
7
trait GenIOInteropCats {
8
8
9
- // FIXME generating anything but success (even genFail)
10
- // surfaces multiple further unaddressed law failures
9
+ // FIXME `genDie` and `genInternalInterrupt` surface multiple further unaddressed law failures
10
+ // See `genDie` scaladoc
11
11
def betterGenerators : Boolean = false
12
12
13
- // FIXME cats conversion surfaces failures in the following laws:
14
- // `async left is uncancelable sequenced raiseError`
15
- // `async right is uncancelable sequenced pure`
16
- // `applicativeError onError raise`
17
- // `canceled sequences onCanceled in order`
13
+ // FIXME cats conversion generator works most of the time
14
+ // but generates rare law failures in
15
+ // - `canceled sequences onCanceled in order`
16
+ // - `uncancelable eliminates onCancel`
17
+ // - `fiber join is guarantee case`
18
+ // possibly coming from the `GenSpawnGenerators#genRacePair` generator + `F.canceled`.
19
+ // Errors occur more often when combined with `genOfRace` or `genOfParallel`
18
20
def catsConversionGenerator : Boolean = false
19
21
20
22
/**
@@ -35,9 +37,28 @@ trait GenIOInteropCats {
35
37
36
38
def genFail [E : Arbitrary , A ]: Gen [IO [E , A ]] = Arbitrary .arbitrary[E ].map(IO .fail[E ](_))
37
39
40
+ /**
41
+ * We can't pass laws like `cats.effect.laws.GenSpawnLaws#fiberJoinIsGuaranteeCase`
42
+ * with either `genDie` or `genInternalInterrupt` because
43
+ * we are forced to rethrow an `Outcome.Errored` using
44
+ * `raiseError` in `Outcome#embed` which converts the
45
+ * specific state into a typed error.
46
+ *
47
+ * While we consider both states to be `Outcome.Errored`,
48
+ * they aren't really 'equivalent' even if we massage them
49
+ * into having the same `Outcome`, because `handleErrorWith`
50
+ * can't recover from these states.
51
+ *
52
+ * Now, we could make ZIO Throwable instances recover from
53
+ * all errors via [[zio.Cause#squashTraceWith ]], but
54
+ * this would make Throwable instances contradict the
55
+ * generic MonadError instance.
56
+ * (Which I believe is acceptable, if confusing, as long
57
+ * as the generic instances are moved to a separate `generic`
58
+ * object.)
59
+ */
38
60
def genDie (implicit arbThrowable : Arbitrary [Throwable ]): Gen [UIO [Nothing ]] = arbThrowable.arbitrary.map(IO .die(_))
39
-
40
- def genInternalInterrupt : Gen [UIO [Nothing ]] = ZIO .interrupt
61
+ def genInternalInterrupt : Gen [UIO [Nothing ]] = ZIO .interrupt
41
62
42
63
def genCancel [E , A : Arbitrary ](implicit F : GenConcurrent [IO [E , _], ? ]): Gen [IO [E , A ]] =
43
64
Arbitrary .arbitrary[A ].map(F .canceled.as(_))
@@ -60,18 +81,22 @@ trait GenIOInteropCats {
60
81
else
61
82
Gen .oneOf(
62
83
genSuccess[E , A ],
84
+ genFail[E , A ],
85
+ genCancel[E , A ],
63
86
genNever
64
87
)
65
88
66
- def genUIO [A : Arbitrary ]: Gen [UIO [A ]] =
89
+ def genUIO [A : Arbitrary ]( implicit F : GenConcurrent [ UIO , ? ]) : Gen [UIO [A ]] =
67
90
Gen .oneOf(genSuccess[Nothing , A ], genIdentityTrans(genSuccess[Nothing , A ]))
68
91
69
92
/**
70
93
* Given a generator for `IO[E, A]`, produces a sized generator for `IO[E, A]` which represents a transformation,
71
94
* by using some random combination of the methods `map`, `flatMap`, `mapError`, and any other method that does not change
72
95
* the success/failure of the value, but may change the value itself.
73
96
*/
74
- def genLikeTrans [E : Arbitrary : Cogen , A : Arbitrary : Cogen ](gen : Gen [IO [E , A ]]): Gen [IO [E , A ]] = {
97
+ def genLikeTrans [E : Arbitrary : Cogen , A : Arbitrary : Cogen ](
98
+ gen : Gen [IO [E , A ]]
99
+ )(implicit F : GenConcurrent [IO [E , _], ? ]): Gen [IO [E , A ]] = {
75
100
val functions : IO [E , A ] => Gen [IO [E , A ]] = io =>
76
101
Gen .oneOf(
77
102
genOfFlatMaps[E , A ](io)(genSuccess[E , A ]),
@@ -87,7 +112,8 @@ trait GenIOInteropCats {
87
112
* Given a generator for `IO[E, A]`, produces a sized generator for `IO[E, A]` which represents a transformation,
88
113
* by using methods that can have no effect on the resulting value (e.g. `map(identity)`, `io.race(never)`, `io.par(io2).map(_._1)`).
89
114
*/
90
- def genIdentityTrans [E , A : Arbitrary ](gen : Gen [IO [E , A ]]): Gen [IO [E , A ]] = {
115
+ def genIdentityTrans [E , A : Arbitrary ](gen : Gen [IO [E , A ]])(implicit F : GenConcurrent [IO [E , _], ? ]): Gen [IO [E , A ]] = {
116
+ implicitly[Arbitrary [A ]]
91
117
val functions : IO [E , A ] => Gen [IO [E , A ]] = io =>
92
118
Gen .oneOf(
93
119
genOfIdentityFlatMaps[E , A ](io),
@@ -131,9 +157,13 @@ trait GenIOInteropCats {
131
157
private def genOfIdentityFlatMaps [E , A ](io : IO [E , A ]): Gen [IO [E , A ]] =
132
158
Gen .const(io.flatMap(a => IO .succeed(a)))
133
159
134
- private def genOfRace [E , A ](io : IO [E , A ]): Gen [IO [E , A ]] =
135
- Gen .const(io.interruptible.raceFirst(ZIO .never.interruptible))
160
+ private def genOfRace [E , A ](io : IO [E , A ])(implicit F : GenConcurrent [IO [E , _], ? ]): Gen [IO [E , A ]] =
161
+ // Gen.const(io.interruptible.raceFirst(ZIO.never.interruptible))
162
+ Gen .const(F .race(io, ZIO .never).map(_.merge)) // we must use cats version for Outcome preservation in F.canceled
136
163
137
- private def genOfParallel [E , A ](io : IO [E , A ])(gen : Gen [IO [E , A ]]): Gen [IO [E , A ]] =
138
- gen.map(parIo => io.interruptible.zipPar(parIo.interruptible).map(_._1))
164
+ private def genOfParallel [E , A ](io : IO [E , A ])(
165
+ gen : Gen [IO [E , A ]]
166
+ )(implicit F : GenConcurrent [IO [E , _], ? ]): Gen [IO [E , A ]] =
167
+ // gen.map(parIo => io.interruptible.zipPar(parIo.interruptible).map(_._1))
168
+ gen.map(parIO => F .both(io, parIO).map(_._1)) // we must use cats version for Outcome preservation in F.canceled
139
169
}
0 commit comments