on 5cbcd5b help sign in
streamly/test/Prop.hs
1003 lines 43 kB history raw
1{-# LANGUAGE CPP #-}
3module Main (main) where
5import Control.Applicative (ZipList(..))
6import Control.Concurrent (MVar, takeMVar, putMVar, newEmptyMVar)
7import Control.Exception
8 (BlockedIndefinitelyOnMVar(..), catches,
9 BlockedIndefinitelyOnSTM(..), Handler(..))
10import Control.Monad (when, forM_, replicateM, replicateM_)
11import Control.Monad.IO.Class (MonadIO(..))
12import Data.Function ((&))
13import Data.IORef (readIORef, modifyIORef, newIORef)
14import Data.List
15 (sort, foldl', scanl', findIndices, findIndex, elemIndices,
16 elemIndex, find, intersperse, foldl1', (\\))
17import Data.Maybe (mapMaybe)
18import GHC.Word (Word8)
20import Test.Hspec.QuickCheck
21import Test.QuickCheck (counterexample, Property, withMaxSuccess)
22import Test.QuickCheck.Monadic (run, monadicIO, monitor, assert, PropertyM)
24import Test.Hspec as H
26import Streamly
27import Streamly.Prelude ((.:), nil)
28import qualified Streamly.Prelude as S
30-- Coverage build takes too long with default number of tests
31maxTestCount :: Int
32#ifdef DEVBUILD
33maxTestCount = 100
34#else
35maxTestCount = 10
36#endif
38singleton :: IsStream t => a -> t m a
39singleton a = a .: nil
41sortEq :: Ord a => [a] -> [a] -> Bool
42sortEq a b = sort a == sort b
44equals
45 :: (Show a, Monad m)
46 => (a -> a -> Bool) -> a -> a -> PropertyM m ()
47equals eq stream list = do
48 when (not $ stream `eq` list) $
49 monitor
50 (counterexample $
51 "stream " <> show stream
52 <> "\nlist " <> show list
53 )
54 assert (stream `eq` list)
56listEquals
57 :: (Show a, Eq a, MonadIO m)
58 => ([a] -> [a] -> Bool) -> [a] -> [a] -> PropertyM m ()
59listEquals eq stream list = do
60 when (not $ stream `eq` list) $ liftIO $ putStrLn $
61 "stream " <> show stream
62 <> "\nlist " <> show list
63 <> "\nstream \\\\ list " <> show (stream \\ list)
64 <> "\nlist \\\\ stream " <> show (list \\ stream)
65 when (not $ stream `eq` list) $
66 monitor
67 (counterexample $
68 "stream " <> show stream
69 <> "\nlist " <> show list
70 <> "\nstream \\\\ list " <> show (stream \\ list)
71 <> "\nlist \\\\ stream " <> show (list \\ stream)
72 )
73 assert (stream `eq` list)
75constructWithReplicateM
76 :: IsStream t
77 => (t IO Int -> SerialT IO Int)
78 -> Word8
79 -> Property
80constructWithReplicateM op len = withMaxSuccess maxTestCount $
81 monadicIO $ do
82 let x = return (1 :: Int)
83 stream <- run $ (S.toList . op) (S.replicateM (fromIntegral len) x)
84 list <- run $ replicateM (fromIntegral len) x
85 listEquals (==) stream list
87transformFromList
88 :: (Eq b, Show b) =>
89 ([a] -> t IO a)
90 -> ([b] -> [b] -> Bool)
91 -> ([a] -> [b])
92 -> (t IO a -> SerialT IO b)
93 -> [a]
94 -> Property
95transformFromList constr eq listOp op a =
96 monadicIO $ do
97 stream <- run ((S.toList . op) (constr a))
98 let list = listOp a
99 listEquals eq stream list
101mvarExcHandler :: String -> BlockedIndefinitelyOnMVar -> IO ()
102mvarExcHandler label BlockedIndefinitelyOnMVar =
103 error $ label <> " " <> "BlockedIndefinitelyOnMVar\n"
105stmExcHandler :: String -> BlockedIndefinitelyOnSTM -> IO ()
106stmExcHandler label BlockedIndefinitelyOnSTM =
107 error $ label <> " " <> "BlockedIndefinitelyOnSTM\n"
109dbgMVar :: String -> IO () -> IO ()
110dbgMVar label action =
111 action `catches` [ Handler (mvarExcHandler label)
112 , Handler (stmExcHandler label)
113 ]
115-- | first n actions takeMVar and the last action performs putMVar n times
116mvarSequenceOp :: MVar () -> Word8 -> Word8 -> IO Word8
117mvarSequenceOp mv n x = do
118 let msg = show x <> "/" <> show n
119 if x < n
120 then dbgMVar ("take mvarSequenceOp " <> msg) (takeMVar mv) >> return x
121 else dbgMVar ("put mvarSequenceOp" <> msg)
122 (replicateM_ (fromIntegral n) (putMVar mv ())) >> return x
124concurrentMapM
125 :: ([Word8] -> t IO Word8)
126 -> ([Word8] -> [Word8] -> Bool)
127 -> (Word8 -> MVar () -> t IO Word8 -> SerialT IO Word8)
128 -> Word8
129 -> Property
130concurrentMapM constr eq op n =
131 monadicIO $ do
132 let list = [0..n]
133 stream <- run $ do
134 mv <- newEmptyMVar :: IO (MVar ())
135 (S.toList . op n mv) (constr list)
136 listEquals eq stream list
138concurrentFromFoldable
139 :: IsStream t
140 => ([Word8] -> [Word8] -> Bool)
141 -> (t IO Word8 -> SerialT IO Word8)
142 -> Word8
143 -> Property
144concurrentFromFoldable eq op n =
145 monadicIO $ do
146 let list = [0..n]
147 stream <- run $ do
148 mv <- newEmptyMVar :: IO (MVar ())
149 (S.toList . op) (S.fromFoldableM (fmap (mvarSequenceOp mv n) list))
150 listEquals eq stream list
152sourceUnfoldrM :: IsStream t => MVar () -> Word8 -> t IO Word8
153sourceUnfoldrM mv n = S.unfoldrM step 0
154 where
155 -- argument must be integer to avoid overflow of word8 at 255
156 step :: Int -> IO (Maybe (Word8, Int))
157 step cnt = do
158 let msg = show cnt <> "/" <> show n
159 if cnt > fromIntegral n
160 then return Nothing
161 else do
162 dbgMVar ("put sourceUnfoldrM " <> msg) (putMVar mv ())
163 return (Just (fromIntegral cnt, cnt + 1))
165-- Note that this test is not guaranteed to succeed, because there is no
166-- guarantee of parallelism in case of Async/Ahead streams.
167concurrentUnfoldrM
168 :: IsStream t
169 => ([Word8] -> [Word8] -> Bool)
170 -> (t IO Word8 -> SerialT IO Word8)
171 -> Word8
172 -> Property
173concurrentUnfoldrM eq op n =
174 monadicIO $ do
175 -- XXX we should test empty list case as well
176 let list = [0..n]
177 stream <- run $ do
178 -- putStrLn $ "concurrentUnfoldrM: " <> show n
179 mv <- newEmptyMVar :: IO (MVar ())
180 cnt <- newIORef 0
181 -- since unfoldr happens in parallel with the stream processing we
182 -- can do two takeMVar in one iteration. If it is not parallel then
183 -- this will not work and the test will fail.
184 S.toList $ do
185 x <- op (sourceUnfoldrM mv n)
186 -- results may not be yielded in order, in case of
187 -- Async/WAsync/Parallel. So we use an increasing count
188 -- instead.
189 i <- S.yieldM $ readIORef cnt
190 S.yieldM $ modifyIORef cnt (+1)
191 let msg = show i <> "/" <> show n
192 S.yieldM $
193 when (even i) $ do
194 dbgMVar ("first take concurrentUnfoldrM " <> msg)
195 (takeMVar mv)
196 when (n > i) $
197 dbgMVar ("second take concurrentUnfoldrM " <> msg)
198 (takeMVar mv)
199 return x
200 listEquals eq stream list
202concurrentApplication :: IsStream t
203 => ([Word8] -> [Word8] -> Bool)
204 -> (t IO Word8 -> SerialT IO Word8)
205 -> Word8
206 -> Property
207concurrentApplication eq t n = withMaxSuccess maxTestCount $
208 monadicIO $ do
209 -- XXX we should test empty list case as well
210 let list = [0..n]
211 stream <- run $ do
212 -- putStrLn $ "concurrentApplication: " <> show n
213 mv <- newEmptyMVar :: IO (MVar ())
214 -- since unfoldr happens in parallel with the stream processing we
215 -- can do two takeMVar in one iteration. If it is not parallel then
216 -- this will not work and the test will fail.
217 (S.toList . t) $
218 sourceUnfoldrM mv n |&
219 S.mapM (\x -> do
220 let msg = show x <> "/" <> show n
221 when (even x) $ do
222 dbgMVar ("first take concurrentApp " <> msg)
223 (takeMVar mv)
224 when (n > x) $
225 dbgMVar ("second take concurrentApp " <> msg)
226 (takeMVar mv)
227 return x)
228 listEquals eq stream list
230sourceUnfoldrM1 :: IsStream t => Word8 -> t IO Word8
231sourceUnfoldrM1 n = S.unfoldrM step 0
232 where
233 -- argument must be integer to avoid overflow of word8 at 255
234 step :: Int -> IO (Maybe (Word8, Int))
235 step cnt =
236 if cnt > fromIntegral n
237 then return Nothing
238 else return (Just (fromIntegral cnt, cnt + 1))
240concurrentFoldlApplication :: Word8 -> Property
241concurrentFoldlApplication n =
242 monadicIO $ do
243 -- XXX we should test empty list case as well
244 let list = [0..n]
245 stream <- run $
246 sourceUnfoldrM1 n |&. S.foldlM' (\xs x -> return (x : xs)) []
247 listEquals (==) (reverse stream) list
249concurrentFoldrApplication :: Word8 -> Property
250concurrentFoldrApplication n =
251 monadicIO $ do
252 -- XXX we should test empty list case as well
253 let list = [0..n]
254 stream <- run $
255 sourceUnfoldrM1 n |&. S.foldrM (\x xs -> return (x : xs)) []
256 listEquals (==) stream list
258transformCombineFromList
259 :: Semigroup (t IO Int)
260 => ([Int] -> t IO Int)
261 -> ([Int] -> [Int] -> Bool)
262 -> ([Int] -> [Int])
263 -> (t IO Int -> SerialT IO Int)
264 -> (t IO Int -> t IO Int)
265 -> [Int]
266 -> [Int]
267 -> [Int]
268 -> Property
269transformCombineFromList constr eq listOp t op a b c =
270 withMaxSuccess maxTestCount $
271 monadicIO $ do
272 stream <- run ((S.toList . t) $
273 constr a <> op (constr b <> constr c))
274 let list = a <> listOp (b <> c)
275 listEquals eq stream list
277foldFromList
278 :: ([Int] -> t IO Int)
279 -> (t IO Int -> SerialT IO Int)
280 -> ([Int] -> [Int] -> Bool)
281 -> [Int]
282 -> Property
283foldFromList constr op eq = transformFromList constr eq id op
285eliminateOp
286 :: (Show a, Eq a)
287 => ([s] -> t IO s)
288 -> ([s] -> a)
289 -> (t IO s -> IO a)
290 -> [s]
291 -> Property
292eliminateOp constr listOp op a =
293 monadicIO $ do
294 stream <- run $ op (constr a)
295 let list = listOp a
296 equals (==) stream list
298elemOp
299 :: ([Word8] -> t IO Word8)
300 -> (t IO Word8 -> SerialT IO Word8)
301 -> (Word8 -> SerialT IO Word8 -> IO Bool)
302 -> (Word8 -> [Word8] -> Bool)
303 -> (Word8, [Word8])
304 -> Property
305elemOp constr op streamOp listOp (x, xs) =
306 monadicIO $ do
307 stream <- run $ (streamOp x . op) (constr xs)
308 let list = listOp x xs
309 equals (==) stream list
311functorOps
312 :: Functor (t IO)
313 => ([Int] -> t IO Int)
314 -> String
315 -> ([Int] -> [Int] -> Bool)
316 -> (t IO Int -> SerialT IO Int)
317 -> Spec
318functorOps constr desc eq t = do
319 prop (desc <> " id") $ transformFromList constr eq id t
320 prop (desc <> " fmap (+1)") $ transformFromList constr eq (fmap (+1)) $ t . fmap (+1)
322transformOps
323 :: IsStream t
324 => ([Int] -> t IO Int)
325 -> String
326 -> ([Int] -> [Int] -> Bool)
327 -> (t IO Int -> SerialT IO Int)
328 -> Spec
329transformOps constr desc eq t = do
330 let transform = transformFromList constr eq
331 -- Filtering
332 prop (desc <> " filter False") $
333 transform (filter (const False)) $ t . S.filter (const False)
334 prop (desc <> " filter True") $
335 transform (filter (const True)) $ t . S.filter (const True)
336 prop (desc <> " filter even") $
337 transform (filter even) $ t . S.filter even
339 prop (desc <> " take maxBound") $
340 transform (take maxBound) $ t . S.take maxBound
341 prop (desc <> " take 0") $ transform (take 0) $ t . S.take 0
342 prop (desc <> " take 1") $ transform (take 1) $ t . S.take 1
343 prop (desc <> " take 10") $ transform (take 10) $ t . S.take 10
345 prop (desc <> " takeWhile True") $
346 transform (takeWhile (const True)) $ t . S.takeWhile (const True)
347 prop (desc <> " takeWhile False") $
348 transform (takeWhile (const False)) $ t . S.takeWhile (const False)
349 prop (desc <> " takeWhile > 0") $
350 transform (takeWhile (> 0)) $ t . S.takeWhile (> 0)
352 let f x = if odd x then Just (x + 100) else Nothing
353 prop (desc <> " mapMaybe") $ transform (mapMaybe f) $ t . S.mapMaybe f
355 prop (desc <> " drop maxBound") $
356 transform (drop maxBound) $ t . S.drop maxBound
357 prop (desc <> " drop 0") $ transform (drop 0) $ t . S.drop 0
358 prop (desc <> " drop 1") $ transform (drop 1) $ t . S.drop 1
359 prop (desc <> " drop 10") $ transform (drop 10) $ t . S.drop 10
361 prop (desc <> " dropWhile True") $
362 transform (dropWhile (const True)) $ t . S.dropWhile (const True)
363 prop (desc <> " dropWhile False") $
364 transform (dropWhile (const False)) $ t . S.dropWhile (const False)
365 prop (desc <> " dropWhile > 0") $
366 transform (dropWhile (> 0)) $ t . S.dropWhile (> 0)
367 prop (desc <> " scan") $ transform (scanl' (+) 0) $ t . S.scanl' (+) 0
368 prop (desc <> " reverse") $ transform reverse $ t . S.reverse
370 prop (desc <> " findIndices") $ transform (findIndices odd) $ t . S.findIndices odd
371 prop (desc <> " elemIndices") $ transform (elemIndices 3) $ t . S.elemIndices 3
373 prop (desc <> " intersperseM") $ transform (intersperse 3) $ t . S.intersperseM (return 3)
376concurrentOps
377 :: IsStream t
378 => ([Word8] -> t IO Word8)
379 -> String
380 -> ([Word8] -> [Word8] -> Bool)
381 -> (t IO Word8 -> SerialT IO Word8)
382 -> Spec
383concurrentOps constr desc eq t = do
384 let prop1 d p = prop d $ withMaxSuccess maxTestCount p
386 prop1 (desc <> " fromFoldableM") $ concurrentFromFoldable eq t
387 prop1 (desc <> " unfoldrM") $ concurrentUnfoldrM eq t
388 -- we pass it the length of the stream n and an mvar mv.
389 -- The stream is [0..n]. The threads communicate in such a way that the
390 -- actions coming first in the stream are dependent on the last action. So
391 -- if the stream is not processed concurrently it will block forever.
392 -- Note that if the size of the stream is bigger than the thread limit
393 -- then it will block even if it is concurrent.
394 prop1 (desc <> " mapM") $
395 concurrentMapM constr eq $ \n mv stream ->
396 t $ S.mapM (mvarSequenceOp mv n) stream
398-- XXX add tests for MonadReader and MonadError etc. In case an SVar is
399-- accidentally passed through them.
400transformCombineOpsCommon
401 :: (IsStream t, Semigroup (t IO Int))
402 => ([Int] -> t IO Int)
403 -> String
404 -> ([Int] -> [Int] -> Bool)
405 -> (t IO Int -> SerialT IO Int)
406 -> Spec
407transformCombineOpsCommon constr desc eq t = do
408 let transform = transformCombineFromList constr eq
409 -- Filtering
410 prop (desc <> " filter False") $
411 transform (filter (const False)) t (S.filter (const False))
412 prop (desc <> " filter True") $
413 transform (filter (const True)) t (S.filter (const True))
414 prop (desc <> " filter even") $
415 transform (filter even) t (S.filter even)
417 prop (desc <> " filterM False") $
418 transform (filter (const False)) t (S.filterM (const $ return False))
419 prop (desc <> " filterM True") $
420 transform (filter (const True)) t (S.filterM (const $ return True))
421 prop (desc <> " filterM even") $
422 transform (filter even) t (S.filterM (return . even))
424 prop (desc <> " take maxBound") $
425 transform (take maxBound) t (S.take maxBound)
426 prop (desc <> " take 0") $ transform (take 0) t (S.take 0)
428 prop (desc <> " takeWhile True") $
429 transform (takeWhile (const True)) t (S.takeWhile (const True))
430 prop (desc <> " takeWhile False") $
431 transform (takeWhile (const False)) t (S.takeWhile (const False))
433 prop (desc <> " takeWhileM True") $
434 transform (takeWhile (const True)) t (S.takeWhileM (const $ return True))
435 prop (desc <> " takeWhileM False") $
436 transform (takeWhile (const False)) t (S.takeWhileM (const $ return False))
438 prop (desc <> " drop maxBound") $
439 transform (drop maxBound) t (S.drop maxBound)
440 prop (desc <> " drop 0") $ transform (drop 0) t (S.drop 0)
442 prop (desc <> " dropWhile True") $
443 transform (dropWhile (const True)) t (S.dropWhile (const True))
444 prop (desc <> " dropWhile False") $
445 transform (dropWhile (const False)) t (S.dropWhile (const False))
447 prop (desc <> " dropWhileM True") $
448 transform (dropWhile (const True)) t (S.dropWhileM (const $ return True))
449 prop (desc <> " dropWhileM False") $
450 transform (dropWhile (const False)) t (S.dropWhileM (const $ return False))
452 prop (desc <> " mapM (+1)") $
453 transform (fmap (+1)) t (S.mapM (\x -> return (x + 1)))
455 prop (desc <> " scan") $ transform (scanl' (flip const) 0) t
456 (S.scanl' (flip const) 0)
457 prop (desc <> " scanlM'") $ transform (scanl' (flip const) 0) t
458 (S.scanlM' (\_ a -> return a) 0)
459 prop (desc <> " reverse") $ transform reverse t S.reverse
461 prop (desc <> " intersperseM") $
462 transform (intersperse 3) t (S.intersperseM $ return 3)
464transformCombineOpsOrdered
465 :: (IsStream t, Semigroup (t IO Int))
466 => ([Int] -> t IO Int)
467 -> String
468 -> ([Int] -> [Int] -> Bool)
469 -> (t IO Int -> SerialT IO Int)
470 -> Spec
471transformCombineOpsOrdered constr desc eq t = do
472 let transform = transformCombineFromList constr eq
473 -- Filtering
474 prop (desc <> " take 1") $ transform (take 1) t (S.take 1)
475#ifdef DEVBUILD
476 prop (desc <> " take 2") $ transform (take 2) t (S.take 2)
477 prop (desc <> " take 3") $ transform (take 3) t (S.take 3)
478 prop (desc <> " take 4") $ transform (take 4) t (S.take 4)
479 prop (desc <> " take 5") $ transform (take 5) t (S.take 5)
480#endif
481 prop (desc <> " take 10") $ transform (take 10) t (S.take 10)
483 prop (desc <> " takeWhile > 0") $
484 transform (takeWhile (> 0)) t (S.takeWhile (> 0))
486 prop (desc <> " drop 1") $ transform (drop 1) t (S.drop 1)
487 prop (desc <> " drop 10") $ transform (drop 10) t (S.drop 10)
489 prop (desc <> " dropWhile > 0") $
490 transform (dropWhile (> 0)) t (S.dropWhile (> 0))
491 prop (desc <> " scan") $ transform (scanl' (+) 0) t (S.scanl' (+) 0)
493 -- XXX this does not fail when the SVar is shared, need to fix.
494 prop (desc <> " concurrent application") $
495 transform (& fmap (+1)) t (|& S.map (+1))
497 prop (desc <> " findIndices") $
498 transform (findIndices odd) t (S.findIndices odd)
499 prop (desc <> " elemIndices") $
500 transform (elemIndices 0) t (S.elemIndices 0)
502wrapMaybe :: ([a1] -> a2) -> [a1] -> Maybe a2
503wrapMaybe f x = if null x then Nothing else Just (f x)
505eliminationOps
506 :: ([Int] -> t IO Int)
507 -> String
508 -> (t IO Int -> SerialT IO Int)
509 -> Spec
510eliminationOps constr desc t = do
511 -- Elimination
512 prop (desc <> " null") $ eliminateOp constr null $ S.null . t
513 prop (desc <> " foldl'") $
514 eliminateOp constr (foldl' (+) 0) $ S.foldl' (+) 0 . t
515 prop (desc <> " foldl1'") $
516 eliminateOp constr (wrapMaybe $ foldl1' (+)) $ S.foldl1' (+) . t
517 prop (desc <> " foldr1") $
518 eliminateOp constr (wrapMaybe $ foldr1 (+)) $ S.foldr1 (+) . t
519 prop (desc <> " all") $ eliminateOp constr (all even) $ S.all even . t
520 prop (desc <> " any") $ eliminateOp constr (any even) $ S.any even . t
521 prop (desc <> " and") $ eliminateOp constr (and . fmap (> 0)) $
522 (S.and . S.map (> 0)) . t
523 prop (desc <> " or") $ eliminateOp constr (or . fmap (> 0)) $
524 (S.or . S.map (> 0)) . t
525 prop (desc <> " length") $ eliminateOp constr length $ S.length . t
526 prop (desc <> " sum") $ eliminateOp constr sum $ S.sum . t
527 prop (desc <> " product") $ eliminateOp constr product $ S.product . t
529 prop (desc <> " maximum") $ eliminateOp constr (wrapMaybe maximum) $ S.maximum . t
530 prop (desc <> " minimum") $ eliminateOp constr (wrapMaybe minimum) $ S.minimum . t
532 prop (desc <> " findIndex") $ eliminateOp constr (findIndex odd) $ S.findIndex odd . t
533 prop (desc <> " elemIndex") $ eliminateOp constr (elemIndex 3) $ S.elemIndex 3 . t
535 prop (desc <> " find") $ eliminateOp constr (find even) $ S.find even . t
536 prop (desc <> " lookup") $
537 eliminateOp constr (lookup 3 . flip zip [1..]) $
538 S.lookup 3 . S.zipWith (\a b -> (b, a)) (S.fromList [(1::Int)..]) . t
540-- head/tail/last may depend on the order in case of parallel streams
541-- so we test these only for serial streams.
542serialEliminationOps
543 :: ([Int] -> t IO Int)
544 -> String
545 -> (t IO Int -> SerialT IO Int)
546 -> Spec
547serialEliminationOps constr desc t = do
548 prop (desc <> " head") $ eliminateOp constr (wrapMaybe head) $ S.head . t
549 prop (desc <> " tail") $ eliminateOp constr (wrapMaybe tail) $ \x -> do
550 r <- S.tail (t x)
551 case r of
552 Nothing -> return Nothing
553 Just s -> Just <$> S.toList s
554 prop (desc <> " last") $ eliminateOp constr (wrapMaybe last) $ S.last . t
555 prop (desc <> " init") $ eliminateOp constr (wrapMaybe init) $ \x -> do
556 r <- S.init (t x)
557 case r of
558 Nothing -> return Nothing
559 Just s -> Just <$> S.toList s
561transformOpsWord8
562 :: ([Word8] -> t IO Word8)
563 -> String
564 -> (t IO Word8 -> SerialT IO Word8)
565 -> Spec
566transformOpsWord8 constr desc t = do
567 prop (desc <> " elem") $ elemOp constr t S.elem elem
568 prop (desc <> " elem") $ elemOp constr t S.notElem notElem
570-- XXX concatenate streams of multiple elements rather than single elements
571semigroupOps
572 :: (IsStream t
574#if __GLASGOW_HASKELL__ < 804
575 , Semigroup (t IO Int)
576#endif
577 , Monoid (t IO Int))
578 => String
579 -> ([Int] -> [Int] -> Bool)
580 -> (t IO Int -> SerialT IO Int)
581 -> Spec
582semigroupOps desc eq t = do
583 prop (desc <> " <>") $ foldFromList (foldMapWith (<>) singleton) t eq
584 prop (desc <> " mappend") $ foldFromList (foldMapWith mappend singleton) t eq
586applicativeOps
587 :: Applicative (t IO)
588 => ([Int] -> t IO Int)
589 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
590 -> (t IO (Int, Int) -> SerialT IO (Int, Int))
591 -> ([Int], [Int])
592 -> Property
593applicativeOps constr eq t (a, b) = withMaxSuccess maxTestCount $
594 monadicIO $ do
595 stream <- run ((S.toList . t) ((,) <$> constr a <*> constr b))
596 let list = (,) <$> a <*> b
597 listEquals eq stream list
599zipApplicative
600 :: (IsStream t, Applicative (t IO))
601 => ([Int] -> t IO Int)
602 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
603 -> (t IO (Int, Int) -> SerialT IO (Int, Int))
604 -> ([Int], [Int])
605 -> Property
606zipApplicative constr eq t (a, b) = withMaxSuccess maxTestCount $
607 monadicIO $ do
608 stream1 <- run ((S.toList . t) ((,) <$> constr a <*> constr b))
609 stream2 <- run ((S.toList . t) (pure (,) <*> constr a <*> constr b))
610 stream3 <- run ((S.toList . t) (S.zipWith (,) (constr a) (constr b)))
611 let list = getZipList $ (,) <$> ZipList a <*> ZipList b
612 listEquals eq stream1 list
613 listEquals eq stream2 list
614 listEquals eq stream3 list
616zipMonadic
617 :: IsStream t
618 => ([Int] -> t IO Int)
619 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
620 -> (t IO (Int, Int) -> SerialT IO (Int, Int))
621 -> ([Int], [Int])
622 -> Property
623zipMonadic constr eq t (a, b) = withMaxSuccess maxTestCount $
624 monadicIO $ do
625 stream1 <-
626 run
627 ((S.toList . t)
628 (S.zipWithM (curry return) (constr a) (constr b)))
629 let list = getZipList $ (,) <$> ZipList a <*> ZipList b
630 listEquals eq stream1 list
632zipAsyncMonadic
633 :: IsStream t
634 => ([Int] -> t IO Int)
635 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
636 -> (t IO (Int, Int) -> SerialT IO (Int, Int))
637 -> ([Int], [Int])
638 -> Property
639zipAsyncMonadic constr eq t (a, b) = withMaxSuccess maxTestCount $
640 monadicIO $ do
641 stream1 <-
642 run
643 ((S.toList . t)
644 (S.zipWithM (curry return) (constr a) (constr b)))
645 stream2 <-
646 run
647 ((S.toList . t)
648 (S.zipAsyncWithM (curry return) (constr a) (constr b)))
649 let list = getZipList $ (,) <$> ZipList a <*> ZipList b
650 listEquals eq stream1 list
651 listEquals eq stream2 list
653monadThen
654 :: Monad (t IO)
655 => ([Int] -> t IO Int)
656 -> ([Int] -> [Int] -> Bool)
657 -> (t IO Int -> SerialT IO Int)
658 -> ([Int], [Int])
659 -> Property
660monadThen constr eq t (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
661 stream <- run ((S.toList . t) (constr a >> constr b))
662 let list = a >> b
663 listEquals eq stream list
665monadBind
666 :: Monad (t IO)
667 => ([Int] -> t IO Int)
668 -> ([Int] -> [Int] -> Bool)
669 -> (t IO Int -> SerialT IO Int)
670 -> ([Int], [Int])
671 -> Property
672monadBind constr eq t (a, b) = withMaxSuccess maxTestCount $
673 monadicIO $ do
674 stream <-
675 run
676 ((S.toList . t)
677 (constr a >>= \x -> (+ x) <$> constr b))
678 let list = a >>= \x -> (+ x) <$> b
679 listEquals eq stream list
681constructWithIterate :: IsStream t => (t IO Int -> SerialT IO Int) -> Spec
682constructWithIterate t = do
683 it "iterate" $
684 (S.toList . t . S.take 100) (S.iterate (+ 1) (0 :: Int))
685 `shouldReturn` take 100 (iterate (+ 1) 0)
686 it "iterateM" $ do
687 let addM y = return (y + 1)
688 S.toList . t . S.take 100 $ S.iterateM addM (0 :: Int)
689 `shouldReturn` take 100 (iterate (+ 1) 0)
691main :: IO ()
692main = hspec
693 $ H.parallel
694#ifdef COVERAGE_BUILD
695 $ modifyMaxSuccess (const 10)
696#endif
697 $ do
698 let folded :: IsStream t => [a] -> t IO a
699 folded = serially . (\xs ->
700 case xs of
701 [x] -> return x -- singleton stream case
702 _ -> foldMapWith (<>) return xs
703 )
705 let makeOps t =
706 [ ("default", t)
707#ifndef COVERAGE_BUILD
708 , ("rate AvgRate 10000", t . avgRate 10000)
709 , ("rate Nothing", t . rate Nothing)
710 , ("maxBuffer 0", t . maxBuffer 0)
711 , ("maxBuffer 1", t . maxBuffer 1)
712 , ("maxThreads 0", t . maxThreads 0)
713 , ("maxThreads 1", t . maxThreads 1)
714 , ("maxThreads -1", t . maxThreads (-1))
715#endif
716 ]
718 let mapOps spec = mapM_ (\(desc, f) -> describe desc $ spec f)
719 let serialOps :: IsStream t => ((SerialT IO a -> t IO a) -> Spec) -> Spec
720 serialOps spec = mapOps spec $ makeOps serially
721#ifndef COVERAGE_BUILD
722 <> [("rate AvgRate 0.00000001", serially . avgRate 0.00000001)]
723 <> [("maxBuffer -1", serially . maxBuffer (-1))]
724#endif
725 let wSerialOps :: IsStream t => ((WSerialT IO a -> t IO a) -> Spec) -> Spec
726 wSerialOps spec = mapOps spec $ makeOps wSerially
727#ifndef COVERAGE_BUILD
728 <> [("rate AvgRate 0.00000001", wSerially . avgRate 0.00000001)]
729 <> [("maxBuffer (-1)", wSerially . maxBuffer (-1))]
730#endif
731 let asyncOps :: IsStream t => ((AsyncT IO a -> t IO a) -> Spec) -> Spec
732 asyncOps spec = mapOps spec $ makeOps asyncly
733#ifndef COVERAGE_BUILD
734 <> [("maxBuffer (-1)", asyncly . maxBuffer (-1))]
735#endif
736 let wAsyncOps :: IsStream t => ((WAsyncT IO a -> t IO a) -> Spec) -> Spec
737 wAsyncOps spec = mapOps spec $ makeOps wAsyncly
738#ifndef COVERAGE_BUILD
739 <> [("maxBuffer (-1)", wAsyncly . maxBuffer (-1))]
740#endif
741 let aheadOps :: IsStream t => ((AheadT IO a -> t IO a) -> Spec) -> Spec
742 aheadOps spec = mapOps spec $ makeOps aheadly
743#ifndef COVERAGE_BUILD
744 <> [("maxBuffer (-1)", aheadly . maxBuffer (-1))]
745#endif
746 let parallelOps :: IsStream t => ((ParallelT IO a -> t IO a) -> Spec) -> Spec
747 parallelOps spec = mapOps spec $ makeOps parallely
748#ifndef COVERAGE_BUILD
749 <> [("rate AvgRate 0.00000001", parallely . avgRate 0.00000001)]
750 <> [("maxBuffer (-1)", parallely . maxBuffer (-1))]
751#endif
752 let zipSerialOps :: IsStream t => ((ZipSerialM IO a -> t IO a) -> Spec) -> Spec
753 zipSerialOps spec = mapOps spec $ makeOps zipSerially
754#ifndef COVERAGE_BUILD
755 <> [("rate AvgRate 0.00000001", zipSerially . avgRate 0.00000001)]
756 <> [("maxBuffer (-1)", zipSerially . maxBuffer (-1))]
757#endif
758 -- Note, the "pure" of applicative Zip streams generates and infinite
759 -- stream and therefore maxBuffer (-1) must not be used for that case.
760 let zipAsyncOps :: IsStream t => ((ZipAsyncM IO a -> t IO a) -> Spec) -> Spec
761 zipAsyncOps spec = mapOps spec $ makeOps zipAsyncly
763 describe "Construction" $ do
764 serialOps $ prop "serially replicateM" . constructWithReplicateM
765 wSerialOps $ prop "wSerially replicateM" . constructWithReplicateM
766 aheadOps $ prop "aheadly replicateM" . constructWithReplicateM
767 asyncOps $ prop "asyncly replicateM" . constructWithReplicateM
768 wAsyncOps $ prop "wAsyncly replicateM" . constructWithReplicateM
769 parallelOps $ prop "parallely replicateM" . constructWithReplicateM
770 -- XXX test for all types of streams
771 constructWithIterate serially
773 describe "Functor operations" $ do
774 serialOps $ functorOps S.fromFoldable "serially" (==)
775 serialOps $ functorOps folded "serially folded" (==)
776 wSerialOps $ functorOps S.fromFoldable "wSerially" (==)
777 wSerialOps $ functorOps folded "wSerially folded" (==)
778 aheadOps $ functorOps S.fromFoldable "aheadly" (==)
779 aheadOps $ functorOps folded "aheadly folded" (==)
780 asyncOps $ functorOps S.fromFoldable "asyncly" sortEq
781 asyncOps $ functorOps folded "asyncly folded" sortEq
782 wAsyncOps $ functorOps S.fromFoldable "wAsyncly" sortEq
783 wAsyncOps $ functorOps folded "wAsyncly folded" sortEq
784 parallelOps $ functorOps S.fromFoldable "parallely" sortEq
785 parallelOps $ functorOps folded "parallely folded" sortEq
786 zipSerialOps $ functorOps S.fromFoldable "zipSerially" (==)
787 zipSerialOps $ functorOps folded "zipSerially folded" (==)
788 zipAsyncOps $ functorOps S.fromFoldable "zipAsyncly" (==)
789 zipAsyncOps $ functorOps folded "zipAsyncly folded" (==)
791 describe "Semigroup operations" $ do
792 serialOps $ semigroupOps "serially" (==)
793 wSerialOps $ semigroupOps "wSerially" (==)
794 aheadOps $ semigroupOps "aheadly" (==)
795 asyncOps $ semigroupOps "asyncly" sortEq
796 wAsyncOps $ semigroupOps "wAsyncly" sortEq
797 parallelOps $ semigroupOps "parallely" sortEq
798 zipSerialOps $ semigroupOps "zipSerially" (==)
799 zipAsyncOps $ semigroupOps "zipAsyncly" (==)
801 describe "Applicative operations" $ do
802 -- The tests using sorted equality are weaker tests
803 -- We need to have stronger unit tests for all those
804 -- XXX applicative with three arguments
805 serialOps $ prop "serially applicative" . applicativeOps S.fromFoldable (==)
806 serialOps $ prop "serially applicative folded" . applicativeOps folded (==)
807 wSerialOps $ prop "wSerially applicative" . applicativeOps S.fromFoldable sortEq
808 wSerialOps $ prop "wSerially applicative folded" . applicativeOps folded sortEq
809 aheadOps $ prop "aheadly applicative" . applicativeOps S.fromFoldable (==)
810 aheadOps $ prop "aheadly applicative folded" . applicativeOps folded (==)
811 asyncOps $ prop "asyncly applicative" . applicativeOps S.fromFoldable sortEq
812 asyncOps $ prop "asyncly applicative folded" . applicativeOps folded sortEq
813 wAsyncOps $ prop "wAsyncly applicative" . applicativeOps S.fromFoldable sortEq
814 wAsyncOps $ prop "wAsyncly applicative folded" . applicativeOps folded sortEq
815 parallelOps $ prop "parallely applicative folded" . applicativeOps folded sortEq
817 describe "Zip operations" $ do
818 zipSerialOps $ prop "zipSerially applicative" . zipApplicative S.fromFoldable (==)
819 zipSerialOps $ prop "zipSerially applicative folded" . zipApplicative folded (==)
820 zipAsyncOps $ prop "zipAsyncly applicative" . zipApplicative S.fromFoldable (==)
821 zipAsyncOps $ prop "zipAsyncly applicative folded" . zipApplicative folded (==)
823 -- We test only the serial zip with serial streams and the parallel
824 -- stream, because the rate setting in these streams can slow down
825 -- zipAsync.
826 serialOps $ prop "zip monadic serially" . zipMonadic S.fromFoldable (==)
827 serialOps $ prop "zip monadic serially folded" . zipMonadic folded (==)
828 wSerialOps $ prop "zip monadic wSerially" . zipMonadic S.fromFoldable (==)
829 wSerialOps $ prop "zip monadic wSerially folded" . zipMonadic folded (==)
830 aheadOps $ prop "zip monadic aheadly" . zipAsyncMonadic S.fromFoldable (==)
831 aheadOps $ prop "zip monadic aheadly folded" . zipAsyncMonadic folded (==)
832 asyncOps $ prop "zip monadic asyncly" . zipAsyncMonadic S.fromFoldable (==)
833 asyncOps $ prop "zip monadic asyncly folded" . zipAsyncMonadic folded (==)
834 wAsyncOps $ prop "zip monadic wAsyncly" . zipAsyncMonadic S.fromFoldable (==)
835 wAsyncOps $ prop "zip monadic wAsyncly folded" . zipAsyncMonadic folded (==)
836 parallelOps $ prop "zip monadic parallely" . zipMonadic S.fromFoldable (==)
837 parallelOps $ prop "zip monadic parallely folded" . zipMonadic folded (==)
839 describe "Monad operations" $ do
840 serialOps $ prop "serially monad then" . monadThen S.fromFoldable (==)
841 wSerialOps $ prop "wSerially monad then" . monadThen S.fromFoldable sortEq
842 aheadOps $ prop "aheadly monad then" . monadThen S.fromFoldable (==)
843 asyncOps $ prop "asyncly monad then" . monadThen S.fromFoldable sortEq
844 wAsyncOps $ prop "wAsyncly monad then" . monadThen S.fromFoldable sortEq
845 parallelOps $ prop "parallely monad then" . monadThen S.fromFoldable sortEq
847 serialOps $ prop "serially monad then folded" . monadThen folded (==)
848 wSerialOps $ prop "wSerially monad then folded" . monadThen folded sortEq
849 aheadOps $ prop "aheadly monad then folded" . monadThen folded (==)
850 asyncOps $ prop "asyncly monad then folded" . monadThen folded sortEq
851 wAsyncOps $ prop "wAsyncly monad then folded" . monadThen folded sortEq
852 parallelOps $ prop "parallely monad then folded" . monadThen folded sortEq
854 serialOps $ prop "serially monad bind" . monadBind S.fromFoldable (==)
855 wSerialOps $ prop "wSerially monad bind" . monadBind S.fromFoldable sortEq
856 aheadOps $ prop "aheadly monad bind" . monadBind S.fromFoldable (==)
857 asyncOps $ prop "asyncly monad bind" . monadBind S.fromFoldable sortEq
858 wAsyncOps $ prop "wAsyncly monad bind" . monadBind S.fromFoldable sortEq
859 parallelOps $ prop "parallely monad bind" . monadBind S.fromFoldable sortEq
861 serialOps $ prop "serially monad bind folded" . monadBind folded (==)
862 wSerialOps $ prop "wSerially monad bind folded" . monadBind folded sortEq
863 aheadOps $ prop "aheadly monad bind folded" . monadBind folded (==)
864 asyncOps $ prop "asyncly monad bind folded" . monadBind folded sortEq
865 wAsyncOps $ prop "wAsyncly monad bind folded" . monadBind folded sortEq
866 parallelOps $ prop "parallely monad bind folded" . monadBind folded sortEq
868 describe "Stream transform operations" $ do
869 serialOps $ transformOps S.fromFoldable "serially" (==)
870 wSerialOps $ transformOps S.fromFoldable "wSerially" (==)
871 aheadOps $ transformOps S.fromFoldable "aheadly" (==)
872 asyncOps $ transformOps S.fromFoldable "asyncly" sortEq
873 wAsyncOps $ transformOps S.fromFoldable "wAsyncly" sortEq
874 parallelOps $ transformOps S.fromFoldable "parallely" sortEq
875 zipSerialOps $ transformOps S.fromFoldable "zipSerially" (==)
876 zipAsyncOps $ transformOps S.fromFoldable "zipAsyncly" (==)
878 serialOps $ transformOps folded "serially folded" (==)
879 wSerialOps $ transformOps folded "wSerially folded" (==)
880 aheadOps $ transformOps folded "aheadly folded" (==)
881 asyncOps $ transformOps folded "asyncly folded" sortEq
882 wAsyncOps $ transformOps folded "wAsyncly folded" sortEq
883 parallelOps $ transformOps folded "parallely folded" sortEq
884 zipSerialOps $ transformOps folded "zipSerially folded" (==)
885 zipAsyncOps $ transformOps folded "zipAsyncly folded" (==)
887 serialOps $ transformOpsWord8 S.fromFoldable "serially"
888 wSerialOps $ transformOpsWord8 S.fromFoldable "wSerially"
889 aheadOps $ transformOpsWord8 S.fromFoldable "aheadly"
890 asyncOps $ transformOpsWord8 S.fromFoldable "asyncly"
891 wAsyncOps $ transformOpsWord8 S.fromFoldable "wAsyncly"
892 parallelOps $ transformOpsWord8 S.fromFoldable "parallely"
893 zipSerialOps $ transformOpsWord8 S.fromFoldable "zipSerially"
894 zipAsyncOps $ transformOpsWord8 S.fromFoldable "zipAsyncly"
896 serialOps $ transformOpsWord8 folded "serially folded"
897 wSerialOps $ transformOpsWord8 folded "wSerially folded"
898 aheadOps $ transformOpsWord8 folded "aheadly folded"
899 asyncOps $ transformOpsWord8 folded "asyncly folded"
900 wAsyncOps $ transformOpsWord8 folded "wAsyncly folded"
901 parallelOps $ transformOpsWord8 folded "parallely folded"
902 zipSerialOps $ transformOpsWord8 folded "zipSerially folded"
903 zipAsyncOps $ transformOpsWord8 folded "zipAsyncly folded"
905 -- These tests won't work with maxBuffer or maxThreads set to 1, so we
906 -- exclude those cases from these.
907 let mkOps t =
908 [ ("default", t)
909#ifndef COVERAGE_BUILD
910 , ("rate Nothing", t . rate Nothing)
911 , ("maxBuffer 0", t . maxBuffer 0)
912 , ("maxThreads 0", t . maxThreads 0)
913 , ("maxThreads 0", t . maxThreads (-1))
914#endif
915 ]
917 let forOps ops spec = forM_ ops (\(desc, f) -> describe desc $ spec f)
918 describe "Stream concurrent operations" $ do
919 forOps (mkOps aheadly) $ concurrentOps S.fromFoldable "aheadly" (==)
920 forOps (mkOps asyncly) $ concurrentOps S.fromFoldable "asyncly" sortEq
921 forOps (mkOps wAsyncly) $ concurrentOps S.fromFoldable "wAsyncly" sortEq
922 forOps (mkOps parallely) $ concurrentOps S.fromFoldable "parallely" sortEq
924 forOps (mkOps aheadly) $ concurrentOps folded "aheadly folded" (==)
925 forOps (mkOps asyncly) $ concurrentOps folded "asyncly folded" sortEq
926 forOps (mkOps wAsyncly) $ concurrentOps folded "wAsyncly folded" sortEq
927 forOps (mkOps parallely) $ concurrentOps folded "parallely folded" sortEq
929 describe "Concurrent application" $ do
930 serialOps $ prop "serial" . concurrentApplication (==)
931 asyncOps $ prop "async" . concurrentApplication sortEq
932 aheadOps $ prop "ahead" . concurrentApplication (==)
933 parallelOps $ prop "parallel" . concurrentApplication sortEq
935 prop "concurrent foldr application" $ withMaxSuccess maxTestCount
936 concurrentFoldrApplication
937 prop "concurrent foldl application" $ withMaxSuccess maxTestCount
938 concurrentFoldlApplication
940 -- These tests are specifically targeted towards detecting illegal sharing
941 -- of SVar across conurrent streams. All transform ops must be added here.
942 describe "Stream transform and combine operations" $ do
943 serialOps $ transformCombineOpsCommon S.fromFoldable "serially" (==)
944 wSerialOps $ transformCombineOpsCommon S.fromFoldable "wSerially" sortEq
945 aheadOps $ transformCombineOpsCommon S.fromFoldable "aheadly" (==)
946 asyncOps $ transformCombineOpsCommon S.fromFoldable "asyncly" sortEq
947 wAsyncOps $ transformCombineOpsCommon S.fromFoldable "wAsyncly" sortEq
948 parallelOps $ transformCombineOpsCommon S.fromFoldable "parallely" sortEq
949 zipSerialOps $ transformCombineOpsCommon S.fromFoldable "zipSerially" (==)
950 zipAsyncOps $ transformCombineOpsCommon S.fromFoldable "zipAsyncly" (==)
952 serialOps $ transformCombineOpsCommon folded "serially" (==)
953 wSerialOps $ transformCombineOpsCommon folded "wSerially" sortEq
954 aheadOps $ transformCombineOpsCommon folded "aheadly" (==)
955 asyncOps $ transformCombineOpsCommon folded "asyncly" sortEq
956 wAsyncOps $ transformCombineOpsCommon folded "wAsyncly" sortEq
957 parallelOps $ transformCombineOpsCommon folded "parallely" sortEq
958 zipSerialOps $ transformCombineOpsCommon folded "zipSerially" (==)
959 zipAsyncOps $ transformCombineOpsCommon folded "zipAsyncly" (==)
961 serialOps $ transformCombineOpsOrdered S.fromFoldable "serially" (==)
962 aheadOps $ transformCombineOpsOrdered S.fromFoldable "aheadly" (==)
963 zipSerialOps $ transformCombineOpsOrdered S.fromFoldable "zipSerially" (==)
964 zipAsyncOps $ transformCombineOpsOrdered S.fromFoldable "zipAsyncly" (==)
966 serialOps $ transformCombineOpsOrdered folded "serially" (==)
967 aheadOps $ transformCombineOpsOrdered folded "aheadly" (==)
968 zipSerialOps $ transformCombineOpsOrdered folded "zipSerially" (==)
969 zipAsyncOps $ transformCombineOpsOrdered folded "zipAsyncly" (==)
971 describe "Stream elimination operations" $ do
972 serialOps $ eliminationOps S.fromFoldable "serially"
973 wSerialOps $ eliminationOps S.fromFoldable "wSerially"
974 aheadOps $ eliminationOps S.fromFoldable "aheadly"
975 asyncOps $ eliminationOps S.fromFoldable "asyncly"
976 wAsyncOps $ eliminationOps S.fromFoldable "wAsyncly"
977 parallelOps $ eliminationOps S.fromFoldable "parallely"
978 zipSerialOps $ eliminationOps S.fromFoldable "zipSerially"
979 zipAsyncOps $ eliminationOps S.fromFoldable "zipAsyncly"
981 serialOps $ eliminationOps folded "serially folded"
982 wSerialOps $ eliminationOps folded "wSerially folded"
983 aheadOps $ eliminationOps folded "aheadly folded"
984 asyncOps $ eliminationOps folded "asyncly folded"
985 wAsyncOps $ eliminationOps folded "wAsyncly folded"
986 parallelOps $ eliminationOps folded "parallely folded"
987 zipSerialOps $ eliminationOps folded "zipSerially folded"
988 zipAsyncOps $ eliminationOps folded "zipAsyncly folded"
990 -- XXX Add a test where we chain all transformation APIs and make sure that
991 -- the state is being passed through all of them.
992 describe "Stream serial elimination operations" $ do
993 serialOps $ serialEliminationOps S.fromFoldable "serially"
994 wSerialOps $ serialEliminationOps S.fromFoldable "wSerially"
995 aheadOps $ serialEliminationOps S.fromFoldable "aheadly"
996 zipSerialOps $ serialEliminationOps S.fromFoldable "zipSerially"
997 zipAsyncOps $ serialEliminationOps S.fromFoldable "zipAsyncly"
999 serialOps $ serialEliminationOps folded "serially folded"
1000 wSerialOps $ serialEliminationOps folded "wSerially folded"
1001 aheadOps $ serialEliminationOps folded "aheadly folded"
1002 zipSerialOps $ serialEliminationOps folded "zipSerially folded"
1003 zipAsyncOps $ serialEliminationOps folded "zipAsyncly folded"