Skip to content
This repository was archived by the owner on Jun 18, 2021. It is now read-only.

Convenient way for manually writing examplesΒ #347

@edsko

Description

@edsko

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions