@@ -17,6 +17,9 @@ import Test.Hspec
1717import Streamly
1818import qualified Streamly.Prelude as A
1919
20+sortEq :: Ord a => [a] -> [a] -> Bool
21+sortEq a b = sort a == sort b
22+
2023equals
2124 :: (Show a, Monad m)
2225 => (a -> a -> Bool) -> a -> a -> PropertyM m ()
@@ -42,23 +45,25 @@ constructWithReplicateM op len =
4245transformFromList
4346 :: (Streaming t)
4447 => ([Int] -> t IO Int)
48+ -> ([Int] -> [Int] -> Bool)
4549 -> ([Int] -> [Int])
4650 -> (t IO Int -> t IO Int)
4751 -> [Int]
4852 -> Property
49−transformFromList constr listOp op a =
53+transformFromList constr eq listOp op a =
5054 monadicIO $ do
5155 stream <- run ((A.toList . op) (constr a))
5256 let list = listOp a
53− equals (==) stream list
57+ equals eq stream list
5458
5559foldFromList
5660 :: (Streaming t)
5761 => ([Int] -> t IO Int)
5862 -> (t IO Int -> t IO Int)
63+ -> ([Int] -> [Int] -> Bool)
5964 -> [Int]
6065 -> Property
61−foldFromList constr op a = transformFromList constr id op a
66+foldFromList constr op eq a = transformFromList constr eq id op a
6267
6368eliminateOp
6469 :: (Streaming t, Show a, Eq a)
@@ -87,57 +92,80 @@ elemOp op streamOp listOp (x, xs) =
8792
8893transformOp
8994 :: (Streaming t)
90− => ([Int] -> [Int]) -> (t IO Int -> t IO Int) -> [Int] -> Property
95+ => ([Int] -> [Int] -> Bool)
96+ -> ([Int] -> [Int])
97+ -> (t IO Int -> t IO Int)
98+ -> [Int]
99+ -> Property
91100transformOp = transformFromList (A.each)
92101
93102functorOps
94103 :: (Streaming t, Functor (t IO))
95− => String -> (t IO Int -> t IO Int) -> Spec
96−functorOps desc t = do
97− prop (desc ++ " id") $ transformOp id $ t
98− prop (desc ++ " fmap (+1)") $ transformOp (fmap (+1)) $ t . (fmap (+1))
104+ => String
105+ -> (t IO Int -> t IO Int)
106+ -> ([Int] -> [Int] -> Bool)
107+ -> Spec
108+functorOps desc t eq = do
109+ prop (desc ++ " id") $ transformOp eq id $ t
110+ prop (desc ++ " fmap (+1)") $ transformOp eq (fmap (+1)) $ t . (fmap (+1))
99111
100112transformOps
101113 :: Streaming t
102− => String -> (t IO Int -> t IO Int) -> Spec
103−transformOps desc t
104− = do
114+ => String
115+ -> (t IO Int -> t IO Int)
116+ -> ([Int] -> [Int] -> Bool)
117+ -> Spec
118+transformOps desc t eq = do
119+ let transform = transformOp eq
105120 -- Filtering
106121 prop (desc ++ " filter False") $
107− transformOp (filter (const False)) $ t . (A.filter (const False))
122+ transform (filter (const False)) $ t . (A.filter (const False))
108123 prop (desc ++ " filter True") $
109− transformOp (filter (const True)) $ t . (A.filter (const True))
124+ transform (filter (const True)) $ t . (A.filter (const True))
110125 prop (desc ++ " filter even") $
111− transformOp (filter even) $ t . (A.filter even)
126+ transform (filter even) $ t . (A.filter even)
112127
113128 prop (desc ++ " take maxBound") $
114− transformOp (take maxBound) $ t . (A.take maxBound)
115− prop (desc ++ " take 0") $ transformOp (take 0) $ t . (A.take 0)
116− prop (desc ++ " take 1") $ transformOp (take 1) $ t . (A.take 1)
117− prop (desc ++ " take 10") $ transformOp (take 10) $ t . (A.take 10)
129+ transform (take maxBound) $ t . (A.take maxBound)
130+ prop (desc ++ " take 0") $ transform (take 0) $ t . (A.take 0)
131+ prop (desc ++ " take 1") $ transform (take 1) $ t . (A.take 1)
132+ prop (desc ++ " take 10") $ transform (take 10) $ t . (A.take 10)
118133
119134 prop (desc ++ " takeWhile True") $
120− transformOp (takeWhile (const True)) $ t . (A.takeWhile (const True))
135+ transform (takeWhile (const True)) $ t . (A.takeWhile (const True))
121136 prop (desc ++ " takeWhile False") $
122− transformOp (takeWhile (const False)) $ t . (A.takeWhile (const False))
137+ transform (takeWhile (const False)) $ t . (A.takeWhile (const False))
123138 prop (desc ++ " takeWhile > 0") $
124− transformOp (takeWhile (> 0)) $ t . (A.takeWhile (> 0))
139+ transform (takeWhile (> 0)) $ t . (A.takeWhile (> 0))
125140
126141 prop (desc ++ " drop maxBound") $
127− transformOp (drop maxBound) $ t . (A.drop maxBound)
128− prop (desc ++ " drop 0") $ transformOp (drop 0) $ t . (A.drop 0)
129− prop (desc ++ " drop 1") $ transformOp (drop 1) $ t . (A.drop 1)
130− prop (desc ++ " drop 10") $ transformOp (drop 10) $ t . (A.drop 10)
142+ transform (drop maxBound) $ t . (A.drop maxBound)
143+ prop (desc ++ " drop 0") $ transform (drop 0) $ t . (A.drop 0)
144+ prop (desc ++ " drop 1") $ transform (drop 1) $ t . (A.drop 1)
145+ prop (desc ++ " drop 10") $ transform (drop 10) $ t . (A.drop 10)
131146
132147 prop (desc ++ " dropWhile True") $
133− transformOp (dropWhile (const True)) $ t . (A.dropWhile (const True))
148+ transform (dropWhile (const True)) $ t . (A.dropWhile (const True))
134149 prop (desc ++ " dropWhile False") $
135− transformOp (dropWhile (const False)) $ t . (A.dropWhile (const False))
150+ transform (dropWhile (const False)) $ t . (A.dropWhile (const False))
136151 prop (desc ++ " dropWhile > 0") $
137− transformOp (dropWhile (> 0)) $ t . (A.dropWhile (> 0))
138− prop (desc ++ " scan") $ transformOp (scanl (+) 0) $ t . (A.scan (+) 0 id)
139− prop (desc ++ "reverse") $ transformOp reverse $ t . A.reverse
140−
152+ transform (dropWhile (> 0)) $ t . (A.dropWhile (> 0))
153+ prop (desc ++ " scan") $ transform (scanl (+) 0) $ t . (A.scan (+) 0 id)
154+ prop (desc ++ "reverse") $ transform reverse $ t . A.reverse
155+
156+wrapMaybe :: Eq a1 => ([a1] -> a2) -> [a1] -> Maybe a2
157+wrapMaybe f =
158+ \x ->
159+ if x == []
160+ then Nothing
161+ else Just (f x)
162+
163+eliminationOps
164+ :: Streaming t
165+ => String
166+ -> (t IO Int -> t IO Int)
167+ -> Spec
168+eliminationOps desc t = do
141169 -- Elimination
142170 prop (desc ++ " null") $ eliminateOp null $ A.null . t
143171 prop (desc ++ " foldl") $
@@ -146,22 +174,26 @@ transformOps desc t
146174 prop (desc ++ " any") $ eliminateOp (any even) $ (A.any even) . t
147175 prop (desc ++ " length") $ eliminateOp length $ A.length . t
148176 prop (desc ++ " sum") $ eliminateOp sum $ A.sum . t
149− prop (desc ++ " sum") $ eliminateOp product $ A.product . t
150−
151− let wrap f =
152− \x ->
153− if x == []
154− then Nothing
155− else Just (f x)
156− prop (desc ++ " head") $ eliminateOp (wrap head) $ A.head . t
157− prop (desc ++ " tail") $ eliminateOp (wrap tail) $ \x -> do
177+ prop (desc ++ " product") $ eliminateOp product $ A.product . t
178+
179+ prop (desc ++ " maximum") $ eliminateOp (wrapMaybe maximum) $ A.maximum . t
180+ prop (desc ++ " minimum") $ eliminateOp (wrapMaybe minimum) $ A.minimum . t
181+
182+-- head/tail/last may depend on the order in case of parallel streams
183+-- so we test these only for serial streams.
184+serialEliminationOps
185+ :: Streaming t
186+ => String
187+ -> (t IO Int -> t IO Int)
188+ -> Spec
189+serialEliminationOps desc t = do
190+ prop (desc ++ " head") $ eliminateOp (wrapMaybe head) $ A.head . t
191+ prop (desc ++ " tail") $ eliminateOp (wrapMaybe tail) $ \x -> do
158192 r <- A.tail (t x)
159193 case r of
160194 Nothing -> return Nothing
161195 Just s -> A.toList s >>= return . Just
162− prop (desc ++ " last") $ eliminateOp (wrap last) $ A.last . t
163− prop (desc ++ " maximum") $ eliminateOp (wrap maximum) $ A.maximum . t
164− prop (desc ++ " minimum") $ eliminateOp (wrap minimum) $ A.minimum . t
196+ prop (desc ++ " last") $ eliminateOp (wrapMaybe last) $ A.last . t
165197
166198transformOpsWord8
167199 :: Streaming t
@@ -170,6 +202,7 @@ transformOpsWord8 desc t = do
170202 prop (desc ++ " elem") $ elemOp t A.elem elem
171203 prop (desc ++ " elem") $ elemOp t A.notElem notElem
172204
205+-- XXX concatenate streams of multiple elements rather than single elements
173206semigroupOps
174207 :: ( Streaming t, MonadPlus (t IO)
175208
@@ -179,13 +212,12 @@ semigroupOps
179212 , Monoid (t IO Int))
180213 => String -> (t IO Int -> t IO Int) -> Spec
181214semigroupOps desc t = do
182− prop (desc ++ " <>") $ foldFromList (foldMapWith (<>) return) t
183− prop (desc ++ " mappend") $ foldFromList (foldMapWith mappend return) t
184− prop (desc ++ " <=>") $ foldFromList (foldMapWith (<=>) return) t
185− -- XXX equality should be multiset equality
186− prop (desc ++ " <|>") $ foldFromList (foldMapWith (<|>) return) t
187− prop (desc ++ " mplus") $ foldFromList (foldMapWith mplus return) t
188− prop (desc ++ " <|") $ foldFromList (foldMapWith (<|) return) t
215+ prop (desc ++ " <>") $ foldFromList (foldMapWith (<>) return) t (==)
216+ prop (desc ++ " mappend") $ foldFromList (foldMapWith mappend return) t (==)
217+ prop (desc ++ " <=>") $ foldFromList (foldMapWith (<=>) return) t (==)
218+ prop (desc ++ " <|>") $ foldFromList (foldMapWith (<|>) return) t sortEq
219+ prop (desc ++ " mplus") $ foldFromList (foldMapWith mplus return) t sortEq
220+ prop (desc ++ " <|") $ foldFromList (foldMapWith (<|) return) t sortEq
189221
190222applicativeOps
191223 :: (Streaming t, Applicative (t IO))
@@ -257,6 +289,7 @@ monadBind t eq (a, b) =
257289main :: IO ()
258290main = hspec $ do
259291 describe "Construction" $ do
292+ -- XXX test for all types of streams
260293 prop "serially replicateM" $ constructWithReplicateM serially
261294 it "iterate" $
262295 (A.toList . serially . (A.take 100) $ (A.iterate (+ 1) (0 :: Int)))
@@ -268,18 +301,17 @@ main = hspec $ do
268301 `shouldReturn` (take 100 $ iterate (+ 1) 0)
269302
270303 describe "Functor operations" $ do
271− functorOps "serially" serially
272− functorOps "interleaving" interleaving
273− functorOps "zipping" zipping
274− functorOps "asyncly" asyncly
275− functorOps "parallely" parallely
276− functorOps "zippingAsync" zippingAsync
304+ functorOps "serially" serially (==)
305+ functorOps "interleaving" interleaving (==)
306+ functorOps "zipping" zipping (==)
307+ functorOps "asyncly" asyncly sortEq
308+ functorOps "parallely" parallely sortEq
309+ functorOps "zippingAsync" zippingAsync (==)
277310
278311 describe "Semigroup operations" $ do
279312 semigroupOps "serially" serially
280313 semigroupOps "interleaving" interleaving
281314
282− let sortEq a b = sort a == sort b
283315 describe "Applicative operations" $ do
284316 -- The tests using sorted equality are weaker tests
285317 -- We need to have stronger unit tests for all those
@@ -309,15 +341,31 @@ main = hspec $ do
309341 prop "asyncly monad bind" $ monadBind asyncly sortEq
310342 prop "parallely monad bind" $ monadBind parallely sortEq
311343
312− describe "Stream operations" $ do
313− transformOps "serially" serially
314− transformOps "interleaving" interleaving
315− transformOps "zipping" zipping
316− transformOps "asyncly" asyncly
317− transformOps "parallely" parallely
344+ describe "Stream transform operations" $ do
345+ transformOps "serially" serially (==)
346+ transformOps "interleaving" interleaving (==)
347+ transformOps "zipping" zipping (==)
348+ transformOps "zippingAsync" zippingAsync (==)
349+ transformOps "asyncly" asyncly sortEq
350+ transformOps "parallely" parallely sortEq
318351
319352 transformOpsWord8 "serially" serially
320353 transformOpsWord8 "interleaving" interleaving
321354 transformOpsWord8 "zipping" zipping
355+ transformOpsWord8 "zippingAsync" zippingAsync
322356 transformOpsWord8 "asyncly" asyncly
323357 transformOpsWord8 "parallely" parallely
358+
359+ describe "Stream elimination operations" $ do
360+ eliminationOps "serially" serially
361+ eliminationOps "interleaving" interleaving
362+ eliminationOps "zipping" zipping
363+ eliminationOps "zippingAsync" zippingAsync
364+ eliminationOps "asyncly" asyncly
365+ eliminationOps "parallely" parallely
366+
367+ describe "Stream elimination operations" $ do
368+ serialEliminationOps "serially" serially
369+ serialEliminationOps "interleaving" interleaving
370+ serialEliminationOps "zipping" zipping
371+ serialEliminationOps "zippingAsync" zippingAsync