{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | Hashable properties
--
-- You will need @TypeApplications@ to use these.
module Test.Validity.Hashable
( hashableSpec,
hashableSpecOnArbitrary,
hashableSpecOnGen,
)
where
import Control.Monad
import Data.Data
import Data.GenValidity
import Data.Hashable
import Test.Hspec
import Test.QuickCheck
import Test.Validity.Property.Utils
import Test.Validity.Utils
-- | Standard test spec for properties of Hashable instances for valid values
--
-- Example usage:
--
-- > hashableSpec @Int
hashableSpec ::
forall a.
(Show a, Eq a, Typeable a, GenValid a, Hashable a) =>
Spec
hashableSpec = hashableSpecOnGen @a genValid "valid" shrinkValid
-- | Standard test spec for properties of Hashable instances for arbitrary values
--
-- Example usage:
--
-- > hashableSpecOnArbitrary @Int
hashableSpecOnArbitrary ::
forall a.
(Show a, Eq a, Typeable a, Arbitrary a, Hashable a) =>
Spec
hashableSpecOnArbitrary = hashableSpecOnGen @a arbitrary "arbitrary" shrink
-- | Standard test spec for properties of Hashable instances for values generated by a given generator (and name for that generator).
--
-- Example usage:
--
-- > hashableSpecOnGen ((* 2) <$> genValid @Int) "even"
hashableSpecOnGen ::
forall a.
(Show a, Eq a, Typeable a, Hashable a) =>
Gen a ->
String ->
(a -> [a]) ->
Spec
hashableSpecOnGen gen = checkGen $ (,) <$> gen <*> gen
-- | Test spec like hashableSpecOnGen but with a special generator
-- | which is documented to generate equal values by (==) most of the time.
checkGen ::
forall a.
(Show a, Eq a, Typeable a, Hashable a) =>
Gen (a, a) ->
String ->
(a -> [a]) ->
Spec
checkGen gen genname s =
parallel $ do
let name = nameOf @a
hashablestr = unwords ["hashWithSalt :: Int ->", name, "-> Int"]
describe ("Hashable " ++ name) $
describe hashablestr $
it
( unwords
[ "satisfies (a == b) => (hashWithSalt n a) ==",
"(hashWithSalt n b), for every n and for",
genname,
name
]
)
$ let ss (a, b) = (,) <$> s a <*> s b
in forAllShrink gen ss $ \(a1, a2) ->
forAllValid $ \int ->
when (a1 == a2) $
let h = hashWithSalt int
in h a1 `shouldBe` h a2