{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE StandaloneDeriving #-} module Main where import Data.List.Split.Internals import Test.QuickCheck import Test.QuickCheck.Function import Control.Monad import System.Environment import Text.Printf import Data.Char import Data.Functor import Data.List ( genericTake, group, intercalate, isInfixOf, isPrefixOf, isSuffixOf, tails, ) import Data.Maybe (isJust) newtype Elt = Elt {unElt :: Char} deriving (Eq) instance Show Elt where show (Elt c) = show c instance Arbitrary Elt where arbitrary = elements (map Elt "abcde") instance CoArbitrary Elt where coarbitrary = coarbitrary . ord . unElt instance Function Elt where function = functionMap unElt Elt deriving instance Show (Splitter Elt) instance Show (Delimiter Elt) where show (Delimiter ps) = show (map function ps) instance (Arbitrary a, CoArbitrary a, Function a) => Arbitrary (Delimiter a) where arbitrary = Delimiter . map apply <$> arbitrary instance (Arbitrary a) => Arbitrary (Chunk a) where arbitrary = oneof [ Text <$> listOf arbitrary , Delim <$> listOf arbitrary ] instance Arbitrary DelimPolicy where arbitrary = elements [Drop, Keep, KeepLeft, KeepRight] instance Arbitrary CondensePolicy where arbitrary = elements [Condense, KeepBlankFields] instance Arbitrary EndPolicy where arbitrary = elements [DropBlank, KeepBlank] instance (Arbitrary a, CoArbitrary a, Function a) => Arbitrary (Splitter a) where arbitrary = liftM5 Splitter arbitrary arbitrary arbitrary arbitrary arbitrary type Delim a = [Fun a Bool] unDelim :: Delim a -> Delimiter a unDelim = Delimiter . map apply main :: IO () main = do results <- mapM (\(s, t) -> printf "%-40s" s >> t) tests unless (all isSuccess results) $ fail "Not all tests passed!" where isSuccess (Success {}) = True isSuccess _ = False qc x = quickCheckWithResult (stdArgs {maxSuccess = 200}) x tests = [ ("default/id", qc prop_default_id) , ("match/decompose", qc prop_match_decompose) , ("match/yields delim", qc prop_match_yields_delim) , ("splitInternal/lossless", qc prop_splitInternal_lossless) , ("splitInternal/yields delims", qc prop_splitInternal_yields_delims) , ("splitInternal/text", qc prop_splitInternal_text_not_delims) , ("doCondense/no consec delims", qc prop_doCondense_no_consec_delims) , ("insBlanks/no consec delims", qc prop_insBlanks_no_consec_delims) , ("insBlanks/fl not delims", qc prop_insBlanks_fl_not_delim) , ("mergeL/no delims", qc prop_mergeL_no_delims) , ("mergeR/no delims", qc prop_mergeR_no_delims) , ("oneOf", qc prop_oneOf) , ("oneOf/not text", qc prop_oneOf_not_text) , ("onSublist", qc prop_onSublist) , ("onSublist/not text", qc prop_onSublist_not_text) , ("whenElt", qc prop_whenElt) , ("whenElt/not text", qc prop_whenElt_not_text) , ("process/dropDelims", qc prop_dropDelims) , ("process/keepDelimsL no delims", qc prop_keepDelimsL_no_delims) , ("process/keepDelimsR no delims", qc prop_keepDelimsR_no_delims) , ("process/keepDelimsL match", qc prop_keepDelimsL_match) , ("process/keepDelimsR match", qc prop_keepDelimsR_match) , ("condense/no consec delims", qc prop_condense_no_consec_delims) , ("condense/all delims", qc prop_condense_all_delims) , ("dropInitBlank", qc prop_dropInitBlank) , ("dropFinalBlank", qc prop_dropFinalBlank) , ("dropBlanks", qc prop_dropBlanks) , ("startsWith", qc prop_startsWith) , ("startsWithOneOf", qc prop_startsWithOneOf) , ("endsWith", qc prop_endsWith) , ("endsWithOneOf", qc prop_endsWithOneOf) , ("splitOn/right inv", qc prop_splitOn_right_inv) , ("splitOn/idem", qc prop_splitOn_intercalate_idem) , ("splitOn/empty delim", qc prop_splitOn_empty_delim) , ("split/empty delim", qc prop_split_empty_delim_drop) , ("chunksOf/lengths", qc prop_chunksOf_all_n) , ("chunksOf/last <= n", qc prop_chunksOf_last_less_n) , ("chunksOf/preserve", qc prop_chunksOf_preserve) , ("splitPlaces/lengths", qc prop_splitPlaces_lengths) , ("splitPlaces/last <= n", qc prop_splitPlaces_last_less_n) , ("splitPlaces/preserve", qc prop_splitPlaces_preserve) , ("splitPlaces/chunksOf", qc prop_splitPlaces_chunksOf) , ("splitPlacesB/length", qc prop_splitPlacesB_length) , ("splitPlacesB/last <= n", qc prop_splitPlacesB_last_less_n) , ("splitPlacesB/preserve", qc prop_splitPlacesB_preserve) , ("lines", qc prop_lines) , ("wordsBy/words", qc prop_wordsBy_words) , ("linesBy/lines", qc prop_linesBy_lines) , ("chop/group", qc prop_chop_group) , ("chop/words", qc prop_chop_words) , ("divvy/evenly", qc prop_divvy_evenly) , ("divvy/discard_remainder", qc prop_divvy_discard_remainder) , ("divvy/outputlists_allsame_length", qc prop_divvy_outputlists_allsame_length) , ("divvy/output_are_sublists", qc prop_divvy_output_are_sublists) , ("divvy/heads", qc prop_divvy_heads) ] prop_default_id :: [Elt] -> Bool prop_default_id l = split defaultSplitter l == [l] prop_match_decompose :: Delim Elt -> [Elt] -> Bool prop_match_decompose d l = maybe True ((== l) . uncurry (++)) $ matchDelim (unDelim d) l isDelimMatch :: Delim Elt -> [Elt] -> Bool isDelimMatch d l = matchDelim (unDelim d) l == Just (l, []) prop_match_yields_delim :: Delim Elt -> [Elt] -> Bool prop_match_yields_delim d l = case matchDelim (unDelim d) l of Nothing -> True Just (del, rest) -> isDelimMatch d del prop_splitInternal_lossless :: Delim Elt -> [Elt] -> Bool prop_splitInternal_lossless d l = concatMap fromElem (splitInternal (unDelim d) l) == l prop_splitInternal_yields_delims :: Delim Elt -> [Elt] -> Bool prop_splitInternal_yields_delims d l = all (isDelimMatch d) $ [del | (Delim del) <- splitInternal d' l] where d' = unDelim d prop_splitInternal_text_not_delims :: Delim Elt -> [Elt] -> Bool prop_splitInternal_text_not_delims d l = not (any (isDelimMatch d) ([ch | (Text ch) <- splitInternal d' l])) where d' = unDelim d noConsecDelims :: SplitList Elt -> Bool noConsecDelims [] = True noConsecDelims [x] = True noConsecDelims (Delim _ : Delim _ : _) = False noConsecDelims (_ : xs) = noConsecDelims xs prop_doCondense_no_consec_delims :: SplitList Elt -> Bool prop_doCondense_no_consec_delims l = noConsecDelims $ doCondense Condense l prop_insBlanks_no_consec_delims :: SplitList Elt -> Bool prop_insBlanks_no_consec_delims l = noConsecDelims $ insertBlanks Condense l prop_insBlanks_fl_not_delim :: SplitList Elt -> Bool prop_insBlanks_fl_not_delim l = case insertBlanks Condense l of [] -> True xs -> (not . isDelim $ head xs) && (not . isDelim $ last xs) prop_mergeL_no_delims :: SplitList Elt -> Bool prop_mergeL_no_delims = not . any isDelim . mergeLeft . insertBlanks Condense prop_mergeR_no_delims :: SplitList Elt -> Bool prop_mergeR_no_delims = not . any isDelim . mergeRight . insertBlanks Condense getDelims :: Splitter Elt -> [Elt] -> [[Elt]] getDelims s l = [d | Delim d <- splitInternal (delimiter s) l] getTexts :: Splitter Elt -> [Elt] -> [[Elt]] getTexts s l = [c | Text c <- splitInternal (delimiter s) l] prop_oneOf :: [Elt] -> [Elt] -> Bool prop_oneOf elts l = all ((== 1) . length) ds && all ((`elem` elts) . head) ds where ds = getDelims (oneOf elts) l prop_oneOf_not_text :: [Elt] -> [Elt] -> Bool prop_oneOf_not_text elts l = not (any (`elem` elts) (concat cs)) where cs = getTexts (oneOf elts) l prop_onSublist :: [Elt] -> [Elt] -> Bool prop_onSublist sub l = all (== sub) $ getDelims (onSublist sub) l prop_onSublist_not_text :: [Elt] -> [Elt] -> Property prop_onSublist_not_text sub l = (not . null $ sub) ==> not (any (isInfixOf sub) $ getTexts (onSublist sub) l) prop_whenElt :: Fun Elt Bool -> [Elt] -> Bool prop_whenElt (Fun _ p) l = all ((== 1) . length) ds && all (p . head) ds where ds = getDelims (whenElt p) l prop_whenElt_not_text :: Fun Elt Bool -> [Elt] -> Bool prop_whenElt_not_text (Fun _ p) l = not (any p (concat cs)) where cs = getTexts (whenElt p) l process :: Splitter Elt -> [Elt] -> SplitList Elt process s = postProcess s . splitInternal (delimiter s) prop_dropDelims :: Splitter Elt -> [Elt] -> Bool prop_dropDelims s l = not (any isDelim (process (dropDelims s) l)) prop_keepDelimsL_no_delims :: Splitter Elt -> [Elt] -> Bool prop_keepDelimsL_no_delims s l = not (any isDelim (process (keepDelimsL s) l)) prop_keepDelimsL_match :: Splitter Elt -> NonEmptyList Elt -> Bool prop_keepDelimsL_match s (NonEmpty l) = all (isJust . matchDelim (delimiter s)) [c | Text c <- tail p] where p = process (keepDelimsL s) l prop_keepDelimsR_no_delims :: Splitter Elt -> [Elt] -> Bool prop_keepDelimsR_no_delims s l = not (any isDelim (process (keepDelimsR s) l)) prop_keepDelimsR_match :: Splitter Elt -> NonEmptyList Elt -> Bool prop_keepDelimsR_match s (NonEmpty l) = all (any (isJust . matchDelim (delimiter s)) . tails) [c | Text c <- init p] where p = process (keepDelimsR s) l prop_condense_no_consec_delims :: Splitter Elt -> [Elt] -> Bool prop_condense_no_consec_delims s l = noConsecDelims $ process (condense s) l prop_condense_all_delims :: Splitter Elt -> [Elt] -> Bool prop_condense_all_delims s l = all allDelims p where p = [d | Delim d <- process (condense s) l] allDelims t = all isDelim (splitInternal (delimiter s) t) prop_dropInitBlank :: Splitter Elt -> NonEmptyList Elt -> Bool prop_dropInitBlank s (NonEmpty l) = head p /= Text [] where p = process (dropInitBlank $ s {delimPolicy = Keep}) l prop_dropFinalBlank :: Splitter Elt -> NonEmptyList Elt -> Bool prop_dropFinalBlank s (NonEmpty l) = last p /= Text [] where p = process (dropFinalBlank $ s {delimPolicy = Keep}) l prop_dropBlanks :: Splitter Elt -> [Elt] -> Bool prop_dropBlanks s = notElem (Text []) . process (dropBlanks s) prop_startsWith :: [Elt] -> NonEmptyList Elt -> Bool prop_startsWith s (NonEmpty l) = all (s `isPrefixOf`) (tail $ split (startsWith s) l) prop_startsWithOneOf :: [Elt] -> NonEmptyList Elt -> Bool prop_startsWithOneOf elts (NonEmpty l) = all ((`elem` elts) . head) (tail $ split (startsWithOneOf elts) l) prop_endsWith :: [Elt] -> NonEmptyList Elt -> Bool prop_endsWith s (NonEmpty l) = all (s `isSuffixOf`) (init $ split (endsWith s) l) prop_endsWithOneOf :: [Elt] -> NonEmptyList Elt -> Bool prop_endsWithOneOf elts (NonEmpty l) = all ((`elem` elts) . last) (init $ split (endsWithOneOf elts) l) prop_splitOn_right_inv :: [Elt] -> [Elt] -> Bool prop_splitOn_right_inv x l = intercalate x (splitOn x l) == l {- This property fails: for example, splitOn "dd" (intercalate "dd" ["d",""]) == ["","d"] so it's not enough just to say that the delimiter is not an infix of any elements of l! prop_splitOn_left_inv :: [Elt] -> NonEmptyList [Elt] -> Property prop_splitOn_left_inv x (NonEmpty ls) = not (any (x `isInfixOf`) ls) ==> splitOn x (intercalate x ls) == ls -} -- Note, the below property is in fact logically entailed by -- prop_splitOn_right_inv, but we keep it here just for kicks. prop_splitOn_intercalate_idem :: [Elt] -> [[Elt]] -> Bool prop_splitOn_intercalate_idem x ls = f (f ls) == f ls where f = splitOn x . intercalate x prop_splitOn_empty_delim :: [Elt] -> Bool prop_splitOn_empty_delim ls = splitOn [] ls == [] : map (: []) ls prop_split_empty_delim_drop :: [Elt] -> Bool prop_split_empty_delim_drop ls = split (dropDelims . dropBlanks $ onSublist []) ls == map (: []) ls prop_chunksOf_all_n :: Positive Int -> NonEmptyList Elt -> Bool prop_chunksOf_all_n (Positive n) (NonEmpty l) = all ((== n) . length) (init $ chunksOf n l) prop_chunksOf_last_less_n :: Positive Int -> NonEmptyList Elt -> Bool prop_chunksOf_last_less_n (Positive n) (NonEmpty l) = (<= n) . length . last $ chunksOf n l prop_chunksOf_preserve :: Positive Int -> [Elt] -> Bool prop_chunksOf_preserve (Positive n) l = concat (chunksOf n l) == l prop_splitPlaces_lengths :: [NonNegative Int] -> [Elt] -> Bool prop_splitPlaces_lengths ps = and . mInit . zipWith (==) ps' . map length . splitPlaces ps' where ps' = map unNN ps prop_splitPlaces_last_less_n :: NonEmptyList (NonNegative Int) -> NonEmptyList Elt -> Bool prop_splitPlaces_last_less_n (NonEmpty ps) (NonEmpty l) = (ps' !! max 0 (length l' - 1)) >= length (last l') where l' = splitPlaces ps' l ps' = map unNN ps prop_splitPlaces_preserve :: [NonNegative Integer] -> [Elt] -> Bool prop_splitPlaces_preserve ps l = concat (splitPlaces ps' l) == genericTake (sum ps') l where ps' = map unNN ps prop_splitPlaces_chunksOf :: Positive Int -> [Elt] -> Bool prop_splitPlaces_chunksOf (Positive n) l = splitPlaces (repeat n) l == chunksOf n l prop_splitPlacesB_length :: [NonNegative Int] -> [Elt] -> Bool prop_splitPlacesB_length ps xs = length ps' == length (splitPlacesBlanks ps' xs) where ps' = map unNN ps prop_splitPlacesB_last_less_n :: NonEmptyList (NonNegative Int) -> NonEmptyList Elt -> Bool prop_splitPlacesB_last_less_n (NonEmpty ps) (NonEmpty l) = (ps' !! max 0 (length l' - 1)) >= length (last l') where l' = splitPlacesBlanks ps' l ps' = map unNN ps prop_splitPlacesB_preserve :: [NonNegative Integer] -> [Elt] -> Bool prop_splitPlacesB_preserve ps l = concat (splitPlacesBlanks ps' l) == genericTake (sum ps') l where ps' = map unNN ps unNN :: NonNegative a -> a unNN (NonNegative x) = x mInit :: [a] -> [a] mInit [] = [] mInit [x] = [] mInit (x : xs) = x : init xs newtype EltWS = EltWS {unEltWS :: Char} deriving (Eq, Show) instance Arbitrary EltWS where arbitrary = elements (map EltWS "abcde \n") prop_lines :: [EltWS] -> Bool prop_lines s = lines s' == endBy "\n" s' where s' = map unEltWS s prop_wordsBy_words :: [EltWS] -> Bool prop_wordsBy_words s = words s' == wordsBy isSpace s' where s' = map unEltWS s prop_linesBy_lines :: [EltWS] -> Bool prop_linesBy_lines s = lines s' == linesBy (== '\n') s' where s' = map unEltWS s prop_chop_group :: [Elt] -> Bool prop_chop_group s = chop (\xs@(x : _) -> span (== x) xs) s == group s prop_chop_words :: [EltWS] -> Bool prop_chop_words s = words s' == (filter (not . null) . chop (break isSpace . dropWhile isSpace) $ s') where s' = map unEltWS s prop_divvy_evenly :: [Elt] -> Positive Int -> Bool prop_divvy_evenly elems (Positive n) = concat (divvy n n elems') == elems' where -- Chop off the smallest possible tail of elems to make the length -- evenly divisible by n. This property used to have a -- precondition (length elements `mod` n == 0), but that led to too -- many discarded test cases and occasional test suite failures. elems' = take ((length elems `div` n) * n) elems prop_divvy_discard_remainder :: [Elt] -> Positive Int -> Bool prop_divvy_discard_remainder elems (Positive n) = concat (divvy n n elems) == (reverse . drop (length elems `mod` n) . reverse $ elems) prop_divvy_outputlists_allsame_length :: [Elt] -> Positive Int -> Positive Int -> Bool prop_divvy_outputlists_allsame_length elems (Positive n) (Positive m) = allSame xs where allSame :: [Int] -> Bool allSame [] = True allSame zs = all (== head zs) (tail zs) xs = map length (divvy n m elems) prop_divvy_output_are_sublists :: [Elt] -> Positive Int -> Positive Int -> Bool prop_divvy_output_are_sublists elems (Positive n) (Positive m) = all (`isInfixOf` elems) xs where xs = divvy n m elems takeEvery :: Int -> [a] -> [a] takeEvery _ [] = [] takeEvery n lst = (map head . chunksOf n) lst initNth :: Int -> [a] -> [a] initNth _ [] = [] initNth n lst = (reverse . drop n . reverse) lst prop_divvy_heads :: [Elt] -> Positive Int -> Positive Int -> Bool prop_divvy_heads [] _ _ = True prop_divvy_heads elems (Positive n) (Positive m) = hds1 == hds2 where hds1 = takeEvery m (initNth (n - 1) elems) hds2 = map head $ divvy n m elems