{-# LANGUAGE ScopedTypeVariables #-}
module Test.Util.QSM (
Example
-- opaque
, example
, run
, run'
) where
import Control.Monad
import Control.Monad.Fail qualified as Fail
import Data.Typeable
import Test.StateMachine.Logic qualified as Logic
import Test.StateMachine.Sequential
import Test.StateMachine.Types
import Test.StateMachine.Types.Rank2 qualified as Rank2
data Example cmd a =
Done a
| Run (cmd Symbolic) ([Var] -> Example cmd a)
| Fail String
instance Functor (Example cmd) where
fmap = liftM
instance Applicative (Example cmd) where
pure = Done
(<*>) = ap
instance Monad (Example cmd) where
return = pure
Done a >>= f = f a
Run c k >>= f = Run c (k >=> f)
Fail err >>= _ = Fail err
instance Fail.MonadFail (Example cmd) where
fail = Fail
-- | Run a command, and capture its references
run :: Typeable a => cmd Symbolic -> Example cmd [Reference a Symbolic]
run cmd = Run cmd (Done . map (Reference . Symbolic))
-- | Run a command, ignoring its references
run' :: cmd Symbolic -> Example cmd ()
run' cmd = Run cmd (\_vars -> Done ())
example :: forall model cmd m resp. (Rank2.Foldable resp, Show (cmd Symbolic))
=> StateMachine model cmd m resp
-> Example cmd ()
-> Commands cmd resp
example sm =
Commands . fst . flip runGenSym newCounter . go (initModel sm)
where
go :: model Symbolic -> Example cmd () -> GenSym [Command cmd resp]
go _ (Done ()) = return []
go _ (Fail err) = error $ "example: " ++ err
go m (Run cmd k) = do
case Logic.logic (precondition sm m cmd) of
Logic.VFalse counterexample ->
error $ "Invalid command " ++ show cmd ++ ": " ++ show counterexample
Logic.VTrue -> do
resp <- mock sm m cmd
let m' :: model Symbolic
m' = transition sm m cmd resp
vars :: [Var]
vars = getUsedVars resp
cmd' :: Command cmd resp
cmd' = Command cmd resp vars
(cmd' :) <$> go m' (k vars)