packages feed

hedgehog-extras-0.10.1.0: src/Hedgehog/Extras/Test/MonadAssertion.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}

module Hedgehog.Extras.Test.MonadAssertion
  ( MonadAssertion(..)
  , assertFailure
  , assertFailure_
  , tryAssertion
  ) where

import           Control.Applicative (Applicative(..))
import           Control.Monad
import           Control.Monad.Trans.Class
import           Data.Either
import           Data.Function
import           Data.Functor ((<$>))
import           Data.Monoid
import           GHC.Stack (HasCallStack)
import           Hedgehog (MonadTest(..))
import           Hedgehog.Extras.Test.Prim
import           Text.Show (Show(..))

import qualified Control.Monad.Trans.Except as E
import qualified Control.Monad.Trans.Resource as IO
import qualified Control.Monad.Trans.Resource.Internal as IO
import qualified GHC.Stack as GHC
import qualified Hedgehog as H
import qualified Hedgehog.Internal.Property as H

class Monad m => MonadAssertion m where
  throwAssertion :: H.Failure -> m a
  catchAssertion :: m a -> (H.Failure -> m a) -> m a

instance Monad m => MonadAssertion (H.TestT m) where
  throwAssertion f = H.liftTest $ H.mkTest (Left f, mempty)
  catchAssertion g h = H.TestT $ E.catchE (H.unTest g) (H.unTest . h)

instance MonadAssertion m => MonadAssertion (IO.ResourceT m) where
  throwAssertion = lift . throwAssertion
  catchAssertion r h = IO.ResourceT $ \i -> IO.unResourceT r i `catchAssertion` \e -> IO.unResourceT (h e) i

deriving instance Monad m => MonadAssertion (H.PropertyT m)

-- | Attempt to run a function that may assert, returning either a failure or the result of the assertion.
tryAssertion :: ()
  => MonadAssertion m
  => m a
  -> m (Either H.Failure a)
tryAssertion m =
  catchAssertion (Right <$> m) (pure . Left)

-- | Run the given action and succeed if the action fails, but fail if it succeeds.
assertFailure :: ()
  => HasCallStack
  => Show a
  => MonadAssertion m
  => MonadTest m
  => m a
  -> m H.Failure
assertFailure f = do
  result <- tryAssertion f
  case result of
    Left e -> pure e
    Right a -> failMessage GHC.callStack $ show a

-- | Run the given action and succeed if the action fails, but fail if it succeeds.
assertFailure_ :: ()
  => HasCallStack
  => Show a
  => MonadAssertion m
  => MonadTest m
  => m a
  -> m ()
assertFailure_ f =
  GHC.withFrozenCallStack $
    void $ assertFailure f