{-| This module defines the 'AssertM' monad, which allows you either to run assertions as ordinary unit tests or to evaluate them as pure functions. -} module Test.Framework.AssertM ( AssertM(..), AssertStackElem(..), AssertBool(..), boolValue, eitherValue, formatStack ) where import Data.Maybe import qualified Data.Text as T import Control.Applicative (Applicative(..)) import Control.Monad (liftM, ap) import Test.Framework.TestInterface import Test.Framework.Location import Test.Framework.Colors -- | A typeclass for generic assertions. class Monad m => AssertM m where genericAssertFailure__ :: Location -> ColorString -> m a genericSubAssert :: Location -> Maybe String -> m a -> m a instance AssertM IO where genericAssertFailure__ loc s = failHTF (FullTestResult (Just loc) [] (Just s) (Just Fail)) genericSubAssert loc mMsg action = subAssertHTF loc mMsg action -- | Stack trace element for generic assertions. data AssertStackElem = AssertStackElem { ase_message :: Maybe String , ase_location :: Maybe Location } deriving (Eq, Ord, Show, Read) -- | Type for evaluating a generic assertion as a pure function. data AssertBool a -- | Assertion passes successfully and yields the given value. = AssertOk a -- | Assertion fails with the given stack trace. In the stack trace, the outermost stackframe comes first. | AssertFailed [AssertStackElem] deriving (Eq, Ord, Show, Read) instance Functor AssertBool where fmap = liftM instance Applicative AssertBool where pure = return (<*>) = ap instance Monad AssertBool where return = AssertOk AssertFailed stack >>= _ = AssertFailed stack AssertOk x >>= k = k x fail msg = AssertFailed [AssertStackElem (Just msg) Nothing] instance AssertM AssertBool where genericAssertFailure__ loc s = AssertFailed [AssertStackElem (Just (T.unpack $ renderColorString s False)) (Just loc)] genericSubAssert loc mMsg action = case action of AssertOk x -> AssertOk x AssertFailed stack -> AssertFailed (AssertStackElem mMsg (Just loc) : stack) -- | Evaluates a generic assertion to a 'Bool' value. boolValue :: AssertBool a -> Bool boolValue x = case x of AssertOk _ -> True AssertFailed _ -> False -- | Evaluates a generic assertion to an 'Either' value. The result -- is @Right x@ if the assertion passes and yields value @x@, otherwise -- the result is @Left err@, where @err@ is an error message. eitherValue :: AssertBool a -> Either String a eitherValue x = case x of AssertOk z -> Right z AssertFailed stack -> Left (formatStack stack) -- | Formats a stack trace. formatStack :: [AssertStackElem] -> String formatStack stack = unlines $ map formatStackElem $ zip [0..] $ reverse stack where formatStackElem (pos, AssertStackElem mMsg mLoc) = let floc = fromMaybe "" $ fmap showLoc mLoc fmsg = fromMaybe "" $ fmap (\s -> ": " ++ s) mMsg pref = if pos > 0 then " called from " else "" in pref ++ floc ++ fmsg