module Test.StateMachine.Internal.Sequential
( generateProgram
, filterInvalid
, getUsedVars
, liftShrinkInternal
, shrinkProgram
, checkProgram
)
where
import Control.Monad
(filterM, foldM_)
import Control.Monad.State
(State, StateT, get, lift, modify, put, evalState)
import Data.Set
(Set)
import qualified Data.Set as S
import Test.QuickCheck
(Gen, shrinkList, sized, choose, suchThat)
import Test.QuickCheck.Monadic
(PropertyM, pre, run)
import Test.StateMachine.Internal.Types
import Test.StateMachine.Internal.Types.Environment
import Test.StateMachine.Internal.Utils
import Test.StateMachine.Types
generateProgram
:: forall model act
. Generator model act
-> Precondition model act
-> Transition model act
-> Int
-> StateT (model Symbolic) Gen (Program act)
generateProgram generator precondition transition index = do
size <- lift (sized (\k -> choose (0, k)))
Program <$> go size index
where
go :: Int -> Int -> StateT (model Symbolic) Gen [Internal act]
go 0 _ = return []
go sz ix = do
model <- get
Untyped act <- lift (generator model `suchThat`
\(Untyped act) -> precondition model act)
let sym = Symbolic (Var ix)
put (transition model act sym)
acts <- go (sz 1) (ix + 1)
return (Internal act sym : acts)
filterInvalid
:: HFoldable act
=> Precondition model act
-> Transition model act
-> Program act
-> State (model Symbolic, Set Var) (Program act)
filterInvalid precondition transition
= fmap Program
. filterM go
. unProgram
where
go (Internal act sym@(Symbolic var)) = do
(model, scope) <- get
put (transition model act sym, S.insert var scope)
return (precondition model act && getUsedVars act `S.isSubsetOf` scope)
getUsedVars :: HFoldable act => act Symbolic a -> Set Var
getUsedVars = hfoldMap (\(Symbolic v) -> S.singleton v)
liftShrinkInternal :: Shrinker act -> (Internal act -> [Internal act])
liftShrinkInternal shrinker (Internal act sym) =
[ Internal act' sym | act' <- shrinker act ]
shrinkProgram
:: HFoldable act
=> Shrinker act
-> Precondition model act
-> Transition model act
-> model Symbolic
-> Program act
-> [Program act]
shrinkProgram shrinker precondition transition model
= map ( flip evalState (model, S.empty)
. filterInvalid precondition transition
. Program
)
. shrinkList (liftShrinkInternal shrinker)
. unProgram
checkProgram
:: Monad m
=> HFunctor act
=> Precondition model act
-> Transition model act
-> Postcondition model act
-> model Symbolic
-> model Concrete
-> Semantics act m
-> Program act
-> PropertyM (StateT Environment m) ()
checkProgram precondition transition postcondition smodel0 cmodel0 semantics
= foldM_ go (smodel0, cmodel0)
. unProgram
where
go (smodel, cmodel) (Internal act sym) = do
pre (precondition smodel act)
env <- run get
let cact = hfmap (fromSymbolic env) act
resp <- run (lift (semantics cact))
liftProperty (postcondition cmodel cact resp)
let cresp = Concrete resp
run (modify (insertConcrete sym cresp))
return (transition smodel act sym, transition cmodel cact cresp)
where
fromSymbolic :: Environment -> Symbolic v -> Concrete v
fromSymbolic env sym' = case reifyEnvironment env sym' of
Left err -> error (show err)
Right con -> con