packages feed

genvalidity-hspec-hashable-1.0.0.0: src/Test/Validity/Hashable.hs

{-# 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