split-0.2.4: test/Properties.hs
{-# 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