Writing example Commands by hand (when developing the tests, or when wanting to save particular generated tests) is a bit inconvenient. Not only does Commands capture the result of the mock implementation (and so when the model changes, we have to update our examples, if even if the commands are still the same), we also have to manually deal with references. To make this a bit more convenient, I wrote the following helper, which I find rather neat:
{-# LANGUAGE ScopedTypeVariables #-}
module Test.Util.QSM (
Example -- opaque
, run
, run'
, example
) where
import Control.Monad
import Control.Monad.Fail
import Data.Typeable
import Test.StateMachine.Sequential
import Test.StateMachine.Types
import qualified Test.StateMachine.Types.Rank2 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 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
=> 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
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)
For example, I am currently working on some tests to do with threads, killing them, etc. Here are some manually written Commands:
_forkCount :: Commands (At IO Cmd) (At IO Success)
_forkCount = example sm' $ do
run' $ At $ Fork
run' $ At $ CountTopLevel
_forkKillCount :: Commands (At IO Cmd) (At IO Success)
_forkKillCount = example sm' $ do
[tid] <- run $ At Fork
run' $ At $ Kill tid
run' $ At $ CountTopLevel
Quite nice, I think. Might be worth adding to the library?
Writing example
Commandsby hand (when developing the tests, or when wanting to save particular generated tests) is a bit inconvenient. Not only doesCommandscapture the result of the mock implementation (and so when the model changes, we have to update our examples, if even if the commands are still the same), we also have to manually deal with references. To make this a bit more convenient, I wrote the following helper, which I find rather neat:{-# LANGUAGE ScopedTypeVariables #-} module Test.Util.QSM ( Example -- opaque , run , run' , example ) where import Control.Monad import Control.Monad.Fail import Data.Typeable import Test.StateMachine.Sequential import Test.StateMachine.Types import qualified Test.StateMachine.Types.Rank2 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 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 => 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 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)For example, I am currently working on some tests to do with threads, killing them, etc. Here are some manually written
Commands:Quite nice, I think. Might be worth adding to the library?