{-# OPTIONS_GHC -fglasgow-exts #-} import Test.QuickCheck import Data.Char (ord) import Control.Monad import Text.Printf import Debug.Trace import System.Environment import qualified Data.CompactString.UTF8 as C import qualified Data.List as L import Data.CompactString.Encodings import qualified Data.ByteString as B import QuickCheckUtils hiding (C) type C = C.CompactString ------------------------------------------------------------------------ -- Functions not in Data.List l_headView [] = Nothing l_headView (x:xs) = Just (x, xs) l_lastView xs = if L.null xs then Nothing else Just (L.init xs, L.last xs) l_mapIndexed f = L.zipWith f [0..] l_spanEnd p = (\(x,y) -> (reverse y, reverse x)) . span p . reverse l_breakEnd p = l_spanEnd (not . p) l_findSubstring pat str = case l_findSubstrings pat str of [] -> Nothing (x:xs) -> Just x l_findSubstrings pat str = L.map fst $ L.filter snd $ zip [0..] $ L.map (pat `L.isPrefixOf`) (L.tails str) l_split c = l_splitWith (==c) l_splitWith p [] = [] l_splitWith p xs = sw xs where sw xs = case L.break p xs of (_ ,[] ) -> [xs] (ys,(_:zs)) -> ys : sw zs l_count c = L.length . L.filter (==c) ------------------------------------------------------------------------ -- Properties : Internal consistency prop_validate cs = C.validate cs == Just cs ------------------------------------------------------------------------ -- Properties : Functions not in the prelude prop_pack xs = C.unpack (C.pack xs) == xs prop_unpack cs = C.pack (C.unpack cs) == cs prop_elemIndexEnd c xs = C.elemIndexEnd c xs == (-) (C.length xs - 1) `fmap` C.elemIndex c (C.reverse xs) prop_findIndexEnd c xs = C.findIndexEnd c xs == (-) (C.length xs - 1) `fmap` C.findIndex c (C.reverse xs) ------------------------------------------------------------------------ -- Properties : Comparing with lists prop_eq = eq2 ((==) :: C -> C -> Bool) ((==) :: S -> S -> Bool) prop_compare = eq2 (compare :: C -> C -> Ordering) (compare :: S -> S -> Ordering) prop_show = eq1 (show :: C -> String) (show :: S -> String) prop_empty = C.empty `eq0` "" prop_singleton = C.singleton `eq1` (return :: Char -> S) prop_cons = C.cons `eq2` ((:) :: Char -> S -> S) prop_snoc = C.snoc `eq2` ((\s c -> s ++ [c]) :: S -> Char -> S) prop_append = C.append `eq2` ((++) :: S -> S -> S) prop_head = C.head `eqnotnull1` (L.head :: S -> Char) prop_tail = C.tail `eqnotnull1` (L.tail :: S -> S) prop_last = C.last `eqnotnull1` (L.last :: S -> Char) prop_init = C.init `eqnotnull1` (L.init :: S -> S) prop_headView = C.headView `eq1` (l_headView :: S -> Maybe (Char, S)) prop_lastView = C.lastView `eq1` (l_lastView :: S -> Maybe (S, Char)) prop_null = C.null `eq1` (L.null :: S -> Bool) prop_length = C.length `eq1` (L.length :: S -> Int) prop_map = C.map `eq2` (L.map :: (Char -> Char) -> (S -> S)) prop_reverse = C.reverse `eq1` (L.reverse :: S -> S) prop_intersperse= C.intersperse `eq2` (L.intersperse :: Char -> S -> S) prop_transpose = C.transpose `eq1` (L.transpose :: [S] -> [S]) prop_foldl = eq3 (C.foldl :: (Int -> Char -> Int) -> Int -> C -> Int) (L.foldl :: (Int -> Char -> Int) -> Int -> S -> Int) prop_foldr = eq3 (C.foldr :: (Char -> Int -> Int) -> Int -> C -> Int) (L.foldr :: (Char -> Int -> Int) -> Int -> S -> Int) prop_foldl' = eq3 (C.foldl' :: (Int -> Char -> Int) -> Int -> C -> Int) (L.foldl' :: (Int -> Char -> Int) -> Int -> S -> Int) prop_foldl1 = C.foldl1 `eqnotnull2` (L.foldl1 :: (Char -> Char -> Char) -> S -> Char) prop_foldr1 = C.foldr1 `eqnotnull2` (L.foldr1 :: (Char -> Char -> Char) -> S -> Char) prop_foldl1' = C.foldl1' `eqnotnull2` (L.foldl1' :: (Char -> Char -> Char) -> S -> Char) prop_concat = C.concat `eq1` (L.concat :: [S] -> S) prop_concatMap = C.concatMap `eq2b` (L.concatMap :: (Char -> S) -> S -> S) prop_any = C.any `eq2` (L.any :: (Char -> Bool) -> S -> Bool) prop_all = C.all `eq2` (L.all :: (Char -> Bool) -> S -> Bool) prop_maximum = C.maximum `eqnotnull1` (L.maximum :: S -> Char) prop_minimum = C.minimum `eqnotnull1` (L.minimum :: S -> Char) prop_concatMap2 = c_concatMap2 `eq2b` (L.concatMap :: (Char -> S) -> S -> S) where c_concatMap2 f = C.concat . L.map (C.validate_ . f) . C.unpack prop_concatMap3 = c_concatMap3 `eq2` (L.map :: (Char -> S) -> S -> [S]) where c_concatMap3 :: (Char -> C) -> C -> [C] c_concatMap3 f = L.map f . C.unpack prop_concatMapFoldr = eq2 (\f -> C.foldr ((:) . f) [] :: C -> [C]) (\f -> L.foldr ((:) . f) [] :: S -> [S]) prop_scanl = C.scanl `eq3` (L.scanl :: (Char -> Char -> Char) -> Char -> S -> S) prop_scanr = C.scanr `eq3` (L.scanr :: (Char -> Char -> Char) -> Char -> S -> S) prop_scanl1 = C.scanl1 `eqnotnull2` (L.scanl1 :: (Char -> Char -> Char) -> S -> S) prop_scanr1 = C.scanr1 `eqnotnull2` (L.scanr1 :: (Char -> Char -> Char) -> S -> S) prop_mapAccumL = eq3 (C.mapAccumL :: (Int -> Char -> (Int, Char)) -> Int -> C -> (Int, C)) (L.mapAccumL :: (Int -> Char -> (Int, Char)) -> Int -> S -> (Int, S)) prop_mapAccumR = eq3 (C.mapAccumR :: (Int -> Char -> (Int, Char)) -> Int -> C -> (Int, C)) (L.mapAccumR :: (Int -> Char -> (Int, Char)) -> Int -> S -> (Int, S)) prop_mapIndexed = eq2 (C.mapIndexed :: (Int -> Char -> Char) -> C -> C) (l_mapIndexed :: (Int -> Char -> Char) -> S -> S) prop_replicate = C.replicate `eq2` (L.replicate :: Int -> Char -> S) -- note: we have to take a finite length, otherwise we could loop -- since C.unfoldr is strict this is not possible --prop_unfoldr = eq3 -- (\n -> C.take n (C.unfoldr :: (Int -> Maybe (Char, Int)) -> Int -> C)) -- (\n -> C.take n (L.unfoldr :: (Int -> Maybe (Char, Int)) -> Int -> S)) prop_unfoldrN = eq3 (\n f a -> fst $ (C.unfoldrN :: Int -> (Int -> Maybe (Char, Int)) -> Int -> (C, Maybe Int)) n f a) (\n f a -> L.take n $ (L.unfoldr :: (Int -> Maybe (Char, Int)) -> Int -> S ) f a) prop_take = C.take `eq2` (L.take :: Int -> S -> S) prop_drop = C.drop `eq2` (L.drop :: Int -> S -> S) prop_splitAt = C.splitAt `eq2` (L.splitAt :: Int -> S -> (S,S)) prop_takeWhile = C.takeWhile `eq2` (L.takeWhile :: (Char -> Bool) -> S -> S) prop_dropWhile = C.dropWhile `eq2` (L.dropWhile :: (Char -> Bool) -> S -> S) prop_break = C.break `eq2` (L.break :: (Char -> Bool) -> S -> (S,S)) prop_span = C.span `eq2` (L.span :: (Char -> Bool) -> S -> (S,S)) prop_breakEnd = C.breakEnd `eq2` (l_breakEnd :: (Char -> Bool) -> S -> (S,S)) prop_spanEnd = C.spanEnd `eq2` (l_spanEnd :: (Char -> Bool) -> S -> (S,S)) prop_group = C.group `eq1` (L.group :: S -> [S]) prop_groupBy = C.groupBy `eq2` (L.groupBy :: (Char -> Char -> Bool) -> S -> [S]) prop_inits = C.inits `eq1` (L.inits :: S -> [S]) prop_tails = C.tails `eq1` (L.tails :: S -> [S]) prop_split = C.split `eq2` (l_split :: Char -> S -> [S]) prop_splitWith = C.splitWith `eq2` (l_splitWith :: (Char -> Bool) ->S -> [S]) prop_lines = C.lines `eq1` (L.lines :: S -> [S]) prop_unlines = C.unlines `eq1` (L.unlines :: [S] -> S) prop_words = C.words `eq1` (L.words :: S -> [S]) prop_unwords = C.unwords `eq1` (L.unwords :: [S] -> S) prop_isPrefixOf = C.isPrefixOf `eq2` (L.isPrefixOf :: S -> S -> Bool) prop_isSuffixOf = C.isSuffixOf `eq2` (L.isSuffixOf :: S -> S -> Bool) prop_isSubstringOf = C.isInfixOf `eq2` (L.isInfixOf :: S -> S -> Bool) prop_findSubstring = C.findSubstring `eq2` (l_findSubstring:: S -> S -> Maybe Int) prop_findSubstrings = C.findSubstrings `eq2` (l_findSubstrings::S -> S -> [Int]) prop_elem = C.elem `eq2` (L.elem :: Char -> S -> Bool) prop_notElem = C.notElem `eq2` (L.notElem :: Char -> S -> Bool) prop_find = C.find `eq2` (L.find :: (Char -> Bool) -> S -> Maybe Char) prop_filter = C.filter `eq2` (L.filter :: (Char -> Bool) -> S -> S) prop_partition = C.partition `eq2` (L.partition :: (Char -> Bool) -> S -> (S,S)) prop_index = C.index `eqexcept2` ((L.!!) :: S -> Int -> Char) prop_elemIndex = C.elemIndex `eq2` (L.elemIndex :: Char -> S -> Maybe Int) prop_elemIndices = C.elemIndices `eq2` (L.elemIndices :: Char -> S -> [Int]) prop_findIndex = C.findIndex `eq2` (L.findIndex :: (Char -> Bool) -> S -> Maybe Int) prop_findIndices = C.findIndices `eq2` (L.findIndices :: (Char -> Bool) -> S -> [Int]) prop_count = C.count `eq2` (l_count :: Char -> S -> Int) prop_zip = C.zip `eq2` (L.zip :: S -> S -> [(Char,Char)]) prop_zipWith = eq3 (C.zipWith :: (Char -> Char -> Int) -> C -> C -> [Int]) (L.zipWith :: (Char -> Char -> Int) -> S -> S -> [Int]) prop_zipWith' = C.zipWith' `eq3` (L.zipWith :: (Char -> Char -> Char) -> S -> S -> S) prop_unzip = C.unzip `eq1` (L.unzip :: [(Char,Char)] -> (S,S)) prop_sort = C.sort `eq1` (L.sort :: S -> S) ------------------------------------------------------------------------ -- Properties : Encding -- "C.fromByteString . C.toByteString = id" prop_fromToByteString cs = C.fromByteString (C.toByteString cs) == Just cs -- "decode . encode = id" prop_de enc = \x -> case C.encode enc x of Nothing -> False ==> False Just y -> property $ C.decode enc y == Just x -- "encode . decode = id" prop_ed enc = \x -> case C.decode enc x of Nothing -> False ==> False Just y -> property $ C.encode enc y == Just x -- "decodeBOM . encodeBOM = id" prop_decode_bom enc = \x -> C.decodeBOM (C.encodeBOM_ enc x) == Just x prop_decode_encode_UTF8 = prop_de UTF8 prop_decode_encode_UTF16BE = prop_de (UTF16 BE) prop_decode_encode_UTF16LE = prop_de (UTF16 LE) prop_decode_encode_UTF32BE = prop_de (UTF32 BE) prop_decode_encode_UTF32LE = prop_de (UTF32 LE) prop_decode_encode_ASCII = prop_de ASCII prop_decode_encode_Latin1 = prop_de Latin1 prop_decode_encode_Compact = prop_de Compact prop_encode_decode_UTF8 = prop_ed UTF8 -- This does not hold for UTF16, because two different bytestrings can represent the -- same Unicode string: The order of a surrogate pair can be swapped --prop_encode_decode_UTF16BE = prop_ed (decode (UTF16 BE)) (encode (UTF16 BE)) --prop_encode_decode_UTF16LE = prop_ed (decode (UTF16 LE)) (encode (UTF16 LE)) prop_encode_decode_UTF32BE = prop_ed (UTF32 BE) prop_encode_decode_UTF32LE = prop_ed (UTF32 LE) prop_encode_decode_ASCII = prop_ed ASCII prop_encode_decode_Latin1 = prop_ed Latin1 prop_encode_decode_Compact = prop_ed Compact prop_decode_no_bom = \x -> C.take 1 x /= C.pack "\xFEFF" ==> C.decodeBOM (C.encode_ UTF8 x) == Just x prop_decode_bom_UTF8 = prop_decode_bom UTF8 prop_decode_bom_UTF16BE = prop_decode_bom (UTF16 BE) prop_decode_bom_UTF16LE = prop_decode_bom (UTF16 LE) prop_decode_bom_UTF32BE = prop_decode_bom (UTF32 BE) prop_decode_bom_UTF32LE = prop_decode_bom (UTF32 LE) ------------------------------------------------------------------------ -- The tests tests = list_tests ++ other_tests ++ encoding_tests list_tests :: [(String, Int -> IO ())] list_tests = [("validate", mytest prop_validate) ,("eq", mytest prop_eq) ,("compare", mytest prop_compare) ,("show", mytest prop_show) ,("empty", mytest prop_empty) ,("singleton", mytest prop_singleton) ,("cons", mytest prop_cons) ,("snoc", mytest prop_snoc) ,("append", mytest prop_append) ,("head", mytest prop_head) ,("tail", mytest prop_tail) ,("init", mytest prop_init) ,("null", mytest prop_null) ,("length", mytest prop_length) ,("map", mytest prop_map) ,("reverse", mytest prop_reverse) ,("intersperse", mytest prop_intersperse) ,("transpose", mytest prop_transpose) ,("foldl", mytest prop_foldl) ,("foldr", mytest prop_foldr) ,("foldl'", mytest prop_foldl') ,("foldl1", mytest prop_foldl1) ,("foldr1", mytest prop_foldr1) ,("foldl1'", mytest prop_foldl1') ,("concat", mytest prop_concat) ,("concatMap", mytest prop_concatMap) ,("any", mytest prop_any) ,("all", mytest prop_all) ,("maximum", mytest prop_maximum) ,("minimum", mytest prop_minimum) ,("scanl", mytest prop_scanl) ,("scanr", mytest prop_scanr) ,("scanl1", mytest prop_scanl1) ,("scanr1", mytest prop_scanr1) ,("mapAccumL", mytest prop_mapAccumL) ,("mapAccumR", mytest prop_mapAccumR) ,("mapIndexed", mytest prop_mapIndexed) ,("replicate", mytest prop_replicate) ,("unfoldrN", mytest prop_unfoldrN) ,("take", mytest prop_take) ,("drop", mytest prop_drop) ,("splitAt", mytest prop_splitAt) ,("takeWhile", mytest prop_takeWhile) ,("dropWhile", mytest prop_dropWhile) ,("break", mytest prop_break) ,("span", mytest prop_span) ,("breakEnd", mytest prop_breakEnd) ,("spanEnd", mytest prop_spanEnd) ,("group", mytest prop_group) ,("groupBy", mytest prop_groupBy) ,("inits", mytest prop_inits) ,("tails", mytest prop_tails) ,("split", mytest prop_split) ,("splitWith", mytest prop_splitWith) ,("lines", mytest prop_lines) ,("unlines", mytest prop_unlines) ,("words", mytest prop_words) ,("unwords", mytest prop_unwords) ,("isPrefixOf", mytest prop_isPrefixOf) ,("isSuffixOf", mytest prop_isSuffixOf) ,("isSubstringOf", mytest prop_isSubstringOf) ,("findSubstring", mytest prop_findSubstring) ,("findSubstrings", mytest prop_findSubstrings) ,("elem", mytest prop_elem) ,("notElem", mytest prop_notElem) ,("find", mytest prop_find) ,("filter", mytest prop_filter) ,("partition", mytest prop_partition) ,("index", mytest prop_index) ,("elemIndex", mytest prop_elemIndex) ,("elemIndices", mytest prop_elemIndices) ,("findIndex", mytest prop_findIndex) ,("findIndices", mytest prop_findIndices) ,("count", mytest prop_count) ,("zip", mytest prop_zip) ,("zipWith", mytest prop_zipWith) ,("zipWith'", mytest prop_zipWith') ,("unzip", mytest prop_unzip) ,("sort", mytest prop_sort) {- ,("", mytest ) -} ] other_tests :: [(String, Int -> IO ())] other_tests = [("unpack . pack", mytest prop_pack) ,("pack . unpack", mytest prop_unpack) ,("elemIndexEnd", mytest prop_elemIndexEnd) ,("findIndexEnd", mytest prop_findIndexEnd) ] encoding_tests :: [(String, Int -> IO ())] encoding_tests = [("from . toByteString", mytest prop_fromToByteString) ,("decode . encode: UTF-8", mytest prop_decode_encode_UTF8) ,("decode . encode: UTF-16BE", mytest prop_decode_encode_UTF16BE) ,("decode . encode: UTF-16LE", mytest prop_decode_encode_UTF16LE) ,("decode . encode: UTF-32BE", mytest prop_decode_encode_UTF32BE) ,("decode . encode: UTF-32LE", mytest prop_decode_encode_UTF32LE) ,("decode . encode: ASCII", mytest prop_decode_encode_ASCII) ,("decode . encode: Latin1", mytest prop_decode_encode_Latin1) ,("decode . encode: Compact", mytest prop_decode_encode_Compact) ,("encode . decode: UTF-8", mytest prop_encode_decode_UTF8) --,("encode . decode: UTF-16BE", mytest prop_encode_decode_UTF16BE) --,("encode . decode: UTF-16LE", mytest prop_encode_decode_UTF16LE) ,("encode . decode: UTF-32BE", mytest prop_encode_decode_UTF32BE) ,("encode . decode: UTF-32LE", mytest prop_encode_decode_UTF32LE) ,("encode . decode: ASCII", mytest prop_encode_decode_ASCII) ,("encode . decode: Latin1", mytest prop_encode_decode_Latin1) ,("encode . decode: Compact", mytest prop_encode_decode_Compact) ,("decode no BOM", mytest prop_decode_no_bom) ,("decode / BOM: UTF-8", mytest prop_decode_bom_UTF8) ,("decode / BOM: UTF-16BE", mytest prop_decode_bom_UTF16BE) ,("decode / BOM: UTF-16LE", mytest prop_decode_bom_UTF16LE) ,("decode / BOM: UTF-32BE", mytest prop_decode_bom_UTF32BE) ,("decode / BOM: UTF-32LE", mytest prop_decode_bom_UTF32LE) ] ------------------------------------------------------------------------ -- The entry point main = run tests run :: [(String, Int -> IO ())] -> IO () run tests = do x <- getArgs let n = if null x then 100 else read . head $ x mapM_ (\(s,a) -> printf "%-25s: " s >> a n) tests