module Main (main) where
import Prelude ()
import Prelude.Compat
import Data.Function (on)
import Data.Hashable (Hashable (..))
import Data.List (nubBy)
import Data.Semigroup ((<>))
import Data.Word (Word8)
import Text.Read (readMaybe)
import qualified Data.Aeson as Aeson
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashMap.Strict.InsOrd as InsOrd
import Test.QuickCheck.Function
import Test.Tasty
import Test.Tasty.QuickCheck
main :: IO ()
main = defaultMain $ testGroup "Properties" $
[ testProperty "toList . fromList ~= id" $ toListFromList
, testProperty "toList distributes over mappend" $ toListMappendDistribute
, testProperty "behaves like HashMap" $ operationModel
, testProperty "valid" $ validProperty
, testProperty "Hashable agree" $ hashableProperty
, testProperty "aeson roundtrip" $ aesonRoundtrip
, testProperty "show . read = id" showReadRoundtrip
]
toListFromList :: [(Int, Int)] -> Property
toListFromList l = l' === InsOrd.toList (InsOrd.fromList l)
where l' = reverse . nubBy (on (==) fst) . reverse $ l
toListMappendDistribute :: [(Int, Int)] -> [(Int, Int)] -> Property
toListMappendDistribute a b = rhs === lhs
where
a' = InsOrd.fromList a
b' = foldr InsOrd.delete (InsOrd.fromList b) (InsOrd.keys a')
rhs = InsOrd.toList (a' <> b')
lhs = InsOrd.toList a' <> InsOrd.toList b'
-------------------------------------------------------------------------------
-- Model
-------------------------------------------------------------------------------
data Operation k v
= FromList [(k, v)]
| Empty
| Singleton k v
| Insert k v (Operation k v)
| Delete k (Operation k v)
| Union (Operation k v) (Operation k v)
| Difference (Operation k v) (Operation k v)
| Intersection (Operation k v) (Operation k v)
| Filter (Fun v Bool) (Operation k v)
deriving (Show)
instance (Arbitrary k, Arbitrary v, Function v, CoArbitrary v) => Arbitrary (Operation k v) where
arbitrary = sized a
where
term =
[ FromList <$> arbitrary
, pure Empty
, Singleton <$> arbitrary <*> arbitrary
]
a 0 = oneof term
a n = oneof $ term ++
[ Insert <$> arbitrary <*> arbitrary <*> aMinus1
, Delete <$> arbitrary <*> aMinus1
, Union <$> aDiv2 <*> aDiv2
, Difference <$> aDiv2 <*> aDiv2
, Intersection <$> aDiv2 <*> aDiv2
, Filter <$> arbitrary <*> aMinus1
]
where
aMinus1 = a (n - 1)
aDiv2 = a (n `div` 2)
evalOpInsOrd
:: (Eq k, Hashable k)
=> Operation k v -> InsOrd.InsOrdHashMap k v
evalOpInsOrd op = case op of
FromList l -> InsOrd.fromList l
Empty -> InsOrd.empty
Singleton k v -> InsOrd.singleton k v
Insert k v a -> InsOrd.insert k v (evalOpInsOrd a)
Delete k a -> InsOrd.delete k (evalOpInsOrd a)
Union a b -> InsOrd.union (evalOpInsOrd a) (evalOpInsOrd b)
Difference a b -> InsOrd.difference (evalOpInsOrd a) (evalOpInsOrd b)
Intersection a b -> InsOrd.intersection (evalOpInsOrd a) (evalOpInsOrd b)
Filter (Fun _ f) a -> InsOrd.filter f (evalOpInsOrd a)
evalOpHashMap
:: (Eq k, Hashable k)
=> Operation k v-> HashMap.HashMap k v
evalOpHashMap op = case op of
FromList l -> HashMap.fromList l
Empty -> HashMap.empty
Singleton k v -> HashMap.singleton k v
Insert k v a -> HashMap.insert k v (evalOpHashMap a)
Delete k a -> HashMap.delete k (evalOpHashMap a)
Union a b -> HashMap.union (evalOpHashMap a) (evalOpHashMap b)
Difference a b -> HashMap.difference (evalOpHashMap a) (evalOpHashMap b)
Intersection a b -> HashMap.intersection (evalOpHashMap a) (evalOpHashMap b)
Filter (Fun _ f) a -> HashMap.filter f (evalOpHashMap a)
operationModel :: Operation Word8 Int -> Property
operationModel op = rhs === lhs
where
iom = evalOpInsOrd op
lhs = InsOrd.toHashMap iom
rhs = evalOpHashMap op
validProperty :: Operation Word8 Int -> Property
validProperty op = property $ InsOrd.valid iom
where
iom = evalOpInsOrd op
hashableProperty :: Operation Word8 Int -> Int -> Property
hashableProperty op salt = rhs === lhs
where
iom = evalOpInsOrd op
lhs = hashWithSalt salt $ iom
rhs = hashWithSalt salt $ evalOpHashMap op
aesonRoundtrip :: Operation Int Int -> Property
aesonRoundtrip op = rhs === lhs
where
iom = evalOpInsOrd op
rhs = Right iom
lhs = Aeson.eitherDecode $ Aeson.encode iom
showReadRoundtrip :: Operation Word8 Int -> Property
showReadRoundtrip op = rhs === lhs
where
iom = evalOpInsOrd op
rhs = Just iom
lhs = readMaybe $ show iom