{-# LANGUAGE CPP #-}
#ifndef NO_SAFE_HASKELL
#if !defined(NO_ST_MONAD) && !(MIN_VERSION_base(4,8,0))
{-# LANGUAGE Trustworthy #-}
#else
{-# LANGUAGE Safe #-}
#endif
#endif
#ifndef NO_ST_MONAD
{-# LANGUAGE Rank2Types #-}
#endif
{-|
Module   : Test.QuickCheck.Monadic

Allows testing of monadic values. Will generally follow this form:

@
prop_monadic a b = 'monadicIO' $ do
  a\' \<- 'run' (f a)
  b\' \<- 'run' (f b)
  -- ...
  'assert' someBoolean
@

Example using the @FACTOR(1)@ command-line utility:

@
import System.Process
import Test.QuickCheck
import Test.QuickCheck.Monadic

-- $ factor 16
-- 16: 2 2 2 2
factor :: Integer -> IO [Integer]
factor n = parse \`fmap\` 'System.Process.readProcess' \"factor\" [show n] \"\" where

  parse :: String -> [Integer]
  parse = map read . tail . words

prop_factor :: Positive Integer -> Property
prop_factor ('Test.QuickCheck.Modifiers.Positive' n) = 'monadicIO' $ do
  factors \<- 'run' (factor n)

  'assert' (product factors == n)
@

>>> quickCheck prop_factor
+++ OK, passed 100 tests.

See the paper \"<http://www.cse.chalmers.se/~rjmh/Papers/QuickCheckST.ps Testing Monadic Code with QuickCheck>\".
-}
module Test.QuickCheck.Monadic (
  -- * Property monad
    PropertyM(..)

  -- * Monadic specification combinators
  , run
  , assert
  , assertWith
  , pre
  , wp
  , pick
  , forAllM
  , monitor
  , stop

  -- * Run functions
  , monadic
  , monadic'
  , monadicIO
#ifndef NO_ST_MONAD
  , monadicST
  , runSTGen
#endif

  -- * Exceptions

#ifndef NO_EXCEPTIONS
  , assertException
  , assertExceptionIO
#ifndef NO_DEEPSEQ
  , assertDeepException
  , assertDeepExceptionIO
#endif
#endif
  ) where

--------------------------------------------------------------------------
-- imports

import Test.QuickCheck.Gen
import Test.QuickCheck.Gen.Unsafe
import Test.QuickCheck.Property

import Control.Monad(liftM, liftM2)

import Control.Monad.ST
import Control.Applicative

#ifndef NO_TRANSFORMERS
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
#endif

#ifndef NO_MONADFAIL
import qualified Control.Monad.Fail as Fail
#endif

#ifndef NO_DEEPSEQ
import Control.DeepSeq
#endif

#ifndef NO_EXCEPTIONS
import qualified Control.Exception as E
#endif
--------------------------------------------------------------------------
-- type PropertyM

-- | The property monad is really a monad transformer that can contain
-- monadic computations in the monad @m@ it is parameterized by:
--
--   * @m@ - the @m@-computations that may be performed within @PropertyM@
--
-- Elements of @PropertyM m a@ may mix property operations and @m@-computations.
newtype PropertyM m a =
  MkPropertyM { forall (m :: * -> *) a.
PropertyM m a -> (a -> Gen (m Property)) -> Gen (m Property)
unPropertyM :: (a -> Gen (m Property)) -> Gen (m Property) }

bind :: PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
MkPropertyM (a -> Gen (m Property)) -> Gen (m Property)
m bind :: forall (m :: * -> *) a b.
PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
`bind` a -> PropertyM m b
f = ((b -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m b
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (\b -> Gen (m Property)
k -> (a -> Gen (m Property)) -> Gen (m Property)
m (\a
a -> PropertyM m b -> (b -> Gen (m Property)) -> Gen (m Property)
forall (m :: * -> *) a.
PropertyM m a -> (a -> Gen (m Property)) -> Gen (m Property)
unPropertyM (a -> PropertyM m b
f a
a) b -> Gen (m Property)
k))

fail_ :: Monad m => String -> PropertyM m a
fail_ :: forall (m :: * -> *) a. Monad m => String -> PropertyM m a
fail_ String
s = Result -> PropertyM m a
forall prop (m :: * -> *) a.
(Testable prop, Monad m) =>
prop -> PropertyM m a
stop (Result
failed { reason = s })

instance Functor (PropertyM m) where
  fmap :: forall a b. (a -> b) -> PropertyM m a -> PropertyM m b
fmap a -> b
f (MkPropertyM (a -> Gen (m Property)) -> Gen (m Property)
m) = ((b -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m b
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (\b -> Gen (m Property)
k -> (a -> Gen (m Property)) -> Gen (m Property)
m (b -> Gen (m Property)
k (b -> Gen (m Property)) -> (a -> b) -> a -> Gen (m Property)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f))

instance Applicative (PropertyM m) where
  pure :: forall a. a -> PropertyM m a
pure a
x = ((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (\a -> Gen (m Property)
k -> a -> Gen (m Property)
k a
x)
  PropertyM m (a -> b)
mf <*> :: forall a b. PropertyM m (a -> b) -> PropertyM m a -> PropertyM m b
<*> PropertyM m a
mx =
    PropertyM m (a -> b)
mf PropertyM m (a -> b)
-> ((a -> b) -> PropertyM m b) -> PropertyM m b
forall (m :: * -> *) a b.
PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
`bind` \a -> b
f -> PropertyM m a
mx PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
forall (m :: * -> *) a b.
PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
`bind` \a
x -> b -> PropertyM m b
forall a. a -> PropertyM m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (a -> b
f a
x)

instance Monad m => Monad (PropertyM m) where
  return :: forall a. a -> PropertyM m a
return = a -> PropertyM m a
forall a. a -> PropertyM m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
  >>= :: forall a b. PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
(>>=) = PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
forall (m :: * -> *) a b.
PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
bind
#if !MIN_VERSION_base(4,13,0)
  fail = fail_
#endif

#ifndef NO_MONADFAIL
instance Monad m => Fail.MonadFail (PropertyM m) where
  fail :: forall a. String -> PropertyM m a
fail = String -> PropertyM m a
forall (m :: * -> *) a. Monad m => String -> PropertyM m a
fail_
#endif

#ifndef NO_TRANSFORMERS
instance MonadTrans PropertyM where
  lift :: forall (m :: * -> *) a. Monad m => m a -> PropertyM m a
lift = m a -> PropertyM m a
forall (m :: * -> *) a. Monad m => m a -> PropertyM m a
run

instance MonadIO m => MonadIO (PropertyM m) where
  liftIO :: forall a. IO a -> PropertyM m a
liftIO = m a -> PropertyM m a
forall (m :: * -> *) a. Monad m => m a -> PropertyM m a
run (m a -> PropertyM m a) -> (IO a -> m a) -> IO a -> PropertyM m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO a -> m a
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO
#endif

stop :: (Testable prop, Monad m) => prop -> PropertyM m a
stop :: forall prop (m :: * -> *) a.
(Testable prop, Monad m) =>
prop -> PropertyM m a
stop prop
p = ((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (\a -> Gen (m Property)
_k -> m Property -> Gen (m Property)
forall a. a -> Gen a
forall (m :: * -> *) a. Monad m => a -> m a
return (Property -> m Property
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (prop -> Property
forall prop. Testable prop => prop -> Property
property prop
p)))

-- should think about strictness/exceptions here
-- assert :: Testable prop => prop -> PropertyM m ()
-- | Allows embedding non-monadic properties into monadic ones.
assert :: Monad m => Bool -> PropertyM m ()
assert :: forall (m :: * -> *). Monad m => Bool -> PropertyM m ()
assert Bool
True  = () -> PropertyM m ()
forall a. a -> PropertyM m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
assert Bool
False = String -> PropertyM m ()
forall a. String -> PropertyM m a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Assertion failed"

-- | Like 'assert' but allows caller to specify an explicit message to show on failure.
--
-- __Example:__
--
-- @
-- do
--   assertWith True  "My first predicate."
--   assertWith False "My other predicate."
--   ...
-- @
--
-- @
-- Assertion failed (after 2 tests):
--     Passed: My first predicate
--     Failed: My other predicate
-- @
assertWith :: Monad m => Bool -> String -> PropertyM m ()
assertWith :: forall (m :: * -> *). Monad m => Bool -> String -> PropertyM m ()
assertWith Bool
condition String
msg = do
    let prefix :: String
prefix = if Bool
condition then String
"Passed: " else String
"Failed: "
    (Property -> Property) -> PropertyM m ()
forall (m :: * -> *).
Monad m =>
(Property -> Property) -> PropertyM m ()
monitor ((Property -> Property) -> PropertyM m ())
-> (Property -> Property) -> PropertyM m ()
forall a b. (a -> b) -> a -> b
$ String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
counterexample (String -> Property -> Property) -> String -> Property -> Property
forall a b. (a -> b) -> a -> b
$ String
prefix String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
msg
    Bool -> PropertyM m ()
forall (m :: * -> *). Monad m => Bool -> PropertyM m ()
assert Bool
condition

-- should think about strictness/exceptions here
-- | Tests preconditions. Unlike 'assert' this does not cause the
-- property to fail, rather it discards them just like using the
-- implication combinator 'Test.QuickCheck.Property.==>'.
--
-- This allows representing the <https://en.wikipedia.org/wiki/Hoare_logic Hoare triple>
--
-- > {p} x ← e{q}
--
-- as
--
-- @
-- pre p
-- x \<- run e
-- assert q
-- @
--
pre :: Monad m => Bool -> PropertyM m ()
pre :: forall (m :: * -> *). Monad m => Bool -> PropertyM m ()
pre Bool
True  = () -> PropertyM m ()
forall a. a -> PropertyM m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
pre Bool
False = Result -> PropertyM m ()
forall prop (m :: * -> *) a.
(Testable prop, Monad m) =>
prop -> PropertyM m a
stop Result
rejected

-- should be called lift?
-- | The lifting operation of the property monad. Allows embedding
-- monadic\/'IO'-actions in properties:
--
-- @
-- log :: Int -> IO ()
--
-- prop_foo n = monadicIO $ do
--   run (log n)
--   -- ...
-- @
run :: Monad m => m a -> PropertyM m a
run :: forall (m :: * -> *) a. Monad m => m a -> PropertyM m a
run m a
m = ((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (((a -> m Property) -> m Property)
-> Gen (a -> m Property) -> Gen (m Property)
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM (m a
m m a -> (a -> m Property) -> m Property
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=) (Gen (a -> m Property) -> Gen (m Property))
-> ((a -> Gen (m Property)) -> Gen (a -> m Property))
-> (a -> Gen (m Property))
-> Gen (m Property)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Gen (m Property)) -> Gen (a -> m Property)
forall (m :: * -> *) a. Monad m => m (Gen a) -> Gen (m a)
promote)

-- | Quantification in a monadic property, fits better with
-- /do-notation/ than 'forAllM'.
-- __Note__: values generated by 'pick' do not shrink.
pick :: (Monad m, Show a) => Gen a -> PropertyM m a
pick :: forall (m :: * -> *) a. (Monad m, Show a) => Gen a -> PropertyM m a
pick Gen a
gen = ((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a)
-> ((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
forall a b. (a -> b) -> a -> b
$ \a -> Gen (m Property)
k ->
  do a
a <- Gen a
gen
     m Property
mp <- a -> Gen (m Property)
k a
a
     m Property -> Gen (m Property)
forall a. a -> Gen a
forall (m :: * -> *) a. Monad m => a -> m a
return (do Property
p <- m Property
mp
                Property -> m Property
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Gen a -> (a -> Property) -> Property
forall a prop.
(Show a, Testable prop) =>
Gen a -> (a -> prop) -> Property
forAll (a -> Gen a
forall a. a -> Gen a
forall (m :: * -> *) a. Monad m => a -> m a
return a
a) (Property -> a -> Property
forall a b. a -> b -> a
const Property
p)))

-- | The <https://en.wikipedia.org/wiki/Predicate_transformer_semantics#Weakest_preconditions weakest precondition>
--
-- > wp(x ← e, p)
--
-- can be expressed as in code as @wp e (\\x -> p)@.
wp :: Monad m => m a -> (a -> PropertyM m b) -> PropertyM m b
wp :: forall (m :: * -> *) a b.
Monad m =>
m a -> (a -> PropertyM m b) -> PropertyM m b
wp m a
m a -> PropertyM m b
k = m a -> PropertyM m a
forall (m :: * -> *) a. Monad m => m a -> PropertyM m a
run m a
m PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
forall a b. PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= a -> PropertyM m b
k

-- | Quantification in monadic properties to 'pick', with a notation similar to
-- 'forAll'. __Note__: values generated by 'forAllM' do not shrink.

forAllM :: (Monad m, Show a) => Gen a -> (a -> PropertyM m b) -> PropertyM m b
forAllM :: forall (m :: * -> *) a b.
(Monad m, Show a) =>
Gen a -> (a -> PropertyM m b) -> PropertyM m b
forAllM Gen a
gen a -> PropertyM m b
k = Gen a -> PropertyM m a
forall (m :: * -> *) a. (Monad m, Show a) => Gen a -> PropertyM m a
pick Gen a
gen PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
forall a b. PropertyM m a -> (a -> PropertyM m b) -> PropertyM m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= a -> PropertyM m b
k

-- | Allows making observations about the test data:
--
-- @
-- monitor ('collect' e)
-- @
--
-- collects the distribution of value of @e@.
--
-- @
-- monitor ('counterexample' "Failure!")
-- @
--
-- Adds @"Failure!"@ to the counterexamples.
monitor :: Monad m => (Property -> Property) -> PropertyM m ()
monitor :: forall (m :: * -> *).
Monad m =>
(Property -> Property) -> PropertyM m ()
monitor Property -> Property
f = ((() -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m ()
forall (m :: * -> *) a.
((a -> Gen (m Property)) -> Gen (m Property)) -> PropertyM m a
MkPropertyM (\() -> Gen (m Property)
k -> (Property -> Property
f (Property -> Property) -> m Property -> m Property
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
`liftM`) (m Property -> m Property) -> Gen (m Property) -> Gen (m Property)
forall a b. (a -> b) -> Gen a -> Gen b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` (() -> Gen (m Property)
k ()))

-- run functions

monadic :: (Testable a, Monad m) => (m Property -> Property) -> PropertyM m a -> Property
monadic :: forall a (m :: * -> *).
(Testable a, Monad m) =>
(m Property -> Property) -> PropertyM m a -> Property
monadic m Property -> Property
runner PropertyM m a
m = Gen Property -> Property
forall prop. Testable prop => prop -> Property
property ((m Property -> Property) -> Gen (m Property) -> Gen Property
forall a b. (a -> b) -> Gen a -> Gen b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap m Property -> Property
runner (PropertyM m a -> Gen (m Property)
forall a (m :: * -> *).
(Testable a, Monad m) =>
PropertyM m a -> Gen (m Property)
monadic' PropertyM m a
m))

monadic' :: (Testable a, Monad m) => PropertyM m a -> Gen (m Property)
monadic' :: forall a (m :: * -> *).
(Testable a, Monad m) =>
PropertyM m a -> Gen (m Property)
monadic' (MkPropertyM (a -> Gen (m Property)) -> Gen (m Property)
m) = (a -> Gen (m Property)) -> Gen (m Property)
m (\a
prop -> m Property -> Gen (m Property)
forall a. a -> Gen a
forall (m :: * -> *) a. Monad m => a -> m a
return (Property -> m Property
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (a -> Property
forall prop. Testable prop => prop -> Property
property a
prop)))

-- | Runs the property monad for 'IO'-computations.
--
-- @
-- prop_cat msg = monadicIO $ do
--   (exitCode, stdout, _) \<- run ('System.Process.readProcessWithExitCode' "cat" [] msg)
--
--   pre ('System.Exit.ExitSuccess' == exitCode)
--
--   assert (stdout == msg)
-- @
--
-- >>> quickCheck prop_cat
-- +++ OK, passed 100 tests.
--
monadicIO :: Testable a => PropertyM IO a -> Property
monadicIO :: forall a. Testable a => PropertyM IO a -> Property
monadicIO = (IO Property -> Property) -> PropertyM IO a -> Property
forall a (m :: * -> *).
(Testable a, Monad m) =>
(m Property -> Property) -> PropertyM m a -> Property
monadic IO Property -> Property
forall prop. Testable prop => IO prop -> Property
ioProperty

#ifndef NO_ST_MONAD
-- | Runs the property monad for 'ST'-computations.
--
-- @
-- -- Your mutable sorting algorithm here
-- sortST :: Ord a => [a] -> 'Control.Monad.ST.ST' s (MVector s a)
-- sortST = 'Data.Vector.thaw' . 'Data.Vector.fromList' . 'Data.List.sort'
--
-- prop_sortST xs = monadicST $ do
--   sorted  \<- run ('Data.Vector.freeze' =<< sortST xs)
--   assert ('Data.Vector.toList' sorted == sort xs)
-- @
--
-- >>> quickCheck prop_sortST
-- +++ OK, passed 100 tests.
--
monadicST :: Testable a => (forall s. PropertyM (ST s) a) -> Property
monadicST :: forall a. Testable a => (forall s. PropertyM (ST s) a) -> Property
monadicST forall s. PropertyM (ST s) a
m = Gen Property -> Property
forall prop. Testable prop => prop -> Property
property ((forall s. Gen (ST s Property)) -> Gen Property
forall a. (forall s. Gen (ST s a)) -> Gen a
runSTGen (PropertyM (ST s) a -> Gen (ST s Property)
forall a (m :: * -> *).
(Testable a, Monad m) =>
PropertyM m a -> Gen (m Property)
monadic' PropertyM (ST s) a
forall s. PropertyM (ST s) a
m))

runSTGen :: (forall s. Gen (ST s a)) -> Gen a
runSTGen :: forall a. (forall s. Gen (ST s a)) -> Gen a
runSTGen forall s. Gen (ST s a)
f = do
  Capture forall a. Gen a -> a
eval <- Gen Capture
capture
  a -> Gen a
forall a. a -> Gen a
forall (m :: * -> *) a. Monad m => a -> m a
return ((forall s. ST s a) -> a
forall a. (forall s. ST s a) -> a
runST (Gen (ST s a) -> ST s a
forall a. Gen a -> a
eval Gen (ST s a)
forall s. Gen (ST s a)
f))
#endif

-- Exceptions


#ifndef NO_EXCEPTIONS

-- | Evaluate the value to Weak Head Normal Form (WHNF) and fail if it does not result in
-- an expected exception being thrown.
assertException ::
     E.Exception exc
  => (exc -> Bool) -- ^ Return `True` if that is the exception that was expected
  -> a -- ^ Value that should result in an exception, when evaluated to WHNF
  -> Property
assertException :: forall exc a. Exception exc => (exc -> Bool) -> a -> Property
assertException exc -> Bool
isExc a
value = (exc -> Bool) -> IO a -> Property
forall exc a. Exception exc => (exc -> Bool) -> IO a -> Property
assertExceptionIO exc -> Bool
isExc (a -> IO a
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
value)


-- | Make sure that a specific exception is thrown during an IO action. The result is
-- evaluated to WHNF.
assertExceptionIO ::
     E.Exception exc
  => (exc -> Bool) -- ^ Return `True` if that is the exception that was expected
  -> IO a -- ^ An action that should throw the expected exception
  -> Property
assertExceptionIO :: forall exc a. Exception exc => (exc -> Bool) -> IO a -> Property
assertExceptionIO exc -> Bool
isExc IO a
action =
  PropertyM IO () -> Property
forall a. Testable a => PropertyM IO a -> Property
monadicIO (PropertyM IO () -> Property) -> PropertyM IO () -> Property
forall a b. (a -> b) -> a -> b
$ do
    Bool
hasFailed <-
      IO Bool -> PropertyM IO Bool
forall (m :: * -> *) a. Monad m => m a -> PropertyM m a
run
        (IO Bool -> (exc -> IO Bool) -> IO Bool
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
           (do a
res <- IO a
action
               a
res a -> IO Bool -> IO Bool
forall a b. a -> b -> b
`seq` Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False)
           (Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> IO Bool) -> (exc -> Bool) -> exc -> IO Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. exc -> Bool
isExc))
    Bool -> PropertyM IO ()
forall (m :: * -> *). Monad m => Bool -> PropertyM m ()
assert Bool
hasFailed

#ifndef NO_DEEPSEQ

-- | Same as `assertException`, but evaluate the value to Normal Form (NF) and fail if it
-- does not result in an expected exception being thrown.
assertDeepException ::
     (NFData a, E.Exception exc)
  => (exc -> Bool) -- ^ Return True if that is the exception that was expected
  -> a -- ^ Value that should result in an exception, when fully evaluated to NF
  -> Property
assertDeepException :: forall a exc.
(NFData a, Exception exc) =>
(exc -> Bool) -> a -> Property
assertDeepException exc -> Bool
isExc a
value = (exc -> Bool) -> () -> Property
forall exc a. Exception exc => (exc -> Bool) -> a -> Property
assertException exc -> Bool
isExc (a -> ()
forall a. NFData a => a -> ()
rnf a
value)

-- | Make sure that a specific exception is thrown during an IO action. The result is
-- evaluated to NF.
assertDeepExceptionIO ::
     (NFData a, E.Exception exc)
  => (exc -> Bool) -- ^ Return True if that is the exception that was expected
  -> IO a -- ^ An action that should throw the expected exception
  -> Property
assertDeepExceptionIO :: forall a exc.
(NFData a, Exception exc) =>
(exc -> Bool) -> IO a -> Property
assertDeepExceptionIO exc -> Bool
isExc IO a
action = (exc -> Bool) -> IO () -> Property
forall exc a. Exception exc => (exc -> Bool) -> IO a -> Property
assertExceptionIO exc -> Bool
isExc ((a -> ()) -> IO a -> IO ()
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> ()
forall a. NFData a => a -> ()
rnf IO a
action)

#endif
#endif

--------------------------------------------------------------------------
-- the end.