packages feed

insert-ordered-containers-0.1.0.0: test/Tests.hs

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