{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -- Copyright (C) 2010 John Millikin -- -- See license.txt for details module Main (tests, main) where import Control.Concurrent import qualified Control.Exception as Exc import Control.Monad.IO.Class (liftIO) import Data.Bits ((.&.)) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as BL import Data.Char (chr) import Data.Functor.Identity (Identity, runIdentity) import qualified Data.List as L import qualified Data.List.Split as LS import Data.Monoid (mappend, mempty, mconcat) import Data.String (IsString, fromString) import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Text.Lazy as TL import Data.Word (Word8) import System.Timeout (timeout) import Test.Chell import Test.Chell.QuickCheck import Test.QuickCheck hiding ((.&.), property, within) import Test.QuickCheck.Poly (A, B, C) import Data.Enumerator (($$), (>>==)) import qualified Data.Enumerator as E import qualified Data.Enumerator.Binary as EB import qualified Data.Enumerator.Text as ET import qualified Data.Enumerator.List as EL tests :: [Suite] tests = [ suite_StreamInstances , suite_Text , suite_ListAnalogues , suite_Other ] main :: IO () main = Test.Chell.defaultMain tests -- Stream instances {{{ suite_StreamInstances :: Suite suite_StreamInstances = suite "stream-instances" [ suite_StreamMonoid , suite_StreamFunctor , suite_StreamMonad ] suite_StreamMonoid :: Suite suite_StreamMonoid = suite "monoid" props where props = [ property "law-1" prop_law1 , property "law-2" prop_law2 , property "law-3" prop_law3 , property "law-4" prop_law4 ] prop_law1 :: E.Stream A -> Bool prop_law1 x = mappend mempty x == x prop_law2 :: E.Stream A -> Bool prop_law2 x = mappend x mempty == x prop_law3 :: E.Stream A -> E.Stream A -> E.Stream A -> Bool prop_law3 x y z = mappend x (mappend y z) == mappend (mappend x y) z prop_law4 :: [E.Stream A] -> Bool prop_law4 xs = mconcat xs == foldr mappend mempty xs suite_StreamFunctor :: Suite suite_StreamFunctor = suite "functor" props where props = [ property "law-1" prop_law1 , property "law-2" prop_law2 ] prop_law1 :: E.Stream A -> Bool prop_law1 x = fmap id x == id x prop_law2 :: E.Stream A -> Blind (B -> C) -> Blind (A -> B) -> Bool prop_law2 x (Blind f) (Blind g) = fmap (f . g) x == (fmap f . fmap g) x suite_StreamMonad :: Suite suite_StreamMonad = suite "Monad Stream" props where props = [ property "law-1" prop_law1 , property "law-2" prop_law2 , property "law-3" prop_law3 ] prop_law1 :: A -> Blind (A -> E.Stream B) -> Bool prop_law1 a (Blind f) = (return a >>= f) == f a prop_law2 :: E.Stream A -> Bool prop_law2 m = (m >>= return) == m prop_law3 :: E.Stream A -> Blind (A -> E.Stream B) -> Blind (B -> E.Stream C) -> Bool prop_law3 m (Blind f) (Blind g) = ((m >>= f) >>= g) == (m >>= (\x -> f x >>= g)) -- }}} -- Generic properties {{{ test_Enumeratee :: T.Text -> E.Enumeratee A A Identity (Maybe A) -> Suite test_Enumeratee name enee = suite name props where props = [ property "incremental" prop_incremental , property "nest-errors" prop_nest_errors ] prop_incremental (Positive n) (NonEmpty xs) = let result = runIdentity (E.run_ iter) expected = (Just (head xs), tail xs) iter = E.enumList n xs $$ do a <- E.joinI (enee $$ EL.head) b <- EL.consume return (a, b) in result == expected prop_nest_errors (Positive n) (NonEmpty xs) = let result = runIdentity (E.run_ iter) iter = E.enumList n xs $$ do _ <- enee $$ E.throwError (Exc.ErrorCall "") EL.consume in result == xs -- }}} -- Text encoding / decoding {{{ suite_Text :: Suite suite_Text = suite "text" [ suite_Encoding , suite_Decoding ] suite_Encoding :: Suite suite_Encoding = suite "encoding" [ suite_Encode_ASCII , suite_Encode_ISO8859 ] suite_Encode_ASCII :: Suite suite_Encode_ASCII = suite "ascii" props where props = [ property "works" (forAll genASCII prop_works) , property "error" prop_error , property "lazy" prop_lazy ] encode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.encode ET.ascii $$ iter) prop_works bytes = result == map B.singleton words where Right result = encode EL.consume (map T.singleton chars) chars = B8.unpack bytes words = B.unpack bytes prop_error = isLeft (encode EL.consume input) where isLeft = either (const True) (const False) input = [T.pack "\x61\xFF"] prop_lazy = either (const False) (== expected) result where result = encode EL.head input input = [T.pack "\x61\xFF"] expected = Just (B.singleton 0x61) suite_Encode_ISO8859 :: Suite suite_Encode_ISO8859 = suite "iso-8859-1" props where props = [ property "works" (forAll genISO8859 prop_works) , property "error" prop_error , property "lazy" prop_lazy ] encode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.encode ET.iso8859_1 $$ iter) prop_works bytes = result == map B.singleton words where Right result = encode EL.consume (map T.singleton chars) chars = B8.unpack bytes words = B.unpack bytes prop_error = isLeft (encode EL.consume input) where isLeft = either (const True) (const False) input = [T.pack "\x61\xFF5E"] prop_lazy = either (const False) (== expected) result where result = encode EL.head input input = [T.pack "\x61\xFF5E"] expected = Just (B.singleton 0x61) suite_Decoding :: Suite suite_Decoding = suite "decoding" [ suite_Decode_ASCII , suite_Decode_UTF8 , suite_Decode_UTF16_BE , suite_Decode_UTF16_LE , suite_Decode_UTF32_BE , suite_Decode_UTF32_LE ] suite_Decode_ASCII :: Suite suite_Decode_ASCII = suite "ascii" props where props = [ property "works" (forAll genASCII prop_works) , property "error" prop_error , property "lazy" prop_lazy ] decode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.decode ET.ascii $$ iter) prop_works text = result == map T.singleton chars where Right result = decode EL.consume (map B.singleton bytes) bytes = B.unpack (TE.encodeUtf8 text) chars = T.unpack text prop_error = isLeft (decode EL.consume input) where isLeft = either (const True) (const False) input = [B.pack [0xFF]] prop_lazy = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x61, 0xFF]] expected = Just (T.pack "a") suite_Decode_UTF8 :: Suite suite_Decode_UTF8 = suite "utf-8" props where props = [ property "works" prop_works , property "error" prop_error , property "lazy" prop_lazy , property "incremental" prop_incremental ] decode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.decode ET.utf8 $$ iter) prop_works text = result == map T.singleton chars where Right result = decode EL.consume (map B.singleton bytes) bytes = B.unpack (TE.encodeUtf8 text) chars = T.unpack text prop_error = isLeft (decode EL.consume input) where isLeft = either (const True) (const False) input = [B.pack [0x61, 0x80]] prop_lazy = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x61, 0x80]] expected = Just (T.pack "a") prop_incremental = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x61, 0xC2, 0xC2]] expected = Just (T.pack "a") suite_Decode_UTF16_BE :: Suite suite_Decode_UTF16_BE = suite "utf-16-be" props where props = [ property "works" prop_works , property "lazy" prop_lazy , property "error" prop_error , property "incremental" prop_incremental ] decode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.decode ET.utf16_be $$ iter) prop_works text = result == map T.singleton chars where Right result = decode EL.consume (map B.singleton bytes) bytes = B.unpack (TE.encodeUtf16BE text) chars = T.unpack text prop_lazy = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x00, 0x61, 0xDD, 0x1E]] expected = Just (T.pack "a") prop_error = isLeft (decode EL.consume input) where isLeft = either (const True) (const False) input = [B.pack [0x00, 0x61, 0xDD, 0x1E]] prop_incremental = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x00, 0x61, 0xD8, 0x34, 0xD8, 0xD8]] expected = Just (T.pack "a") suite_Decode_UTF16_LE :: Suite suite_Decode_UTF16_LE = suite "utf-16-le" props where props = [ property "works" prop_works , property "lazy" prop_lazy , property "error" prop_error , property "incremental" prop_incremental ] decode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.decode ET.utf16_le $$ iter) prop_works text = result == map T.singleton chars where Right result = decode EL.consume (map B.singleton bytes) bytes = B.unpack (TE.encodeUtf16LE text) chars = T.unpack text prop_lazy = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x61, 0x00, 0x1E, 0xDD]] expected = Just (T.pack "a") prop_error = isLeft (decode EL.consume input) where isLeft = either (const True) (const False) input = [B.pack [0x61, 0x00, 0x1E, 0xDD]] prop_incremental = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x61, 0x00, 0x34, 0xD8, 0xD8, 0xD8]] expected = Just (T.pack "a") suite_Decode_UTF32_BE :: Suite suite_Decode_UTF32_BE = suite "utf-32-be" props where props = [ property "works" prop_works , property "lazy" prop_lazy , property "error" prop_error ] decode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.decode ET.utf32_be $$ iter) prop_works text = result == map T.singleton chars where Right result = decode EL.consume (map B.singleton bytes) bytes = B.unpack (TE.encodeUtf32BE text) chars = T.unpack text prop_lazy = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x00, 0x00, 0x00, 0x61, 0xFF, 0xFF]] expected = Just (T.pack "a") prop_error = isLeft (decode EL.consume input) where isLeft = either (const True) (const False) input = [B.pack [0xFF, 0xFF, 0xFF, 0xFF]] suite_Decode_UTF32_LE :: Suite suite_Decode_UTF32_LE = suite "utf-32-le" props where props = [ property "works" prop_works , property "lazy" prop_lazy , property "error" prop_error ] decode iter input = runIdentity . E.run $ E.enumList 1 input $$ E.joinI (ET.decode ET.utf32_le $$ iter) prop_works text = result == map T.singleton chars where Right result = decode EL.consume (map B.singleton bytes) bytes = B.unpack (TE.encodeUtf32LE text) chars = T.unpack text prop_lazy = either (const False) (== expected) result where result = decode EL.head input input = [B.pack [0x61, 0x00, 0x00, 0x00, 0xFF, 0xFF]] expected = Just (T.pack "a") prop_error = isLeft (decode EL.consume input) where isLeft = either (const True) (const False) input = [B.pack [0xFF, 0xFF, 0xFF, 0xFF]] -- }}} -- List analogues {{{ suite_ListAnalogues :: Suite suite_ListAnalogues = suite "list-analogues" [ suite_Consume , suite_Head , suite_Drop , suite_Take , suite_Require , suite_Isolate , suite_SplitWhen , suite_Map , suite_ConcatMap , suite_MapM , suite_ConcatMapM , suite_MapAccum , suite_MapAccumM , suite_Filter , suite_FilterM ] check :: Eq b => E.Iteratee a Identity b -> ([a] -> Either Exc.ErrorCall b) -> [a] -> Bool check iter plain xs = expected == run iter xs where expected = case plain xs of Left exc -> Left (Just exc) Right x -> Right x run iter xs = case runIdentity (E.run (E.enumList 1 xs $$ iter)) of Left exc -> Left (Exc.fromException exc) Right x -> Right x testListAnalogue name iterList plainList iterText plainText iterBytes plainBytes = suite name tests where tests = [ property "list" prop_List , property "text" prop_Text , property "bytes" prop_Bytes ] prop_List :: [A] -> Bool prop_List xs = check iterList plainList xs prop_Text xs = check iterText (plainText . TL.fromChunks) xs prop_Bytes xs = check iterBytes (plainBytes . BL.fromChunks) xs testListAnalogueN name iterList plainList iterText plainText iterBytes plainBytes = suite name tests where tests = [ property "list" prop_List , property "text" prop_Text , property "bytes" prop_Bytes ] prop_List :: Positive Integer -> [A] -> Bool prop_List (Positive n) xs = check (iterList n) (plainList n) xs prop_Text (Positive n) xs = check (iterText n) (plainText n . TL.fromChunks) xs prop_Bytes (Positive n) xs = check (iterBytes n) (plainBytes n . BL.fromChunks) xs testListAnalogueX name iterList plainList iterText plainText iterBytes plainBytes = suite name tests where tests = [ property "list" prop_List , property "text" prop_Text , property "bytes" prop_Bytes ] prop_List :: A -> [A] -> Bool prop_List x xs = check (iterList x) (plainList x) xs prop_Text x xs = check (iterText x) (plainText x . TL.fromChunks) xs prop_Bytes x xs = check (iterBytes x) (plainBytes x . BL.fromChunks) xs suite_Consume :: Suite suite_Consume = testListAnalogue "consume" EL.consume Right ET.consume Right EB.consume Right suite_Head :: Suite suite_Head = testListAnalogue "head" (do x <- EL.head extra <- EL.consume return (x, extra) ) (\xs -> Right $ case xs of [] -> (Nothing, []) (x:xs') -> (Just x, xs')) (do x <- ET.head extra <- ET.consume return (x, extra) ) (\text -> Right $ case TL.uncons text of Nothing -> (Nothing, TL.empty) Just (x, extra) -> (Just x, extra)) (do x <- EB.head extra <- EB.consume return (x, extra) ) (\bytes -> Right $ case BL.uncons bytes of Nothing -> (Nothing, BL.empty) Just (x, extra) -> (Just x, extra)) suite_Drop :: Suite suite_Drop = testListAnalogueN "drop" (\n -> EL.drop n >> EL.consume) (\n -> Right . L.genericDrop n) (\n -> ET.drop n >> ET.consume) (\n -> Right . TL.drop (fromInteger n)) (\n -> EB.drop n >> EB.consume) (\n -> Right . BL.drop (fromInteger n)) suite_Take :: Suite suite_Take = testListAnalogueN "take" (\n -> do xs <- EL.take n extra <- EL.consume return (xs, extra)) (\n -> Right . L.genericSplitAt n) (\n -> do xs <- ET.take n extra <- ET.consume return (xs, extra)) (\n -> Right . TL.splitAt (fromInteger n)) (\n -> do xs <- EB.take n extra <- EB.consume return (xs, extra)) (\n -> Right . BL.splitAt (fromInteger n)) suite_Require :: Suite suite_Require = testListAnalogueN "require" (\n -> do EL.require n EL.consume) (\n xs -> if n > toInteger (length xs) then Left (Exc.ErrorCall "require: Unexpected EOF") else Right xs) (\n -> do ET.require n ET.consume) (\n xs -> if n > toInteger (TL.length xs) then Left (Exc.ErrorCall "require: Unexpected EOF") else Right xs) (\n -> do EB.require n EB.consume) (\n xs -> if n > toInteger (BL.length xs) then Left (Exc.ErrorCall "require: Unexpected EOF") else Right xs) suite_Isolate :: Suite suite_Isolate = testListAnalogue "isolate" (do x <- E.joinI (EL.isolate 2 $$ EL.head) extra <- EL.consume return (x, extra)) (\xs -> Right $ case xs of [] -> (Nothing, []) (x:[]) -> (Just x, []) (x:_:xs') -> (Just x, xs')) (do x <- E.joinI (ET.isolate 2 $$ ET.head) extra <- ET.consume return (x, extra)) (\text -> Right $ case TL.unpack text of [] -> (Nothing, TL.empty) (x:[]) -> (Just x, TL.empty) (x:_:xs') -> (Just x, TL.pack xs')) (do x <- E.joinI (EB.isolate 2 $$ EB.head) extra <- EB.consume return (x, extra)) (\bytes -> Right $ case BL.unpack bytes of [] -> (Nothing, BL.empty) (x:[]) -> (Just x, BL.empty) (x:_:xs) -> (Just x, BL.pack xs)) suite_SplitWhen :: Suite suite_SplitWhen = testListAnalogueX "splitWhen" (\x -> do xs <- E.joinI (EL.splitWhen (== x) $$ EL.consume) extra <- EL.consume return (xs, extra)) (\x xs -> let split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt in Right (split (== x) xs, [])) (\c -> do xs <- E.joinI (ET.splitWhen (== c) $$ EL.consume) extra <- EL.consume return (xs, extra)) (\c text -> let split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt chars = TL.unpack text in Right (map T.pack (split (== c) chars), [])) (\x -> do xs <- E.joinI (EB.splitWhen (== x) $$ EL.consume) extra <- EL.consume return (xs, extra)) (\x bytes -> let split = LS.split . LS.dropFinalBlank . LS.dropDelims . LS.whenElt words = BL.unpack bytes in Right (map B.pack (split (== x) words), [])) suite_Map :: Suite suite_Map = test_Enumeratee "map" (EL.map id) suite_ConcatMap :: Suite suite_ConcatMap = test_Enumeratee "concatMap" (EL.concatMap (:[])) suite_MapM :: Suite suite_MapM = test_Enumeratee "mapM" (EL.mapM return) suite_ConcatMapM :: Suite suite_ConcatMapM = test_Enumeratee "concatMapM" (EL.concatMapM (\x -> return [x])) suite_MapAccum :: Suite suite_MapAccum = testListAnalogue "mapAccum" (do let enee = EL.mapAccum (\s ao -> (s+1, (s, ao))) 10 a <- E.joinI (enee $$ EL.head) b <- EL.consume return (a, b)) (\xs -> Right $ case xs of [] -> (Nothing, []) (x:xs') -> (Just (10, x), xs')) (do let enee = ET.mapAccum (\s ao -> (s+1, succ ao)) 10 a <- E.joinI (enee $$ EL.head) b <- ET.consume return (a, b)) (\text -> Right $ case TL.uncons text of Nothing -> (Nothing, TL.empty) Just (c, text') -> (Just (T.singleton (succ c)), text')) (do let enee = EB.mapAccum (\s ao -> (s+1, ao + s)) 10 a <- E.joinI (enee $$ EL.head) b <- EB.consume return (a, b)) (\bytes -> Right $ case BL.uncons bytes of Nothing -> (Nothing, BL.empty) Just (b, bytes') -> (Just (B.singleton (b + 10)), bytes')) suite_MapAccumM :: Suite suite_MapAccumM = testListAnalogue "mapAccumM" (do let enee = EL.mapAccumM (\s ao -> return (s+1, (s, ao))) 10 a <- E.joinI (enee $$ EL.head) b <- EL.consume return (a, b)) (\xs -> Right $ case xs of [] -> (Nothing, []) (x:xs') -> (Just (10, x), xs')) (do let enee = ET.mapAccumM (\s ao -> return (s+1, succ ao)) 10 a <- E.joinI (enee $$ EL.head) b <- ET.consume return (a, b)) (\text -> Right $ case TL.uncons text of Nothing -> (Nothing, TL.empty) Just (c, text') -> (Just (T.singleton (succ c)), text')) (do let enee = EB.mapAccumM (\s ao -> return (s+1, ao + s)) 10 a <- E.joinI (enee $$ EL.head) b <- EB.consume return (a, b)) (\bytes -> Right $ case BL.uncons bytes of Nothing -> (Nothing, BL.empty) Just (b, bytes') -> (Just (B.singleton (b + 10)), bytes')) suite_Filter :: Suite suite_Filter = test_Enumeratee "filter" (EL.filter (\_ -> True)) suite_FilterM :: Suite suite_FilterM = test_Enumeratee "filterM" (EL.filterM (\_ -> return True)) -- }}} -- Specific functions suite_Other :: Suite suite_Other = suite "other" [ test_Sequence , test_joinE , suite "catchError" [ test test_CatchError_WithoutContinue , test test_CatchError_NotDivergent , test test_CatchError_Interleaved ] , test test_Zip , test test_ZipBytes , test test_ZipText ] test_Sequence :: Suite test_Sequence = property "sequence" prop where prop :: Positive Integer -> [A] -> Bool prop (Positive n) xs = result == expected where result = runIdentity (E.run_ iter) expected = map Just xs iter = E.enumList n xs $$ E.joinI (E.sequence EL.head $$ EL.consume) test_joinE :: Suite test_joinE = property "joinE" prop where prop :: [Integer] -> Bool prop xs = result == expected where result = runIdentity (E.run_ iter) expected = map (* 10) xs iter = (E.joinE (E.enumList 1 xs) (EL.map (* 10))) $$ EL.consume test_CatchError_WithoutContinue :: Test test_CatchError_WithoutContinue = assertions "without-continue" $ do let iter = E.catchError (E.throwError (Exc.ErrorCall "error")) (\_ -> EL.require 1) res <- E.run (E.enumList 1 [] $$ iter) $assert (left res) let Left err = res $assert $ equal (Exc.fromException err) (Just (Exc.ErrorCall "require: Unexpected EOF")) test_CatchError_NotDivergent :: Test test_CatchError_NotDivergent = assertions "not-divergent" $ do let iter = E.catchError (do EL.head E.throwError (Exc.ErrorCall "error")) (\_ -> EL.require 1) res <- E.run (E.enumList 1 [] $$ iter) $assert (left res) let Left err = res $assert $ equal (Exc.fromException err) (Just (Exc.ErrorCall "require: Unexpected EOF")) test_CatchError_Interleaved :: Test test_CatchError_Interleaved = within 1000 $ assertions "interleaved" $ do let enumMVar mvar = EL.repeatM (liftIO (takeMVar mvar)) let iter mvar = do liftIO (putMVar mvar ()) EL.head return True let onError err = return False mvar <- liftIO newEmptyMVar E.run_ (enumMVar mvar $$ E.catchError (iter mvar) onError) test_Zip :: Test test_Zip = assertions "zip" $ do let iterTup = do Just x <- EL.head Just y <- EL.head return (x, y) let iterTupFlip = do Just x <- EL.head Just y <- EL.head return (y, x) let check i1 i2 = E.run_ (E.enumList 4 [1, 2, 3, 4, 5] $$ do (x, y) <- EL.zip i1 i2 extra <- EL.consume return (x, y, extra)) -- Both sides have same behavior (tup, tup2, extra) <- check iterTup iterTupFlip $expect (equal tup (1, 2)) $expect (equal tup2 (2, 1)) $expect (equal extra [3, 4, 5]) -- First side has more extra data (took, tup, extra) <- check (EL.take 1) iterTup $expect (equal took [1]) $expect (equal tup (1, 2)) $expect (equal extra [3, 4, 5]) -- Second side has more extra data (tup, took, extra) <- check iterTup (EL.take 1) $expect (equal tup (1, 2)) $expect (equal took [1]) $expect (equal extra [3, 4, 5]) test_ZipBytes :: Test test_ZipBytes = assertions "zip-bytes" $ do let iterTup = do Just x <- EB.head Just y <- EB.head return (x, y) let iterTupFlip = do Just x <- EB.head Just y <- EB.head return (y, x) let check i1 i2 = E.run_ (E.enumList 2 ["abc", "def", "ghi"] $$ do (x, y) <- EB.zip i1 i2 extra <- EL.consume return (x, y, extra)) -- Both sides have same behavior (tup, tup2, extra) <- check iterTup iterTupFlip $expect (equal tup (0x61, 0x62)) $expect (equal tup2 (0x62, 0x61)) $expect (equal extra ["c", "def", "ghi"]) -- First side has more extra data (took, tup, extra) <- check (EB.take 1) iterTup $expect (equal took "a") $expect (equal tup (0x61, 0x62)) $expect (equal extra ["c", "def", "ghi"]) -- Second side has more extra data (tup, took, extra) <- check iterTup (EB.take 1) $expect (equal tup (0x61, 0x62)) $expect (equal took "a") $expect (equal extra ["c", "def", "ghi"]) test_ZipText :: Test test_ZipText = assertions "zip-text" $ do let iterTup = do Just x <- ET.head Just y <- ET.head return (x, y) let iterTupFlip = do Just x <- ET.head Just y <- ET.head return (y, x) let check i1 i2 = E.run_ (E.enumList 2 ["abc", "def", "ghi"] $$ do (x, y) <- ET.zip i1 i2 extra <- EL.consume return (x, y, extra)) -- Both sides have same behavior (tup, tup2, extra) <- check iterTup iterTupFlip $expect (equal tup ('a', 'b')) $expect (equal tup2 ('b', 'a')) $expect (equal extra ["c", "def", "ghi"]) -- First side has more extra data (took, tup, extra) <- check (ET.take 1) iterTup $expect (equal took "a") $expect (equal tup ('a', 'b')) $expect (equal extra ["c", "def", "ghi"]) -- Second side has more extra data (tup, took, extra) <- check iterTup (ET.take 1) $expect (equal tup ('a', 'b')) $expect (equal took "a") $expect (equal extra ["c", "def", "ghi"]) -- misc genASCII :: IsString a => Gen a genASCII = fmap fromString string where string = sized $ \n -> do k <- choose (0,n) sequence [ char | _ <- [1..k] ] char = chr `fmap` choose (0,0x7F) genISO8859 :: IsString a => Gen a genISO8859 = fmap fromString string where string = sized $ \n -> do k <- choose (0,n) sequence [ char | _ <- [1..k] ] char = chr `fmap` choose (0,0xFF) genUnicode :: IsString a => Gen a genUnicode = fmap fromString string where string = sized $ \n -> do k <- choose (0,n) sequence [ char | _ <- [1..k] ] excluding :: [a -> Bool] -> Gen a -> Gen a excluding bad gen = loop where loop = do x <- gen if or (map ($ x) bad) then loop else return x reserved = [lowSurrogate, highSurrogate, noncharacter] lowSurrogate c = c >= 0xDC00 && c <= 0xDFFF highSurrogate c = c >= 0xD800 && c <= 0xDBFF noncharacter c = masked == 0xFFFE || masked == 0xFFFF where masked = c .&. 0xFFFF ascii = choose (0,0x7F) plane0 = choose (0xF0, 0xFFFF) plane1 = oneof [ choose (0x10000, 0x10FFF) , choose (0x11000, 0x11FFF) , choose (0x12000, 0x12FFF) , choose (0x13000, 0x13FFF) , choose (0x1D000, 0x1DFFF) , choose (0x1F000, 0x1FFFF) ] plane2 = oneof [ choose (0x20000, 0x20FFF) , choose (0x21000, 0x21FFF) , choose (0x22000, 0x22FFF) , choose (0x23000, 0x23FFF) , choose (0x24000, 0x24FFF) , choose (0x25000, 0x25FFF) , choose (0x26000, 0x26FFF) , choose (0x27000, 0x27FFF) , choose (0x28000, 0x28FFF) , choose (0x29000, 0x29FFF) , choose (0x2A000, 0x2AFFF) , choose (0x2B000, 0x2BFFF) , choose (0x2F000, 0x2FFFF) ] plane14 = choose (0xE0000, 0xE0FFF) planes = [ascii, plane0, plane1, plane2, plane14] char = chr `fmap` excluding reserved (oneof planes) instance Arbitrary a => Arbitrary (E.Stream a) where arbitrary = frequency [ (10, return E.EOF) , (90, fmap E.Chunks arbitrary) ] instance Arbitrary T.Text where arbitrary = genUnicode instance Arbitrary B.ByteString where arbitrary = genUnicode instance Eq Exc.ErrorCall where (Exc.ErrorCall s1) == (Exc.ErrorCall s2) = s1 == s2 -- | Require a test to complete within /n/ milliseconds. within :: Int -> Test -> Test within time (Test name io) = Test name $ \opts -> do res <- timeout (time * 1000) (io opts) case res of Just res' -> return res' Nothing -> return (TestAborted [] (T.pack ("Test timed out after " ++ show time ++ " milliseconds")))