{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
module Spec (spec) where

import Conduit
import Prelude hiding (FilePath)
import Data.Maybe (listToMaybe)
import Data.Conduit.Combinators (slidingWindow, chunksOfE, chunksOfExactlyE)
import Data.List (intersperse, sort, find, mapAccumL)
import Safe (tailSafe)
import System.FilePath (takeExtension, (</>))
import Test.Hspec
import Test.Hspec.QuickCheck
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import Data.IORef
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Storable as VS
import Control.Monad (liftM)
import Control.Monad.ST (runST)
import Control.Monad.Trans.Writer
import qualified System.IO as IO
#if ! MIN_VERSION_base(4,8,0)
import Data.Monoid (Monoid (..))
import Control.Applicative ((<$>), (<*>))
#endif
#if MIN_VERSION_mono_traversable(1,0,0)
import Data.Sequences (LazySequence (..), Utf8 (..))
#else
import Data.Sequences.Lazy
import Data.Textual.Encoding
#endif
import qualified Data.NonNull as NN
import GHC.IO.Handle (hDuplicateTo)
import qualified Data.ByteString as S
import Data.ByteString.Builder (byteString, toLazyByteString)
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy.Char8 as L8
import qualified StreamSpec
import UnliftIO.Exception (pureTry)

spec :: Spec
spec = do
    describe "yieldMany" $ do
        it "list" $
            runConduitPure (yieldMany [1..10] .| sinkList)
            `shouldBe` [1..10]
        it "Text" $
            runConduitPure (yieldMany ("Hello World" :: T.Text) .| sinkList)
            `shouldBe` "Hello World"
    it "unfold" $
        let f 11 = Nothing
            f i = Just (show i, i + 1)
         in runConduitPure (unfoldC f 1 .| sinkList)
            `shouldBe` map show [1..10]
    it "enumFromTo" $
        runConduitPure (enumFromToC 1 10 .| sinkList) `shouldBe` [1..10]
    it "iterate" $
        let f i = i + 1
            src = iterateC f seed
            seed = 1
            count = 10
            res = runConduitPure $ src .| takeC count .| sinkList
         in res `shouldBe` take count (iterate f seed)
    it "repeat" $
        let src = repeatC seed
            seed = 1
            count = 10
            res = runConduitPure $ src .| takeC count .| sinkList
         in res `shouldBe` take count (repeat seed)
    it "replicate" $
        let src = replicateC count seed
            seed = 1
            count = 10
            res = runConduitPure $ src .| sinkList
         in res `shouldBe` replicate count seed
    it "sourceLazy" $
        let tss = ["foo", "bar", "baz"]
            tl = TL.fromChunks tss
            res = runConduitPure $ sourceLazy tl .| sinkList
         in res `shouldBe` tss
    it "repeatM" $
        let src = repeatMC (return seed)
            seed = 1
            count = 10
            res = runConduitPure $ src .| takeC count .| sinkList
         in res `shouldBe` take count (repeat seed)
    it "repeatWhileM" $ do
        ref <- newIORef 0
        let f = atomicModifyIORef ref $ \i -> (succ i, succ i)
            src = repeatWhileMC f (< 11)
        res <- runConduit $ src .| sinkList
        res `shouldBe` [1..10]
    it "replicateM" $ do
        ref <- newIORef 0
        let f = atomicModifyIORef ref $ \i -> (succ i, succ i)
            src = replicateMC 10 f
        res <- runConduit $ src .| sinkList
        res `shouldBe` [1..10]
    it "sourceFile" $ do
        let contents = concat $ replicate 10000 $ "this is some content\n"
            fp = "tmp"
        writeFile fp contents
        res <- runConduitRes $ sourceFile fp .| sinkLazy
        nocrBL res `shouldBe` TL.encodeUtf8 (TL.pack contents)
    it "sourceHandle" $ do
        let contents = concat $ replicate 10000 $ "this is some content\n"
            fp = "tmp"
        writeFile fp contents
        res <- IO.withBinaryFile "tmp" IO.ReadMode $ \h ->
          runConduit $ sourceHandle h .| sinkLazy
        nocrBL res `shouldBe` TL.encodeUtf8 (TL.pack contents)
    it "sourceIOHandle" $ do
        let contents = concat $ replicate 10000 $ "this is some content\n"
            fp = "tmp"
        writeFile fp contents
        let open = IO.openBinaryFile "tmp" IO.ReadMode
        res <- runConduitRes $ sourceIOHandle open .| sinkLazy
        nocrBL res `shouldBe` TL.encodeUtf8 (TL.pack contents)
    prop "stdin" $ \(S.pack -> content) -> do
        S.writeFile "tmp" content
        IO.withBinaryFile "tmp" IO.ReadMode $ \h -> do
            hDuplicateTo h IO.stdin
            x <- runConduit $ stdinC .| foldC
            x `shouldBe` content
    let hasExtension' ext fp = takeExtension fp == ext
    it "sourceDirectory" $ do
        res <- runConduitRes
             $ sourceDirectory "test" .| filterC (not . hasExtension' ".swp") .| sinkList
        sort res `shouldBe`
          [ "test" </> "Data"
          , "test" </> "Spec.hs"
          , "test" </> "StreamSpec.hs"
          , "test" </> "doctests.hs"
          , "test" </> "main.hs"
          , "test" </> "subdir"
          ]
    it "sourceDirectoryDeep" $ do
        res1 <- runConduitRes
              $ sourceDirectoryDeep False "test" .| filterC (not . hasExtension' ".swp") .| sinkList
        res2 <- runConduitRes
              $ sourceDirectoryDeep True "test" .| filterC (not . hasExtension' ".swp") .| sinkList
        sort res1 `shouldBe`
          [ "test" </> "Data" </> "Conduit" </> "Extra" </> "ZipConduitSpec.hs"
          , "test" </> "Data" </> "Conduit" </> "StreamSpec.hs"
          , "test" </> "Spec.hs"
          , "test" </> "StreamSpec.hs"
          , "test" </> "doctests.hs"
          , "test" </> "main.hs"
          , "test" </> "subdir" </> "dummyfile.txt"
          ]
        sort res1 `shouldBe` sort res2
    prop "drop" $ \(T.pack -> input) count ->
        runConduitPure (yieldMany input .| (dropC count >>= \() -> sinkList))
        `shouldBe` T.unpack (T.drop count input)
    prop "dropE" $ \(T.pack -> input) ->
        runConduitPure (yield input .| (dropCE 5 >>= \() -> foldC))
        `shouldBe` T.drop 5 input
    prop "dropWhile" $ \(T.pack -> input) sep ->
        runConduitPure (yieldMany input .| (dropWhileC (<= sep) >>= \() -> sinkList))
        `shouldBe` T.unpack (T.dropWhile (<= sep) input)
    prop "dropWhileE" $ \(T.pack -> input) sep ->
        runConduitPure (yield input .| (dropWhileCE (<= sep) >>= \() -> foldC))
        `shouldBe` T.dropWhile (<= sep) input
    it "fold" $
        let list = [[1..10], [11..20]]
            src = yieldMany list
            res = runConduitPure $ src .| foldC
         in res `shouldBe` concat list
    it "foldE" $
        let list = [[1..10], [11..20]]
            src = yieldMany $ Identity list
            res = runConduitPure $ src .| foldCE
         in res `shouldBe` concat list
    it "foldl" $
        let res = runConduitPure $ yieldMany [1..10] .| foldlC (+) 0
         in res `shouldBe` sum [1..10]
    it "foldlE" $
        let res = runConduitPure $ yield [1..10] .| foldlCE (+) 0
         in res `shouldBe` sum [1..10]
    it "foldMap" $
        let src = yieldMany [1..10]
            res = runConduitPure $ src .| foldMapC return
         in res `shouldBe` [1..10]
    it "foldMapE" $
        let src = yield [1..10]
            res = runConduitPure $ src .| foldMapCE return
         in res `shouldBe` [1..10]
    prop "all" $ \ (input :: [Int]) -> runConduitPure (yieldMany input .| allC even) `shouldBe` all evenInt input
    prop "allE" $ \ (input :: [Int]) -> runConduitPure (yield input .| allCE even) `shouldBe` all evenInt input
    prop "any" $ \ (input :: [Int]) -> runConduitPure (yieldMany input .| anyC even) `shouldBe` any evenInt input
    prop "anyE" $ \ (input :: [Int]) -> runConduitPure (yield input .| anyCE even) `shouldBe` any evenInt input
    prop "and" $ \ (input :: [Bool]) -> runConduitPure (yieldMany input .| andC) `shouldBe` and input
    prop "andE" $ \ (input :: [Bool]) -> runConduitPure (yield input .| andCE) `shouldBe` and input
    prop "or" $ \ (input :: [Bool]) -> runConduitPure (yieldMany input .| orC) `shouldBe` or input
    prop "orE" $ \ (input :: [Bool]) -> runConduitPure (yield input .| orCE) `shouldBe` or input
    prop "elem" $ \x xs -> runConduitPure (yieldMany xs .| elemC x) `shouldBe` elemInt x xs
    prop "elemE" $ \x xs -> runConduitPure (yield xs .| elemCE x) `shouldBe` elemInt x xs
    prop "notElem" $ \x xs -> runConduitPure (yieldMany xs .| notElemC x) `shouldBe` notElemInt x xs
    prop "notElemE" $ \x xs -> runConduitPure (yield xs .| notElemCE x) `shouldBe` notElemInt x xs
    prop "sinkVector regular" $ \xs -> do
        res <- runConduit $ yieldMany xs .| sinkVector
        res `shouldBe` V.fromList (xs :: [Int])
    prop "sinkVector unboxed" $ \xs -> do
        res <- runConduit $ yieldMany xs .| sinkVector
        res `shouldBe` VU.fromList (xs :: [Int])
    prop "sinkVector storable" $ \xs -> do
        res <- runConduit $ yieldMany xs .| sinkVector
        res `shouldBe` VS.fromList (xs :: [Int])
    prop "sinkVectorN regular" $ \xs' -> do
        let maxSize = 20
            xs = take maxSize xs'
        res <- runConduit $ yieldMany xs' .| sinkVectorN maxSize
        res `shouldBe` V.fromList (xs :: [Int])
    prop "sinkVectorN unboxed" $ \xs' -> do
        let maxSize = 20
            xs = take maxSize xs'
        res <- runConduit $ yieldMany xs' .| sinkVectorN maxSize
        res `shouldBe` VU.fromList (xs :: [Int])
    prop "sinkVectorN storable" $ \xs' -> do
        let maxSize = 20
            xs = take maxSize xs'
        res <- runConduit $ yieldMany xs' .| sinkVectorN maxSize
        res `shouldBe` VS.fromList (xs :: [Int])
    prop "sinkBuilder" $ \(map S.pack -> inputs) ->
        let builder = runConduitPure $ yieldMany inputs .| foldMapC byteString
            ltext = toLazyByteString builder
         in ltext `shouldBe` fromChunks inputs
    prop "sinkLazyBuilder" $ \(map S.pack -> inputs) ->
        let lbs = runConduitPure (yieldMany (map byteString inputs) .| sinkLazyBuilder)
         in lbs `shouldBe` fromChunks inputs
    prop "sinkNull" $ \xs toSkip -> do
        res <- runConduit $ yieldMany xs .| do
            takeC toSkip .| sinkNull
            sinkList
        res `shouldBe` drop toSkip (xs :: [Int])
    prop "awaitNonNull" $ \xs ->
        fmap NN.toNullable (runConduitPure $ yieldMany xs .| awaitNonNull)
        `shouldBe` listToMaybe (filter (not . null) (xs :: [[Int]]))
    prop "headE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| ((,) <$> headCE <*> foldC))
        `shouldBe` (listToMaybe $ concat xs, drop 1 $ concat xs)
    prop "peek" $ \xs ->
        runConduitPure (yieldMany xs .| ((,) <$> peekC <*> sinkList))
        `shouldBe` (listToMaybe xs, xs :: [Int])
    prop "peekE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| ((,) <$> peekCE <*> foldC))
        `shouldBe` (listToMaybe $ concat xs, concat xs)
    prop "last" $ \xs ->
        runConduitPure (yieldMany xs .| lastC)
        `shouldBe` listToMaybe (reverse (xs :: [Int]))
    prop "lastE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| lastCE)
        `shouldBe` listToMaybe (reverse (concat xs))
    prop "length" $ \xs ->
        runConduitPure (yieldMany xs .| lengthC)
        `shouldBe` length (xs :: [Int])
    prop "lengthE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| lengthCE)
        `shouldBe` length (concat xs)
    prop "lengthIf" $ \x xs ->
        runConduitPure (yieldMany xs .| lengthIfC (< x))
        `shouldBe` length (filter (< x) xs :: [Int])
    prop "lengthIfE" $ \x (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| lengthIfCE (< x))
        `shouldBe` length (filter (< x) (concat xs))
    prop "maximum" $ \xs ->
        runConduitPure (yieldMany xs .| maximumC)
        `shouldBe` (if null (xs :: [Int]) then Nothing else Just (maximum xs))
    prop "maximumE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| maximumCE)
        `shouldBe` (if null (concat xs) then Nothing else Just (maximum $ concat xs))
    prop "minimum" $ \xs ->
        runConduitPure (yieldMany xs .| minimumC)
        `shouldBe` (if null (xs :: [Int]) then Nothing else Just (minimum xs))
    prop "minimumE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| minimumCE)
        `shouldBe` (if null (concat xs) then Nothing else Just (minimum $ concat xs))
    prop "null" $ \xs ->
        runConduitPure (yieldMany xs .| nullC)
        `shouldBe` null (xs :: [Int])
    prop "nullE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| ((,) <$> nullCE <*> foldC))
        `shouldBe` (null (concat xs), concat xs)
    prop "sum" $ \xs ->
        runConduitPure (yieldMany xs .| sumC)
        `shouldBe` sum (xs :: [Int])
    prop "sumE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| sumCE)
        `shouldBe` sum (concat xs)
    prop "product" $ \xs ->
        runConduitPure (yieldMany xs .| productC)
        `shouldBe` product (xs :: [Int])
    prop "productE" $ \ (xs :: [[Int]]) ->
        runConduitPure (yieldMany xs .| productCE)
        `shouldBe` product (concat xs)
    prop "find" $ \x xs ->
        runConduitPure (yieldMany xs .| findC (< x))
        `shouldBe` find (< x) (xs :: [Int])
    prop "mapM_" $ \xs ->
        let res = execWriter $ runConduit $ yieldMany xs .| mapM_C (tell . return)
         in res `shouldBe` (xs :: [Int])
    prop "mapM_E" $ \xs ->
        let res = execWriter $ runConduit $ yield xs .| mapM_CE (tell . return)
         in res `shouldBe` (xs :: [Int])
    prop "foldM" $ \ (xs :: [Int]) -> do
        res <- runConduit $ yieldMany xs .| foldMC addM 0
        res `shouldBe` sum xs
    prop "foldME" $ \ (xs :: [Int]) -> do
        res <- runConduit $ yield xs .| foldMCE addM 0
        res `shouldBe` sum xs
    it "foldMapM" $
        let src = yieldMany [1..10]
            res = runConduitPure $ src .| foldMapMC (return . return)
         in res `shouldBe` [1..10]
    it "foldMapME" $
        let src = yield [1..10]
            res = runConduitPure $ src .| foldMapMCE (return . return)
         in res `shouldBe` [1..10]
    it "sinkFile" $ do
        let contents = mconcat $ replicate 1000 $ "this is some content\n"
            fp = "tmp"
        runConduitRes $ yield contents .| sinkFile fp
        res <- S.readFile fp
        res `shouldBe` contents
    it "sinkHandle" $ do
        let contents = mconcat $ replicate 1000 $ "this is some content\n"
            fp = "tmp"
        IO.withBinaryFile "tmp" IO.WriteMode $ \h -> runConduit $ yield contents .| sinkHandle h
        res <- S.readFile fp
        res `shouldBe` contents
    it "sinkIOHandle" $ do
        let contents = mconcat $ replicate 1000 $ "this is some content\n"
            fp = "tmp"
            open = IO.openBinaryFile "tmp" IO.WriteMode
        runConduitRes $ yield contents .| sinkIOHandle open
        res <- S.readFile fp
        res `shouldBe` contents
    prop "map" $ \input ->
        runConduitPure (yieldMany input .| mapC succChar .| sinkList)
        `shouldBe` map succChar input
    prop "mapE" $ \(map V.fromList -> inputs) ->
        runConduitPure (yieldMany inputs .| mapCE succChar .| foldC)
        `shouldBe` V.map succChar (V.concat inputs)
    prop "omapE" $ \(map T.pack -> inputs) ->
        runConduitPure (yieldMany inputs .| omapCE succChar .| foldC)
        `shouldBe` T.map succChar (T.concat inputs)
    prop "concatMap" $ \ (input :: [Int]) ->
        runConduitPure (yieldMany input .| concatMapC showInt .| sinkList)
        `shouldBe` concatMap showInt input
    prop "concatMapE" $ \ (input :: [Int]) ->
        runConduitPure (yield input .| concatMapCE showInt .| foldC)
        `shouldBe` concatMap showInt input
    prop "take" $ \(T.pack -> input) count ->
        runConduitPure (yieldMany input .| (takeC count >>= \() -> mempty) .| sinkList)
        `shouldBe` T.unpack (T.take count input)
    prop "takeE" $ \(T.pack -> input) count ->
        runConduitPure (yield input .| (takeCE count >>= \() -> mempty) .| foldC)
        `shouldBe` T.take count input
    prop "takeWhile" $ \(T.pack -> input) sep ->
        runConduitPure (yieldMany input .| do
            x <- (takeWhileC (<= sep) >>= \() -> mempty) .| sinkList
            y <- sinkList
            return (x, y))
        `shouldBe` span (<= sep) (T.unpack input)
    prop "takeWhileE" $ \(T.pack -> input) sep ->
        runConduitPure (yield input .| do
            x <- (takeWhileCE (<= sep) >>= \() -> mempty) .| foldC
            y <- foldC
            return (x, y))
        `shouldBe` T.span (<= sep) input
    it "takeExactly" $
        let src = yieldMany [1..10]
            sink = do
                x <- takeExactlyC 5 $ return 1
                y <- sinkList
                return (x, y)
            res = runConduitPure $ src .| sink
         in res `shouldBe` (1, [6..10])
    it "takeExactlyE" $
        let src = yield ("Hello World" :: T.Text)
            sink = do
                takeExactlyCE 5 (mempty :: ConduitT T.Text Void Identity ())
                y <- sinkLazy
                return y
            res = runConduitPure $ src .| sink
         in res `shouldBe` " World"
    it "takeExactlyE Vector" $ do
        let src = yield (V.fromList $ T.unpack "Hello World")
            sink = do
                x <- takeExactlyCE 5 $ return 1
                y <- foldC
                return (x, y)
        res <- runConduit $ src .| sink
        res `shouldBe` (1, V.fromList $ T.unpack " World")
    it "takeExactlyE 2" $
        let src = yield ("Hello World" :: T.Text)
            sink = do
                x <- takeExactlyCE 5 $ return 1
                y <- sinkLazy
                return (x, y)
            res = runConduitPure $ src .| sink
            -- FIXME type signature on next line is necessary in GHC 7.6.3 to
            -- avoid a crash:
            --
            -- test: internal error: ARR_WORDS object entered!
            --     (GHC version 7.6.3 for x86_64_unknown_linux)
            --     Please report this as a GHC bug:  http://www.haskell.org/ghc/reportabug
            -- Aborted (core dumped)
            --
            -- Report upstream when packages are released
         in res `shouldBe` (1, " World" :: TL.Text)
    prop "concat" $ \input ->
        runConduitPure (yield (T.pack input) .| concatC .| sinkList)
        `shouldBe` input
    prop "filter" $ \input ->
        runConduitPure (yieldMany input .| filterC evenInt .| sinkList)
        `shouldBe` filter evenInt input
    prop "filterE" $ \input ->
        runConduitPure (yield input .| filterCE evenInt .| foldC)
        `shouldBe` filter evenInt input
    prop "mapWhile" $ \input (min 20 -> highest) ->
        let f i | i < highest = Just (i + 2 :: Int)
                | otherwise   = Nothing
            res = runConduitPure $ yieldMany input .| do
                x <- (mapWhileC f >>= \() -> mempty) .| sinkList
                y <- sinkList
                return (x, y)
            (taken, dropped) = span (< highest) input
         in res `shouldBe` (map (+ 2) taken, dropped)
    prop "conduitVector" $ \(take 200 -> input) size' -> do
        let size = min 30 $ succ $ abs size'
        res <- runConduit $ yieldMany input .| conduitVector size .| sinkList
        res `shouldSatisfy` all (\v -> V.length v <= size)
        drop 1 (reverse res) `shouldSatisfy` all (\v -> V.length v == size)
        V.concat res `shouldBe` V.fromList (input :: [Int])
    prop "scanl" $ \input seed ->
        let f a b = a + b :: Int
            res = runConduitPure $ yieldMany input .| scanlC f seed .| sinkList
         in res `shouldBe` scanl f seed input
    prop "mapAccumWhile" $ \input (min 20 -> highest) ->
        let f i accum | i < highest = Right (i + accum, 2 * i :: Int)
                      | otherwise   = Left accum
            res = runConduitPure $ yieldMany input .| do
                (s, x) <- fuseBoth (mapAccumWhileC f 0) sinkList
                y <- sinkList
                return (s, x, y)
            (taken, dropped) = span (< highest) input
         in res `shouldBe` (sum taken, map (* 2) taken, tailSafe dropped)
    prop "concatMapAccum" $ \(input :: [Int]) ->
        let f a accum = (a + accum, [a, accum])
            res = runConduitPure $ yieldMany input .| concatMapAccumC f 0 .| sinkList
            expected = concat $ snd $ mapAccumL (flip f) 0 input
         in res `shouldBe` expected
    prop "intersperse" $ \xs x ->
        runConduitPure (yieldMany xs .| intersperseC x .| sinkList)
        `shouldBe` intersperse (x :: Int) xs
    prop "mapM" $ \input ->
        runConduitPure (yieldMany input .| mapMC (return . succChar) .| sinkList)
        `shouldBe` map succChar input
    prop "mapME" $ \(map V.fromList -> inputs) ->
        runConduitPure (yieldMany inputs .| mapMCE (return . succChar) .| foldC)
        `shouldBe` V.map succChar (V.concat inputs)
    prop "omapME" $ \(map T.pack -> inputs) ->
        runConduitPure (yieldMany inputs .| omapMCE (return . succChar) .| foldC)
        `shouldBe` T.map succChar (T.concat inputs)
    prop "concatMapM" $ \ (input :: [Int]) ->
        runConduitPure (yieldMany input .| concatMapMC (return . showInt) .| sinkList)
        `shouldBe` concatMap showInt input
    prop "filterM" $ \input ->
        runConduitPure (yieldMany input .| filterMC (return . evenInt) .| sinkList)
        `shouldBe` filter evenInt input
    prop "filterME" $ \input ->
        runConduitPure (yield input .| filterMCE (return . evenInt) .| foldC)
        `shouldBe` filter evenInt input
    prop "iterM" $ \input -> do
        (x, y) <- runWriterT $ runConduit $ yieldMany input .| iterMC (tell . return) .| sinkList
        x `shouldBe` (input :: [Int])
        y `shouldBe` input
    prop "scanlM" $ \input seed ->
        let f a b = a + b :: Int
            fm a b = return $ a + b
            res = runConduitPure $ yieldMany input .| scanlMC fm seed .| sinkList
         in res `shouldBe` scanl f seed input
    prop "mapAccumWhileM" $ \input (min 20 -> highest) ->
        let f i accum | i < highest = Right (i + accum, 2 * i :: Int)
                      | otherwise   = Left accum
            res = runConduitPure $ yieldMany input .| do
                (s, x) <- fuseBoth (mapAccumWhileMC ((return.).f) 0) sinkList
                y <- sinkList
                return (s, x, y)
            (taken, dropped) = span (< highest) input
         in res `shouldBe` (sum taken, map (* 2) taken, tailSafe dropped)
    prop "concatMapAccumM" $ \(input :: [Int]) ->
        let f a accum = (a + accum, [a, accum])
            res = runConduitPure $ yieldMany input .| concatMapAccumMC ((return.).f) 0 .| sinkList
            expected = concat $ snd $ mapAccumL (flip f) 0 input
         in res `shouldBe` expected
    prop "encode UTF8" $ \(map T.pack -> inputs) -> do
        let expected = encodeUtf8 $ fromChunks inputs
        actual <- runConduit
                $ yieldMany inputs
               .| encodeUtf8C
               .| sinkLazy
        actual `shouldBe` expected
    prop "encode/decode UTF8" $ \(map T.pack -> inputs) (min 50 . max 1 . abs -> chunkSize) -> do
        let expected = fromChunks inputs
        actual <- runConduit
                $ yieldMany inputs
               .| encodeUtf8C
               .| concatC
               .| conduitVector chunkSize
               .| mapC (S.pack . V.toList)
               .| decodeUtf8C
               .| sinkLazy
        actual `shouldBe` expected
    it "invalid UTF8 is an exception" $
      case runConduit $ yield "\129" .| decodeUtf8C .| sinkLazy of
        Left _ -> return () :: IO ()
        Right x -> error $ "this should have failed, got: " ++ show x
    prop "encode/decode UTF8 lenient" $ \(map T.pack -> inputs) (min 50 . max 1 . abs -> chunkSize) -> do
        let expected = fromChunks inputs
        actual <- runConduit
                $ yieldMany inputs
               .| encodeUtf8C
               .| concatC
               .| conduitVector chunkSize
               .| mapC (S.pack . V.toList)
               .| decodeUtf8LenientC
               .| sinkLazy
        actual `shouldBe` expected
    prop "line" $ \(map T.pack -> input) size ->
        let src = yieldMany input
            sink = do
                x <- lineC $ takeCE size .| foldC
                y <- foldC
                return (x, y)
            res = runConduitPure $ src .| sink
            expected =
                let (x, y) = T.break (== '\n') (T.concat input)
                 in (T.take size x, T.drop 1 y)
         in res `shouldBe` expected
    prop "lineAscii" $ \(map S.pack -> input) size ->
        let src = yieldMany input
            sink = do
                x <- lineAsciiC $ takeCE size .| foldC
                y <- foldC
                return (x, y)
            res = runConduitPure $ src .| sink
            expected =
                let (x, y) = S.break (== 10) (S.concat input)
                 in (S.take size x, S.drop 1 y)
         in res `shouldBe` expected
    prop "unlines" $ \(map T.pack -> input) ->
        runConduitPure (yieldMany input .| unlinesC .| foldC)
        `shouldBe` T.unlines input
    prop "unlinesAscii" $ \(map S.pack -> input) ->
        runConduitPure (yieldMany input .| unlinesAsciiC .| foldC)
        `shouldBe` S8.unlines input
    prop "linesUnbounded" $ \(map T.pack -> input) ->
        runConduitPure (yieldMany input .| (linesUnboundedC >>= \() -> mempty) .| sinkList)
        `shouldBe` T.lines (T.concat input)
    prop "linesUnboundedAscii" $ \(map S.pack -> input) ->
        runConduitPure (yieldMany input .| (linesUnboundedAsciiC >>= \() -> mempty) .| sinkList)
        `shouldBe` S8.lines (S.concat input)
    it "slidingWindow 0" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 0 .| sinkList
        in res `shouldBe` [[1],[2],[3],[4],[5]]
    it "slidingWindow 1" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 1 .| sinkList
        in res `shouldBe` [[1],[2],[3],[4],[5]]
    it "slidingWindow 2" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 2 .| sinkList
        in res `shouldBe` [[1,2],[2,3],[3,4],[4,5]]
    it "slidingWindow 3" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 3 .| sinkList
        in res `shouldBe` [[1,2,3],[2,3,4],[3,4,5]]
    it "slidingWindow 4" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 4 .| sinkList
        in res `shouldBe` [[1,2,3,4],[2,3,4,5]]
    it "slidingWindow 5" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 5 .| sinkList
        in res `shouldBe` [[1,2,3,4,5]]
    it "slidingWindow 6" $
        let res = runConduitPure $ yieldMany [1..5] .| slidingWindow 6 .| sinkList
        in res `shouldBe` [[1,2,3,4,5]]
    it "chunksOfE 1" $
        let res = runConduitPure $ yieldMany [[1,2], [3,4], [5,6]] .| chunksOfE 3 .| sinkList
        in res `shouldBe` [[1,2,3], [4,5,6]]
    it "chunksOfE 2 (last smaller)" $
        let res = runConduitPure $ yieldMany [[1,2], [3,4], [5,6,7]] .| chunksOfE 3 .| sinkList
        in res `shouldBe` [[1,2,3], [4,5,6], [7]]
    it "chunksOfE (ByteString)" $
        let res = runConduitPure $ yieldMany [S8.pack "01234", "56789ab", "cdef", "h"] .| chunksOfE 4 .| sinkList
        in res `shouldBe` ["0123", "4567", "89ab", "cdef", "h"]
    it "chunksOfExactlyE 1" $
        let res = runConduitPure $ yieldMany [[1,2], [3,4], [5,6]] .| chunksOfExactlyE 3 .| sinkList
        in res `shouldBe` [[1,2,3], [4,5,6]]
    it "chunksOfExactlyE 2 (last smaller; thus not yielded)" $
        let res = runConduitPure $ yieldMany [[1,2], [3,4], [5,6,7]] .| chunksOfExactlyE 3 .| sinkList
        in res `shouldBe` [[1,2,3], [4,5,6]]
    prop "vectorBuilder" $ \(values :: [[Int]]) ((+1) . (`mod` 30) . abs -> size) -> do
        let res = runST $ runConduit
                $ yieldMany values
               .| vectorBuilderC size mapM_CE
               .| sinkList
            expected =
                loop $ concat values
              where
                loop [] = []
                loop x =
                    VU.fromList y : loop z
                  where
                    (y, z) = splitAt size x
        res `shouldBe` expected
    prop "mapAccumS" $ \input ->
        let ints  = [1..]
            f a s = liftM (:s) $ mapC (* a) .| takeC a .| sinkList
            res   = reverse $ runConduitPure $ yieldMany input
                           .| mapAccumS f [] (yieldMany ints)
            expected = loop input ints
                where  loop []     _  = []
                       loop (a:as) xs = let (y, ys) = Prelude.splitAt a xs
                                        in  map (* a) y : loop as ys
        in  res `shouldBe` expected
    prop "peekForever" $ \(strs' :: [String]) -> do
        let strs = filter (not . null) strs'
        res1 <- runConduit $ yieldMany strs .| linesUnboundedC .| sinkList
        res2 <- runConduit $ yieldMany strs .| peekForever (lineC $ foldC >>= yield) .| sinkList
        res2 `shouldBe` res1
    prop "peekForeverE" $ \(strs :: [String]) -> do
        res1 <- runConduit $ yieldMany strs .| linesUnboundedC .| sinkList
        res2 <- runConduit $ yieldMany strs .| peekForeverE (lineC $ foldC >>= yield) .| sinkList
        res2 `shouldBe` res1
    StreamSpec.spec

evenInt :: Int -> Bool
evenInt = even

elemInt :: Int -> [Int] -> Bool
elemInt = elem

notElemInt :: Int -> [Int] -> Bool
notElemInt = notElem

addM :: Monad m => Int -> Int -> m Int
addM x y = return (x + y)

succChar :: Char -> Char
succChar c =
  case pureTry (succ c) of
    Left _ -> 'X' -- QuickCheck may generate characters out of range
    Right x -> x

showInt :: Int -> String
showInt = Prelude.show

nocrBL :: L8.ByteString -> L8.ByteString
nocrBL = L8.filter (/= '\r')
