module Control.Monad.Freer.TestControl
( TestControl
, runTestControl
, runTestControlData
, runTestControlData_
, runTestControlError
, TestExitStatus(..)
, fulfilled
, throwUnexpected
, throwExpecting
, failure
, expect
, collect
, stub
, stubs
, result
, result_
, converse
, spy
) where
import Control.Applicative
import Control.Arrow (first)
import Control.Monad (join)
import Control.Monad.Freer.Converse
import Control.Monad.Freer.Exception
import Data.Monoid
import Data.Functor.Classes.FreerConverse.Parametric
type TestControl = Exc TestExitStatus
data TestExitStatus = TestFulfilled
| TestFailed String
fulfilled :: Member TestControl r => Eff r a
fulfilled = throwError TestFulfilled
runTestControl :: (String -> Eff r a)
-> Eff r a
-> Eff (TestControl ': r) a
-> Eff r a
runTestControl onFail onFulfill t = runError t >>= \testResult -> case testResult of
Left (TestFailed s) -> onFail s
Left TestFulfilled -> onFulfill
Right x -> return x
runTestControlData :: Eff (TestControl ': r) a -> Eff r (Either String (Maybe a))
runTestControlData a = runTestControl (return . Left) (return (Right Nothing)) (fmap (Right . Just) a)
runTestControlData_ :: Eff (TestControl ': r) a -> Eff r (Either String ())
runTestControlData_ a = runTestControl (return . Left) (return (Right ())) (Right () <$ a)
runTestControlError :: Eff (TestControl ': r) () -> Eff r ()
runTestControlError = runTestControl (error . showString "Test failed: ") (return ())
failure :: (Member TestControl r, Show v, ShowP f) => String
-> Eff (Converse f r v ': r) a
failure reason = do
nextEvent <- showNext
throwError $ TestFailed $ reason ++ "\nNext event: " ++ nextEvent
throwExpecting
:: ( ShowP f
, Member TestControl r
)
=> String
-> f a
-> Eff r b
throwExpecting expectation v = throwError $ TestFailed $ "Expecting " ++ expectation ++ ", but got " ++ showP v
throwUnexpected :: (ShowP f, Member TestControl r) => f a -> Eff r b
throwUnexpected v = throwError $ TestFailed $ "Unexpected effect: " ++ showP v
expect
:: Member TestControl r
=> (forall a. f a -> Eff r (a, b))
-> Eff (Converse f r v ': r) b
expect f = converse (\x -> first Just <$> f x) (const $ throwError $ TestFailed "Unexpected program termination: effect expected.")
stub :: Member TestControl r
=> (forall b. f b -> Eff r b)
-> Eff (Converse f r v ': r) ()
stub f = expect (fmap (\x -> (x,())) <$> f)
collect :: (forall a. f a -> Eff r (Maybe (a, b))) -> Eff (Converse f r v ': r) [b]
collect f = do
join $ converse (
\x -> do
replyMaybe <- f x
case replyMaybe of
Just (reply, spied) -> return (Just reply, (spied :) <$> collect f)
Nothing -> return (Nothing, (return []))
) (const $ return (return []))
stubs
:: (forall b. f b -> Eff r (Maybe b))
-> Eff (Converse f r v ': r) ()
stubs f = do
join $ converse (
\x -> do
replyMaybe <- f x
case replyMaybe of
Just reply -> return (Just reply, stubs f)
Nothing -> return (Nothing, (return ()))
) (const $ return (return ()))
result :: (Member TestControl r, ShowP f) => Eff (Converse f r v ': r) v
result = converse throwUnexpected return
result_ :: (Member TestControl r) => Eff (Converse f r v ': r) v
result_ = converse (const $ throwError $ TestFailed $
"Expected program termination with result, but got an effect instead."
)
return
spy :: (Monad m, Monoid mm) => a -> m (mm, a)
spy a = return (mempty, a)