{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Tutorial: <http://github.com/bennofs/quickcheck-property-monad/tree/master/README.md>
module Test.QuickCheck.Property.Monad
  ( PropM()
  , assert
  , failWith
  , generate
  , logMessage
  , logMessageLn
  ) where

import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Trans.Either
import Control.Monad.Trans.Writer
import Test.QuickCheck.Gen
import Test.QuickCheck.Property

-- | PropM is a monad for writing properties that depend on random
-- data. This is especially useful if you have many invariants for
-- your data and cannot simply write an 'Arbitrary' instance.
newtype PropM a = PropM (EitherT String (WriterT String Gen) a) deriving (Functor, Applicative, Alternative, Monad, MonadPlus)

instance Testable a => Testable (PropM a) where
  property (PropM m) = property $ do
    (r,w) <- runWriterT $ runEitherT m
    case r of
      Right r' -> return $ property r'
      Left err -> return $ printTestCase (w ++ "\n" ++ err) $ property False

-- | Assert that a certain condition is true. If the condition is false, fail with the
-- given error message.
assert :: String -> Bool -> PropM ()
assert err cond = unless cond $ failWith $ "Assertion failed: " ++ err

-- | Fail with the given error message.
failWith :: String -> PropM ()
failWith err = PropM $ left err

-- | Use the given generator to generate a value.
generate :: Gen a -> PropM a
generate = PropM . lift . lift

-- | Log a message that will be printed when the test case fails.
logMessage :: String -> PropM ()
logMessage = PropM . lift . tell

-- | Like 'logMessage' but appends a line break after the message.
logMessageLn :: String -> PropM ()
logMessageLn = logMessage . (++ "\n")