conduit-0.0.3: test/main.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
import Test.Hspec.Monadic
import Test.Hspec.HUnit ()
import Test.Hspec.QuickCheck (prop)
import Test.HUnit
import qualified Data.Conduit as C
import qualified Data.Conduit.List as CL
import qualified Data.Conduit.Lazy as CLazy
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.Text as CT
import Data.Conduit (runResourceT)
import qualified Data.List as DL
import Control.Monad.ST (runST)
import Data.Monoid
import qualified Data.ByteString as S
import qualified Data.IORef as I
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Lazy.Char8 ()
import Control.Monad.Trans.Writer (Writer)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import Control.Monad.Trans.Resource (runExceptionT_, withIO, resourceForkIO)
import Control.Concurrent (threadDelay, killThread)
import Control.Monad.IO.Class (liftIO)
import Control.Applicative (pure, (<$>), (<*>))
main :: IO ()
main = hspecX $ do
describe "data loss rules" $ do
it "consumes the source to quickly" $ do
x <- runResourceT $ CL.sourceList [1..10 :: Int] C.$$ do
strings <- CL.map show C.=$ CL.take 5
liftIO $ putStr $ unlines strings
CL.fold (+) 0
40 @?= x
it "correctly consumes a chunked resource" $ do
x <- runResourceT $ (CL.sourceList [1..5 :: Int] `mappend` CL.sourceList [6..10]) C.$$ do
strings <- CL.map show C.=$ CL.take 5
liftIO $ putStr $ unlines strings
CL.fold (+) 0
40 @?= x
describe "filter" $ do
it "even" $ do
x <- runResourceT $ CL.sourceList [1..10] C.$$ CL.filter even C.=$ CL.consume
x @?= filter even [1..10 :: Int]
describe "ResourceT" $ do
it "resourceForkIO" $ do
counter <- I.newIORef 0
let w = withIO
(I.atomicModifyIORef counter $ \i ->
(i + 1, ()))
(const $ I.atomicModifyIORef counter $ \i ->
(i - 1, ()))
runResourceT $ do
_ <- w
_ <- resourceForkIO $ return ()
_ <- resourceForkIO $ return ()
sequence_ $ replicate 1000 $ do
tid <- resourceForkIO $ return ()
liftIO $ killThread tid
_ <- resourceForkIO $ return ()
_ <- resourceForkIO $ return ()
return ()
-- give enough of a chance to the cleanup code to finish
threadDelay 1000
res <- I.readIORef counter
res @?= (0 :: Int)
describe "sum" $ do
it "works for 1..10" $ do
x <- runResourceT $ CL.sourceList [1..10] C.$$ CL.fold (+) (0 :: Int)
x @?= sum [1..10]
prop "is idempotent" $ \list ->
(runST $ runResourceT $ CL.sourceList list C.$$ CL.fold (+) (0 :: Int))
== sum list
describe "Monoid instance for Source" $ do
it "mappend" $ do
x <- runResourceT $ (CL.sourceList [1..5 :: Int] `mappend` CL.sourceList [6..10]) C.$$ CL.fold (+) 0
x @?= sum [1..10]
it "mconcat" $ do
x <- runResourceT $ mconcat
[ CL.sourceList [1..5 :: Int]
, CL.sourceList [6..10]
, CL.sourceList [11..20]
] C.$$ CL.fold (+) 0
x @?= sum [1..20]
describe "file access" $ do
it "read" $ do
bs <- S.readFile "conduit.cabal"
bss <- runResourceT $ CB.sourceFile "conduit.cabal" C.$$ CL.consume
bs @=? S.concat bss
it "read range" $ do
S.writeFile "tmp" "0123456789"
bss <- runResourceT $ CB.sourceFileRange "tmp" (Just 2) (Just 3) C.$$ CL.consume
S.concat bss @?= "234"
it "write" $ do
runResourceT $ CB.sourceFile "conduit.cabal" C.$$ CB.sinkFile "tmp"
bs1 <- S.readFile "conduit.cabal"
bs2 <- S.readFile "tmp"
bs1 @=? bs2
it "conduit" $ do
runResourceT $ CB.sourceFile "conduit.cabal"
C.$= CB.conduitFile "tmp"
C.$$ CB.sinkFile "tmp2"
bs1 <- S.readFile "conduit.cabal"
bs2 <- S.readFile "tmp"
bs3 <- S.readFile "tmp2"
bs1 @=? bs2
bs1 @=? bs3
describe "Monad instance for Sink" $ do
it "binding" $ do
x <- runResourceT $ CL.sourceList [1..10] C.$$ do
_ <- CL.take 5
CL.fold (+) (0 :: Int)
x @?= sum [6..10]
describe "Applicative instance for Sink" $ do
it "<$> and <*>" $ do
x <- runResourceT $ CL.sourceList [1..10] C.$$
(+) <$> pure 5 <*> CL.fold (+) (0 :: Int)
x @?= sum [1..10] + 5
describe "resumable sources" $ do
it "simple" $ do
(x, y, z) <- runResourceT $ do
bs <- C.bufferSource $ CL.sourceList [1..10 :: Int]
x <- bs C.$$ CL.take 5
y <- bs C.$$ CL.fold (+) 0
z <- bs C.$$ CL.consume
return (x, y, z)
x @?= [1..5] :: IO ()
y @?= sum [6..10]
z @?= []
describe "conduits" $ do
it "map, left" $ do
x <- runResourceT $
CL.sourceList [1..10]
C.$= CL.map (* 2)
C.$$ CL.fold (+) 0
x @?= 2 * sum [1..10 :: Int]
it "map, right" $ do
x <- runResourceT $
CL.sourceList [1..10]
C.$$ CL.map (* 2)
C.=$ CL.fold (+) 0
x @?= 2 * sum [1..10 :: Int]
it "groupBy" $ do
let input = [1::Int, 1, 2, 3, 3, 3, 4, 5, 5]
x <- runResourceT $ CL.sourceList input
C.$$ CL.groupBy (==)
C.=$ CL.consume
x @?= DL.groupBy (==) input
it "groupBy (nondup begin/end)" $ do
let input = [1::Int, 2, 3, 3, 3, 4, 5]
x <- runResourceT $ CL.sourceList input
C.$$ CL.groupBy (==)
C.=$ CL.consume
x @?= DL.groupBy (==) input
it "concatMap" $ do
let input = [1, 11, 21]
x <- runResourceT $ CL.sourceList input
C.$$ CL.concatMap (\i -> enumFromTo i (i + 9))
C.=$ CL.fold (+) (0 :: Int)
x @?= sum [1..30]
it "bind together" $ do
let conduit = CL.map (+ 5) C.=$= CL.map (* 2)
x <- runResourceT $ CL.sourceList [1..10] C.$= conduit C.$$ CL.fold (+) 0
x @?= sum (map (* 2) $ map (+ 5) [1..10 :: Int])
#if !FAST
describe "isolate" $ do
it "bound to resumable source" $ do
(x, y) <- runResourceT $ do
bsrc <- C.bufferSource $ CL.sourceList [1..10 :: Int]
x <- bsrc C.$= CL.isolate 5 C.$$ CL.consume
y <- bsrc C.$$ CL.consume
return (x, y)
x @?= [1..5]
y @?= [6..10]
it "bound to sink, non-resumable" $ do
(x, y) <- runResourceT $ do
CL.sourceList [1..10 :: Int] C.$$ do
x <- CL.isolate 5 C.=$ CL.consume
y <- CL.consume
return (x, y)
x @?= [1..5]
y @?= [6..10]
it "bound to sink, resumable" $ do
(x, y) <- runResourceT $ do
bsrc <- C.bufferSource $ CL.sourceList [1..10 :: Int]
x <- bsrc C.$$ CL.isolate 5 C.=$ CL.consume
y <- bsrc C.$$ CL.consume
return (x, y)
x @?= [1..5]
y @?= [6..10]
it "consumes all data" $ do
x <- runResourceT $ CL.sourceList [1..10 :: Int] C.$$ do
CL.isolate 5 C.=$ CL.sinkNull
CL.consume
x @?= [6..10]
describe "lazy" $ do
it' "works inside a ResourceT" $ runResourceT $ do
counter <- liftIO $ I.newIORef 0
let incr i = C.sourceIO
(liftIO $ I.newIORef $ C.Open (i :: Int))
(const $ return ())
(\istate -> do
res <- liftIO $ I.atomicModifyIORef istate
(\state -> (C.Closed, state))
case res of
C.Closed -> return ()
_ -> do
count <- liftIO $ I.atomicModifyIORef counter
(\j -> (j + 1, j + 1))
liftIO $ count @?= i
return res
)
nums <- CLazy.lazyConsume $ mconcat $ map incr [1..10]
liftIO $ nums @?= [1..10]
describe "sequenceSink" $ do
it "simple sink" $ do
let sink () = do
_ <- CL.drop 2
x <- CL.head
return $ C.Emit () $ maybe [] return x
let conduit = C.sequenceSink () sink
res <- runResourceT $ CL.sourceList [1..10 :: Int]
C.$= conduit
C.$$ CL.consume
res @?= [3, 6, 9]
it "finishes on new state" $ do
let sink () = do
x <- CL.head
return $ C.Emit () $ maybe [] return x
let conduit = C.sequenceSink () sink
res <- runResourceT $ CL.sourceList [1..10 :: Int]
C.$= conduit C.$$ CL.consume
res @?= [1..10]
it "switch to a conduit" $ do
let sink () = do
_ <- CL.drop 4
return $ C.StartConduit $ CL.filter even
let conduit = C.sequenceSink () sink
res <- runResourceT $ CL.sourceList [1..10 :: Int]
C.$= conduit
C.$$ CL.consume
res @?= [6, 8, 10]
#endif
describe "peek" $ do
it "works" $ do
(a, b) <- runResourceT $ CL.sourceList [1..10 :: Int] C.$$ do
a <- CL.peek
b <- CL.consume
return (a, b)
(a, b) @?= (Just 1, [1..10])
describe "text" $ do
let go enc tenc cenc = do
prop (enc ++ " single chunk") $ \chars -> runST $ runExceptionT_ $ runResourceT $ do
let tl = TL.pack chars
lbs = tenc tl
src = CL.sourceList $ L.toChunks lbs
ts <- src C.$= CT.decode cenc C.$$ CL.consume
return $ TL.fromChunks ts == tl
prop (enc ++ " many chunks") $ \chars -> runST $ runExceptionT_ $ runResourceT $ do
let tl = TL.pack chars
lbs = tenc tl
src = mconcat $ map (CL.sourceList . return . S.singleton) $ L.unpack lbs
ts <- src C.$= CT.decode cenc C.$$ CL.consume
return $ TL.fromChunks ts == tl
prop (enc ++ " encoding") $ \chars -> runST $ runExceptionT_ $ runResourceT $ do
let tss = map T.pack chars
lbs = tenc $ TL.fromChunks tss
src = mconcat $ map (CL.sourceList . return) tss
bss <- src C.$= CT.encode cenc C.$$ CL.consume
return $ L.fromChunks bss == lbs
go "utf8" TLE.encodeUtf8 CT.utf8
go "utf16_le" TLE.encodeUtf16LE CT.utf16_le
go "utf16_be" TLE.encodeUtf16BE CT.utf16_be
go "utf32_le" TLE.encodeUtf32LE CT.utf32_le
go "utf32_be" TLE.encodeUtf32BE CT.utf32_be
describe "binary isolate" $ do
it "works" $ do
bss <- runResourceT $ CL.sourceList (replicate 1000 "X")
C.$= CB.isolate 6
C.$$ CL.consume
S.concat bss @?= "XXXXXX"
describe "unbuffering" $ do
it "works" $ do
x <- runResourceT $ do
bsrc <- C.bufferSource $ CL.sourceList [1..10 :: Int]
bsrc C.$$ CL.drop 5
let src = C.unbufferSource bsrc
src C.$$ CL.fold (+) 0
x @?= sum [6..10]
describe "properly using binary file reading" $ do
it "sourceFile" $ do
x <- runResourceT $ CB.sourceFile "test/random" C.$$ CL.consume
lbs <- L.readFile "test/random"
L.fromChunks x @?= lbs
describe "binary head" $ do
let go lbs = do
x <- CB.head
case (x, L.uncons lbs) of
(Nothing, Nothing) -> return True
(Just y, Just (z, lbs'))
| y == z -> go lbs'
_ -> return False
prop "works" $ \bss' ->
let bss = map S.pack bss'
in runST $ runResourceT $
CL.sourceList bss C.$$ go (L.fromChunks bss)
describe "binary takeWhile" $ do
prop "works" $ \bss' ->
let bss = map S.pack bss'
in runST $ runResourceT $ do
bss2 <- CL.sourceList bss C.$$ CB.takeWhile (>= 5) C.=$ CL.consume
return $ L.fromChunks bss2 == L.takeWhile (>= 5) (L.fromChunks bss)
describe "binary dropWhile" $ do
prop "works" $ \bss' ->
let bss = map S.pack bss'
in runST $ runResourceT $ do
bss2 <- CL.sourceList bss C.$$ do
CB.dropWhile (< 5)
CL.consume
return $ L.fromChunks bss2 == L.dropWhile (< 5) (L.fromChunks bss)
describe "binary take" $ do
let go n l = CL.sourceList l C.$$ do
a <- CB.take n
b <- CL.consume
return (a, b)
-- Taking nothing should result in an empty Bytestring
it "nothing" $ do
(a, b) <- runResourceT $ go 0 ["abc", "defg"]
a @?= L.empty
L.fromChunks b @?= "abcdefg"
it "normal" $ do
(a, b) <- runResourceT $ go 4 ["abc", "defg"]
a @?= "abcd"
L.fromChunks b @?= "efg"
-- Taking exactly the data that is available should result in no
-- leftover.
it "all" $ do
(a, b) <- runResourceT $ go 7 ["abc", "defg"]
a @?= "abcdefg"
b @?= []
-- Take as much as possible.
it "more" $ do
(a, b) <- runResourceT $ go 10 ["abc", "defg"]
a @?= "abcdefg"
b @?= []
it' :: String -> IO () -> Writer [Spec] ()
it' = it