packages feed

unordered-containers-0.1.3.0: tests/SetProperties.hs

 {-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- | Tests for the 'Data.HashSet' module.  We test functions by
-- comparing them to a simpler model, a list.

module Main (main) where

import qualified Data.Foldable as Foldable
import Data.Hashable (Hashable(hash))
import qualified Data.List as L
import qualified Data.HashSet as S
import qualified Data.Set as Set
import Test.QuickCheck (Arbitrary)
import Test.Framework (Test, defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)

-- Key type that generates more hash collisions.
newtype Key = K { unK :: Int }
            deriving (Arbitrary, Eq, Ord, Show)

instance Hashable Key where
    hash k = hash (unK k) `mod` 20

------------------------------------------------------------------------
-- * Properties

------------------------------------------------------------------------
-- ** Instances

pEq :: [Key] -> [Key] -> Bool
pEq xs = (unique xs ==) `eq` (S.fromList xs ==)

pNeq :: [Key] -> [Key] -> Bool
pNeq xs = (unique xs /=) `eq` (S.fromList xs /=)

pFoldable :: [Int] -> Bool
pFoldable = (L.sort . Foldable.foldr (:) []) `eq`
            (L.sort . Foldable.foldr (:) [])

------------------------------------------------------------------------
-- ** Basic interface

pSize :: [Key] -> Bool
pSize = length `eq` S.size

pMember :: Key -> [Key] -> Bool
pMember k = L.elem k `eq` S.member k

pInsert :: Key -> [Key] -> Bool
pInsert a = insert a `eq` (toAscList . S.insert a)

pDelete :: Key -> [Key] -> Bool
pDelete a = delete a `eq` (toAscList . S.delete a)

------------------------------------------------------------------------
-- ** Combine

pUnion :: [Key] -> [Key] -> Bool
pUnion xs ys = L.sort (L.union as bs) ==
               toAscList (S.union (S.fromList as) (S.fromList bs))
  where
    as = fromList xs
    bs = fromList ys

------------------------------------------------------------------------
-- ** Transformations

pMap :: [Key] -> Bool
pMap = map f `eq` (toAscList . S.map f)
  where f (K k) = K (k + 1)

------------------------------------------------------------------------
-- ** Folds

pFoldr :: [Int] -> Bool
pFoldr = (L.sort . L.foldr (:) []) `eq`
         (L.sort . S.foldr (:) [])

pFoldl' :: Int -> [Int] -> Bool
pFoldl' z0 = L.foldl' (+) z0 `eq` S.foldl' (+) z0

------------------------------------------------------------------------
-- ** Conversions

pToList :: [Key] -> Bool
pToList = id `eq` toAscList

------------------------------------------------------------------------
-- * Test list

tests :: [Test]
tests =
    [
    -- Instances
      testGroup "instances"
      [ testProperty "==" pEq
      , testProperty "/=" pNeq
      , testProperty "Foldable" pFoldable
      ]
    -- Basic interface
    , testGroup "basic interface"
      [ testProperty "size" pSize
      , testProperty "member" pMember
      , testProperty "insert" pInsert
      , testProperty "delete" pDelete
      ]
    -- Combine
    , testProperty "union" pUnion
    -- Transformations
    , testProperty "map" pMap
    -- Folds
    , testGroup "folds"
      [ testProperty "foldr" pFoldr
      , testProperty "foldl'" pFoldl'
      ]
    -- Conversions
    , testGroup "conversions"
      [ testProperty "toList" pToList
      ]
    ]

------------------------------------------------------------------------
-- * Model

-- Invariant: the list is sorted in ascending order, by key.
type Model a = [a]

-- | Check that a function operating on a 'HashMap' is equivalent to
-- one operating on a 'Model'.
eq :: (Eq a, Hashable a, Ord a, Eq b)
   => (Model a -> b)      -- ^ Function that modifies a 'Model' in the same
                          -- way
   -> (S.HashSet a -> b)  -- ^ Function that modified a 'HashSet'
   -> [a]                 -- ^ Initial content of the 'HashSet' and 'Model'
   -> Bool                -- ^ True if the functions are equivalent
eq f g xs = g (S.fromList ys) == f ys
  where ys = fromList xs

insert :: Ord a => a -> Model a -> Model a
insert x [] = [x]
insert x (y:xs)
    | x == y    = x : xs
    | x > y     = y : insert x xs
    | otherwise = x : y : xs

delete :: Ord a => a -> Model a -> Model a
delete _ [] = []
delete k ys@(y:xs)
    | k == y   = xs
    | k > y    = y : delete k xs
    | otherwise = ys

-- | Create a model from a list of key-value pairs.  If the input
-- contains multiple entries for the same key, the latter one is used.
fromList :: Ord a => [a] -> Model a
fromList = L.foldl' (\ m p -> insert p m) []

------------------------------------------------------------------------
-- * Test harness

main :: IO ()
main = defaultMain tests

------------------------------------------------------------------------
-- * Helpers

toAscList :: Ord a => S.HashSet a -> [a]
toAscList = L.sort . S.toList

unique :: (Eq a, Ord a) => [a] -> [a]
unique = Set.toList . Set.fromList