{-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE PatternSynonyms #-} module Spec where import Data.MonoTraversable import Data.Containers import Data.Sequences import qualified Data.Sequence as Seq import qualified Data.NonNull as NN import Data.Monoid (mempty, mconcat) import Data.Maybe (fromMaybe) import qualified Data.List as List import Test.Hspec import Test.Hspec.QuickCheck import Test.HUnit ((@?=)) import Test.QuickCheck hiding (NonEmptyList(..)) import Test.QuickCheck.Function (pattern Fn) import qualified Test.QuickCheck.Modifiers as QCM import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Storable as VS import qualified Data.List.NonEmpty as NE import qualified Data.Semigroup as SG import qualified Data.Map as Map import qualified Data.IntMap as IntMap import qualified Data.HashMap.Strict as HashMap import qualified Data.Set as Set import qualified Control.Foldl as Foldl import Control.Arrow (second) import Control.Applicative import Control.Monad.Trans.Writer import Prelude (Bool (..), ($), IO, Eq (..), fromIntegral, Ord (..), String, mod, Int, Integer, show, return, asTypeOf, (.), Show, (+), succ, Maybe (..), (*), mod, map, flip, otherwise, (-), div, maybe) import qualified Prelude newtype NonEmpty' a = NonEmpty' (NE.NonEmpty a) deriving (Show, Eq) instance Arbitrary a => Arbitrary (NonEmpty' a) where arbitrary = NonEmpty' <$> ((NE.:|) <$> arbitrary <*> arbitrary) -- | Arbitrary newtype for key-value pairs without any duplicate keys -- and is not empty newtype DuplPairs k v = DuplPairs { unDupl :: [(k,v)] } deriving (Eq, Show) removeDuplicateKeys :: Ord k => [(k,v)] -> [(k,v)] removeDuplicateKeys m = go Set.empty m where go _ [] = [] go used ((k,v):xs) | k `member` used = go used xs | otherwise = (k,v) : go (insertSet k used) xs instance (Arbitrary k, Arbitrary v, Ord k, Eq v) => Arbitrary (DuplPairs k v) where arbitrary = DuplPairs . removeDuplicateKeys <$> arbitrary `suchThat` (/= []) shrink (DuplPairs xs) = map (DuplPairs . removeDuplicateKeys) $ filter (/= []) $ shrink xs -- | Arbitrary newtype for small lists whose length is <= 10 -- -- Used for testing 'unionsWith' newtype SmallList a = SmallList { getSmallList :: [a] } deriving (Eq, Show, Ord) instance (Arbitrary a) => Arbitrary (SmallList a) where arbitrary = SmallList <$> arbitrary `suchThat` ((<= 10) . olength) shrink (SmallList xs) = map SmallList $ filter ((<= 10) . olength) $ shrink xs -- | Choose a random key from a key-value pair list indexIn :: (Show k, Testable prop) => [(k,v)] -> (k -> prop) -> Property indexIn = forAll . elements . map Prelude.fst -- | Type restricted 'fromList' fromListAs :: IsSequence a => [Element a] -> a -> a fromListAs xs _ = fromList xs -- | Type restricted 'mapFromListAs' mapFromListAs :: IsMap a => [(ContainerKey a, MapValue a)] -> a -> a mapFromListAs xs _ = mapFromList xs main :: IO () main = hspec $ do describe "onull" $ do it "works on empty lists" $ onull [] @?= True it "works on non-empty lists" $ onull [()] @?= False it "works on empty texts" $ onull ("" :: Text) @?= True it "works on non-empty texts" $ onull ("foo" :: Text) @?= False describe "osum" $ do prop "works on lists" $ \(Small x) (Small y) -> y >= x ==> osum [x..y] @?= ((x + y) * (y - x + 1) `div` (2 :: Int)) describe "oproduct" $ do prop "works on lists" $ \(Positive x) (Positive y) -> let fact n = oproduct [1..n] in (y :: Integer) > (x :: Integer) ==> oproduct [x..y] @?= fact y `div` fact (x - 1) describe "olength" $ do prop "works on lists" $ \(NonNegative i) -> olength (replicate i () :: [()]) @?= i prop "works on texts" $ \(NonNegative i) -> olength (replicate i 'a' :: Text) @?= i prop "works on lazy bytestrings" $ \(NonNegative (Small i)) -> olength64 (replicate i 6 :: L.ByteString) @?= i describe "omap" $ do prop "works on lists" $ \xs -> omap (+1) xs @?= map (+1) (xs :: [Int]) prop "works on lazy bytestrings" $ \xs -> omap (+1) (fromList xs :: L.ByteString) @?= fromList (map (+1) xs) prop "works on texts" $ \xs -> omap succ (fromList xs :: Text) @?= fromList (map succ xs) describe "oconcatMap" $ do prop "works on lists" $ \xs -> oconcatMap (: []) xs @?= (xs :: [Int]) describe "ocompareLength" $ do prop "works on lists" $ \(Positive i) j -> ocompareLength (replicate i () :: [()]) j @?= compare i j describe "groupAll" $ do it "works on lists" $ groupAll ("abcabcabc" :: String) @?= ["aaa", "bbb", "ccc"] it "works on texts" $ groupAll ("abcabcabc" :: Text) @?= ["aaa", "bbb", "ccc"] describe "unsnoc" $ do let test name dummy = prop name $ \(QCM.NonEmpty xs) -> let seq' = fromListAs xs dummy in case unsnoc seq' of Just (y, z) -> do y SG.<> singleton z @?= seq' snoc y z @?= seq' otoList (snoc y z) @?= xs Nothing -> expectationFailure "unsnoc returned Nothing" test "works on lists" ([] :: [Int]) test "works on texts" ("" :: Text) test "works on lazy bytestrings" L.empty describe "index" $ do let test name dummy = prop name $ \i' (QCM.NonEmpty xs) -> let seq' = fromListAs xs dummy mx = index xs (fromIntegral i) i = fromIntegral (i' :: Int) in do mx @?= index seq' i case mx of Nothing -> return () Just x -> indexEx seq' i @?= x test "works on lists" ([] :: [Int]) test "works on strict texts" ("" :: Text) test "works on lazy texts" ("" :: TL.Text) test "works on strict bytestrings" S.empty test "works on lazy bytestrings" L.empty test "works on Vector" (V.singleton (1 :: Int)) test "works on SVector" (VS.singleton (1 :: Int)) test "works on UVector" (U.singleton (1 :: Int)) test "works on Seq" (Seq.fromList [1 :: Int]) describe "groupAllOn" $ do it "works on lists" $ groupAllOn (`mod` 3) ([1..9] :: [Int]) @?= [[1, 4, 7], [2, 5, 8], [3, 6, 9]] describe "breakWord" $ do let test x y z = it (show (x, y, z)) $ breakWord (x :: Text) @?= (y, z) test "hello world" "hello" "world" test "hello world" "hello" "world" test "hello\r\nworld" "hello" "world" test "hello there world" "hello" "there world" test "" "" "" test "hello \n\r\t" "hello" "" describe "breakLine" $ do let test x y z = it (show (x, y, z)) $ breakLine (x :: Text) @?= (y, z) test "hello world" "hello world" "" test "hello\r\n world" "hello" " world" test "hello\n world" "hello" " world" test "hello\r world" "hello\r world" "" test "hello\r\nworld" "hello" "world" test "hello\r\nthere\nworld" "hello" "there\nworld" test "hello\n\r\nworld" "hello" "\r\nworld" test "" "" "" describe "omapM_" $ do let test typ dummy = prop typ $ \input -> input @?= execWriter (omapM_ (tell . return) (fromListAs input dummy)) test "works on strict bytestrings" S.empty test "works on lazy bytestrings" L.empty test "works on strict texts" T.empty test "works on lazy texts" TL.empty describe "NonNull" $ do describe "fromNonEmpty" $ do prop "toMinList" $ \(NonEmpty' ne) -> (NE.toList ne :: [Int]) @?= NN.toNullable (NN.toMinList ne) describe "mapNonNull" $ do prop "mapNonNull id == id" $ \x xs -> let nonNull = NN.ncons x (xs :: [Int]) in NN.mapNonNull Prelude.id nonNull @?= nonNull prop "mapNonNull (f . g) == mapNonNull f . mapNonNull g" $ \(Fn (f :: Integer -> String)) (Fn (g :: Int -> Integer)) x xs -> let nns = NN.ncons x (xs :: [Int]) in NN.mapNonNull (f . g) nns @?= NN.mapNonNull f (NN.mapNonNull g nns) let -- | Type restricted 'NN.ncons' nconsAs :: IsSequence seq => Element seq -> [Element seq] -> seq -> NN.NonNull seq nconsAs x xs _ = NN.ncons x (fromList xs) test :: (IsSequence typ, Ord (Element typ), Arbitrary (Element typ), Show (Element typ), Show typ, Eq typ, Eq (Element typ)) => String -> typ -> Spec test typ du = describe typ $ do prop "head" $ \x xs -> NN.head (nconsAs x xs du) @?= x prop "tail" $ \x xs -> NN.tail (nconsAs x xs du) @?= fromList xs prop "last" $ \x xs -> NN.last (reverse $ nconsAs x xs du) @?= x prop "init" $ \x xs -> NN.init (reverse $ nconsAs x xs du) @?= reverse (fromList xs) prop "maximum" $ \x xs -> NN.maximum (nconsAs x xs du) @?= Prelude.maximum (x:xs) prop "maximumBy" $ \x xs -> NN.maximumBy compare (nconsAs x xs du) @?= Prelude.maximum (x:xs) prop "minimum" $ \x xs -> NN.minimum (nconsAs x xs du) @?= Prelude.minimum (x:xs) prop "minimumBy" $ \x xs -> NN.minimumBy compare (nconsAs x xs du) @?= Prelude.minimum (x:xs) prop "ofoldMap1" $ \x xs -> SG.getMax (NN.ofoldMap1 SG.Max $ nconsAs x xs du) @?= Prelude.maximum (x:xs) prop "ofoldr1" $ \x xs -> NN.ofoldr1 Prelude.min (nconsAs x xs du) @?= Prelude.minimum (x:xs) prop "ofoldl1'" $ \x xs -> NN.ofoldl1' Prelude.min (nconsAs x xs du) @?= Prelude.minimum (x:xs) test "Strict ByteString" S.empty test "Lazy ByteString" L.empty test "Strict Text" T.empty test "Lazy Text" TL.empty test "Vector" (V.empty :: V.Vector Int) test "Unboxed Vector" (U.empty :: U.Vector Int) test "Storable Vector" (VS.empty :: VS.Vector Int) test "List" ([5 :: Int]) describe "Containers" $ do let test typ dummy xlookup xinsert xdelete = describe typ $ do prop "difference" $ \(DuplPairs xs) (DuplPairs ys) -> let m1 = mapFromList xs `difference` mapFromList ys m2 = mapFromListAs (xs `difference` ys) dummy in m1 @?= m2 prop "lookup" $ \(DuplPairs xs) -> indexIn xs $ \k -> let m = mapFromListAs xs dummy v1 = lookup k m in do v1 @?= lookup k xs v1 @?= xlookup k m prop "insert" $ \(DuplPairs xs) v -> indexIn xs $ \k -> let m = mapFromListAs xs dummy m1 = insertMap k v m in do m1 @?= mapFromList (insertMap k v xs) m1 @?= xinsert k v m prop "delete" $ \(DuplPairs xs) -> indexIn xs $ \k -> let m = mapFromListAs xs dummy m1 = deleteMap k m in do m1 @?= mapFromList (deleteMap k xs) m1 @?= xdelete k m prop "singletonMap" $ \k v -> singletonMap k v @?= (mapFromListAs [(k, v)] dummy) prop "findWithDefault" $ \(DuplPairs xs) k v -> findWithDefault v k (mapFromListAs xs dummy) @?= findWithDefault v k xs prop "insertWith" $ \(DuplPairs xs) k v -> insertWith (+) k v (mapFromListAs xs dummy) @?= mapFromList (insertWith (+) k v xs) prop "insertWithKey" $ \(DuplPairs xs) k v -> let m = mapFromListAs xs dummy f x y z = x + y + z in insertWithKey f k v m @?= mapFromList (insertWithKey f k v xs) prop "insertLookupWithKey" $ \(DuplPairs xs) k v -> let m = mapFromListAs xs dummy f x y z = x + y + z in insertLookupWithKey f k v m @?= second mapFromList (insertLookupWithKey f k v xs) prop "adjustMap" $ \(DuplPairs xs) k -> adjustMap succ k (mapFromListAs xs dummy) @?= mapFromList (adjustMap succ k xs) prop "adjustWithKey" $ \(DuplPairs xs) k -> adjustWithKey (+) k (mapFromListAs xs dummy) @?= mapFromList (adjustWithKey (+) k xs) prop "updateMap" $ \(DuplPairs xs) k -> let f i = if i < 0 then Nothing else Just $ i * 2 in updateMap f k (mapFromListAs xs dummy) @?= mapFromList (updateMap f k xs) prop "updateWithKey" $ \(DuplPairs xs) k' -> let f k i = if i < 0 then Nothing else Just $ i * k in updateWithKey f k' (mapFromListAs xs dummy) @?= mapFromList (updateWithKey f k' xs) prop "updateLookupWithKey" $ \(DuplPairs xs) k' -> let f k i = if i < 0 then Nothing else Just $ i * k in updateLookupWithKey f k' (mapFromListAs xs dummy) @?= second mapFromList (updateLookupWithKey f k' xs) prop "alter" $ \(DuplPairs xs) k -> let m = mapFromListAs xs dummy f Nothing = Just (-1) f (Just i) = if i < 0 then Nothing else Just (i * 2) in lookup k (alterMap f k m) @?= f (lookup k m) prop "unionWith" $ \(DuplPairs xs) (DuplPairs ys) -> let m1 = unionWith (+) (mapFromListAs xs dummy) (mapFromListAs ys dummy) m2 = mapFromList (unionWith (+) xs ys) in m1 @?= m2 prop "unionWithKey" $ \(DuplPairs xs) (DuplPairs ys) -> let f k x y = k + x + y m1 = unionWithKey f (mapFromListAs xs dummy) (mapFromListAs ys dummy) m2 = mapFromList (unionWithKey f xs ys) in m1 @?= m2 prop "unionsWith" $ \(SmallList xss) -> let duplXss = map unDupl xss ms = map mapFromList duplXss `asTypeOf` [dummy] in unionsWith (+) ms @?= mapFromList (unionsWith (+) duplXss) prop "mapWithKey" $ \(DuplPairs xs) -> let m1 = mapWithKey (+) (mapFromList xs) `asTypeOf` dummy m2 = mapFromList $ mapWithKey (+) xs in m1 @?= m2 prop "omapKeysWith" $ \(DuplPairs xs) -> let f = flip mod 5 m1 = omapKeysWith (+) f (mapFromList xs) `asTypeOf` dummy m2 = mapFromList $ omapKeysWith (+) f xs in m1 @?= m2 test "Data.Map" (Map.empty :: Map.Map Int Int) Map.lookup Map.insert Map.delete test "Data.IntMap" (IntMap.empty :: IntMap.IntMap Int) IntMap.lookup IntMap.insert IntMap.delete test "Data.HashMap" (HashMap.empty :: HashMap.HashMap Int Int) HashMap.lookup HashMap.insert HashMap.delete describe "Foldl Integration" $ do prop "vector" $ \xs -> do #if MIN_VERSION_foldl(1,3,0) let x1 = Foldl.fold Foldl.vector (xs :: [Int]) x2 = Foldl.purely ofoldlUnwrap Foldl.vector xs #else x1 <- Foldl.foldM Foldl.vector (xs :: [Int]) x2 <- Foldl.impurely ofoldMUnwrap Foldl.vector xs #endif x2 @?= (x1 :: V.Vector Int) prop "length" $ \xs -> do let x1 = Foldl.fold Foldl.length (xs :: [Int]) x2 = Foldl.purely ofoldlUnwrap Foldl.length xs x2 @?= x1 describe "Replacing" $ do let test typ dummy = describe typ $ do prop "replaceElem old new === omap (\\x -> if x == old then new else x)" $ -- replace random element or any random value with random new value \x list new -> forAll (elements (x:list)) $ \old -> let seq' = fromListAs list dummy in replaceElem old new seq' @?= omap (\x' -> if x' == old then new else x') seq' #if MIN_VERSION_QuickCheck(2,8,0) prop "replaceSeq old new === ointercalate new . splitSeq old" $ -- replace random subsequence with random new sequence \list new -> forAll (sublistOf list) $ \old -> let [seq', old', new'] = map (`fromListAs` dummy) [list, old, new] in replaceSeq old' new' seq' @?= ointercalate new' (splitSeq old' seq') prop "replaceSeq old old === id" $ \list -> forAll (sublistOf list) $ \old -> let [seq', old'] = map (`fromListAs` dummy) [list, old] in replaceSeq old' old' seq' @?= seq' #endif test "List" ([] :: [Int]) test "Vector" (V.empty :: V.Vector Int) test "Storable Vector" (VS.empty :: VS.Vector Int) test "Unboxed Vector" (U.empty :: U.Vector Int) test "Strict ByteString" S.empty test "Lazy ByteString" L.empty test "Strict Text" T.empty test "Lazy Text" TL.empty describe "Sorting" $ do let test typ dummy = describe typ $ do prop "sortBy" $ \input -> do let f x y = compare y x fromList (sortBy f input) @?= sortBy f (fromListAs input dummy) prop "sort" $ \input -> fromList (sort input) @?= sort (fromListAs input dummy) test "List" ([] :: [Int]) test "Vector" (V.empty :: V.Vector Int) test "Storable Vector" (VS.empty :: VS.Vector Int) test "Unboxed Vector" (U.empty :: U.Vector Int) test "Strict ByteString" S.empty test "Lazy ByteString" L.empty test "Strict Text" T.empty test "Lazy Text" TL.empty describe "Intercalate" $ do let test typ dummy = describe typ $ do prop "intercalate === defaultIntercalate" $ \list lists -> let seq' = fromListAs list dummy seqs = map (`fromListAs` dummy) lists in ointercalate seq' seqs @?= fromList (List.intercalate list lists) test "List" ([] :: [Int]) test "Vector" (V.empty :: V.Vector Int) test "Storable Vector" (VS.empty :: VS.Vector Int) test "Unboxed Vector" (U.empty :: U.Vector Int) test "Strict ByteString" S.empty test "Lazy ByteString" L.empty test "Strict Text" T.empty test "Lazy Text" TL.empty describe "Splitting" $ do let test typ dummy = describe typ $ do let fromList' = (`fromListAs` dummy) let fromSepList sep = fromList' . map (fromMaybe sep) prop "intercalate sep . splitSeq sep === id" $ \(fromList' -> sep) -> \(mconcat . map (maybe sep fromList') -> xs) -> ointercalate sep (splitSeq sep xs) @?= xs prop "splitSeq mempty xs === mempty : map singleton (otoList xs)" $ \input -> splitSeq mempty (fromList' input) @?= mempty : map singleton input prop "splitSeq _ mempty == [mempty]" $ \(fromList' -> sep) -> splitSeq sep mempty @?= [mempty] prop "intercalate (singleton sep) . splitElem sep === id" $ \sep -> \(fromSepList sep -> xs) -> ointercalate (singleton sep) (splitElem sep xs) @?= xs prop "length . splitElem sep === succ . length . filter (== sep)" $ \sep -> \(fromSepList sep -> xs) -> olength (splitElem sep xs) @?= olength (filter (== sep) xs) + 1 prop "splitElem sep (replicate n sep) == replicate (n+1) mempty" $ \(NonNegative n) sep -> splitElem sep (fromList' (replicate n sep)) @?= replicate (n + 1) mempty prop "splitElem sep === splitWhen (== sep)" $ \sep -> \(fromSepList sep -> xs) -> splitElem sep xs @?= splitWhen (== sep) xs prop "splitElem sep === splitSeq (singleton sep)" $ \sep -> \(fromSepList sep -> xs) -> splitElem sep xs @?= splitSeq (singleton sep) xs test "List" ([] :: [Int]) test "Vector" (V.empty :: V.Vector Int) test "Storable Vector" (VS.empty :: VS.Vector Int) test "Unboxed Vector" (U.empty :: U.Vector Int) test "Strict ByteString" S.empty test "Lazy ByteString" L.empty test "Strict Text" T.empty test "Lazy Text" TL.empty describe "Other Issues" $ do it "#26 headEx on a list works" $ headEx (1 : filter Prelude.odd [2,4..]) @?= (1 :: Int) it "#31 find doesn't infinitely loop on NonEmpty" $ find (== "a") ("a" NE.:| ["d","fgf"]) @?= Just ("a" :: String) it "#83 head on Seq works correctly" $ do headEx (Seq.fromList [1 :: Int,2,3]) @?= (1 :: Int) headMay (Seq.fromList [] :: Seq.Seq Int) @?= Nothing