module Test.StateMachine
(
Program
, programLength
, forAllProgram
, monadicSequential
, runProgram
, prettyProgram
, actionNames
, checkActionNames
, ParallelProgram
, History
, monadicParallel
, runParallelProgram
, runParallelProgram'
, prettyParallelProgram
, forAllProgramC
, monadicSequentialC
, monadicParallelC
, module Test.StateMachine.Types
, Test.QuickCheck.quickCheck
) where
import Control.Monad.IO.Class
(MonadIO)
import Control.Monad.State
(evalStateT, replicateM)
import Control.Monad.Trans.Control
(MonadBaseControl)
import Data.Functor.Classes
(Show1)
import Data.Map
(Map)
import qualified Data.Map as M
import Data.Typeable
(Typeable)
import Test.QuickCheck
(Property, Testable, collect, cover, ioProperty,
property)
import qualified Test.QuickCheck
import Test.QuickCheck.Counterexamples
((:&:)(..), PropertyOf)
import qualified Test.QuickCheck.Counterexamples as CE
import Test.QuickCheck.Monadic
(PropertyM, monadic, run)
import Test.QuickCheck.Property
(succeeded)
import Test.StateMachine.Internal.Parallel
import Test.StateMachine.Internal.Sequential
import Test.StateMachine.Internal.Types
import Test.StateMachine.Internal.Utils
(forAllShrinkShowC, whenFailM)
import Test.StateMachine.Types
import Test.StateMachine.Types.History
forAllProgram
:: HFoldable act
=> Generator model act
-> Shrinker act
-> Precondition model act
-> Transition' model act err
-> InitialModel model
-> (Program act -> Property)
-> Property
forAllProgram generator shrinker precondition transition model =
property
. forAllProgramC generator shrinker precondition transition model
. \prop p -> CE.property (prop p)
forAllProgramC
:: HFoldable act
=> Generator model act
-> Shrinker act
-> Precondition model act
-> Transition' model act err
-> InitialModel model
-> (Program act -> PropertyOf a)
-> PropertyOf (Program act :&: a)
forAllProgramC generator shrinker precondition transition model =
forAllShrinkShowC
(evalStateT (generateProgram generator precondition transition 0) model)
(shrinkProgram shrinker precondition transition model)
(const "")
monadicSequential
:: Monad m
=> HFoldable act
=> Testable a
=> StateMachine' model act m err
-> (Program act -> PropertyM m a)
-> Property
monadicSequential sm = property . monadicSequentialC sm
monadicSequentialC
:: Monad m
=> HFoldable act
=> Testable a
=> StateMachine' model act m err
-> (Program act -> PropertyM m a)
-> PropertyOf (Program act)
monadicSequentialC StateMachine {..} predicate
= fmap (\(prog :&: ()) -> prog)
. forAllProgramC generator' shrinker' precondition' transition' model'
$ CE.property
. monadic (ioProperty . runner')
. predicate
#if !MIN_VERSION_QuickCheck(2,10,0)
instance Testable () where
property = property . liftUnit
where
liftUnit () = succeeded
#endif
runProgram
:: Monad m
=> Show1 (act Symbolic)
=> Show err
=> Typeable err
=> HTraversable act
=> StateMachine' model act m err
-> Program act
-> PropertyM m (History act err, model Concrete, Reason)
runProgram sm = run . executeProgram sm
prettyProgram
:: MonadIO m
=> Show (model Concrete)
=> Show err
=> StateMachine' model act m err
-> History act err
-> Property
-> PropertyM m ()
prettyProgram StateMachine{..} hist prop =
putStrLn (ppHistory model' transition' hist) `whenFailM` prop
checkActionNames :: Constructors act => Program act -> Property -> Property
checkActionNames prog
= collect names
. cover (length names == numOfConstructors) 1 "coverage"
where
names = actionNames prog
numOfConstructors = nConstructors prog
actionNames :: forall act. Constructors act => Program act -> [(Constructor, Int)]
actionNames = M.toList . foldl go M.empty . unProgram
where
go :: Map Constructor Int -> Internal act -> Map Constructor Int
go ih (Internal act _) = M.insertWith (+) (constructor act) 1 ih
forAllParallelProgramC
:: Show1 (act Symbolic)
=> Eq (Untyped act)
=> HFoldable act
=> Generator model act
-> Shrinker act
-> Precondition model act
-> Transition' model act err
-> InitialModel model
-> (ParallelProgram act -> PropertyOf a)
-> PropertyOf (ParallelProgram act :&: a)
forAllParallelProgramC generator shrinker precondition transition model =
forAllShrinkShowC
(generateParallelProgram generator precondition transition model)
(shrinkParallelProgram shrinker precondition transition model)
(const "")
monadicParallel
:: MonadBaseControl IO m
=> Eq (Untyped act)
=> Show1 (act Symbolic)
=> HFoldable act
=> StateMachine' model act m err
-> (ParallelProgram act -> PropertyM m ())
-> Property
monadicParallel sm = property . monadicParallelC sm
monadicParallelC
:: MonadBaseControl IO m
=> Eq (Untyped act)
=> Show1 (act Symbolic)
=> HFoldable act
=> StateMachine' model act m err
-> (ParallelProgram act -> PropertyM m ())
-> PropertyOf (ParallelProgram act)
monadicParallelC StateMachine {..} predicate
= fmap (\(prog :&: ()) -> prog)
. forAllParallelProgramC generator' shrinker' precondition' transition' model'
$ CE.property
. monadic (ioProperty . runner')
. predicate
runParallelProgram
:: MonadBaseControl IO m
=> Show1 (act Symbolic)
=> HTraversable act
=> StateMachine' model act m err
-> ParallelProgram act
-> PropertyM m [(History act err, Property)]
runParallelProgram = runParallelProgram' 10
runParallelProgram'
:: MonadBaseControl IO m
=> Show1 (act Symbolic)
=> HTraversable act
=> Int
-> StateMachine' model act m err
-> ParallelProgram act
-> PropertyM m [(History act err, Property)]
runParallelProgram' n StateMachine {..} prog =
replicateM n $ do
hist <- run (executeParallelProgram semantics' prog)
return (hist, linearise transition' postcondition' model' hist)
prettyParallelProgram
:: MonadIO m
=> HFoldable act
=> Show (Untyped act)
=> ParallelProgram act
-> [(History act err, Property)]
-> PropertyM m ()
prettyParallelProgram prog
= mapM_ (\(hist, prop) ->
print (toBoxDrawings prog hist) `whenFailM` prop)