-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Library for lockstep-style testing with 'quickcheck-dynamic' -- -- Lockstep-style testing is a particular approach for blackbox testing -- of stateful APIs: we generate a random sequence of APIs calls, then -- execute them both against the system under test and against a model, -- and compare responses up to some notion of observability. @package quickcheck-lockstep @version 0.3.0 module Test.QuickCheck.StateModel.Lockstep.Op class Operation op opIdentity :: Operation op => op a a class Operation op => InterpretOp op f intOp :: InterpretOp op f => op a b -> f a -> Maybe (f b) newtype WrapRealized m a WrapRealized :: Realized m a -> WrapRealized m a [unwrapRealized] :: WrapRealized m a -> Realized m a -- | Convenience function for defining InterpretOp instances -- -- This can be used for monads like IO where Realized m -- a is just a. intOpRealizedId :: (Realized m a ~ a, Realized m b ~ b) => (op a b -> a -> Maybe b) -> op a b -> WrapRealized m a -> Maybe (WrapRealized m b) -- | Convenience function for defining InterpretOp instances for -- monad transformer stacks. intOpTransformer :: forall t m a b op. (Realized (t m) a ~ Realized m a, Realized (t m) b ~ Realized m b, InterpretOp op (WrapRealized m)) => op a b -> WrapRealized (t m) a -> Maybe (WrapRealized (t m) b) module Test.QuickCheck.StateModel.Lockstep.Op.Identity -- | Very simple operation type that supports identity only -- -- This can be used by tests that don't need to map over variables. That -- is, where variables always refer to the exact result of -- previously executed commands. Such tests will not need to define any -- InterpretOp instances. data Op a b [OpId] :: Op a a instance GHC.Show.Show (Test.QuickCheck.StateModel.Lockstep.Op.Identity.Op a b) instance GHC.Classes.Eq (Test.QuickCheck.StateModel.Lockstep.Op.Identity.Op a b) instance Test.QuickCheck.StateModel.Lockstep.Op.Operation Test.QuickCheck.StateModel.Lockstep.Op.Identity.Op instance Test.QuickCheck.StateModel.Lockstep.Op.InterpretOp Test.QuickCheck.StateModel.Lockstep.Op.Identity.Op f -- | Default implementations for the quickcheck-dynamic class -- methods -- -- Intended for qualified import. -- --
-- import Test.QuickCheck.StateModel.Lockstep.Defaults qualified as Lockstep --module Test.QuickCheck.StateModel.Lockstep.Defaults -- | Default implementation for initialState initialState :: state -> Lockstep state -- | Default implementation for nextState nextState :: forall state a. (InLockstep state, Typeable a) => Lockstep state -> LockstepAction state a -> Var a -> Lockstep state -- | Default implementation for precondition -- -- The default precondition only checks that all variables have a value -- and that the operations on them are defined. precondition :: InLockstep state => Lockstep state -> LockstepAction state a -> Bool -- | Default implementation for arbitraryAction arbitraryAction :: InLockstep state => VarContext -> Lockstep state -> Gen (Any (LockstepAction state)) -- | Default implementation for shrinkAction shrinkAction :: InLockstep state => VarContext -> Lockstep state -> LockstepAction state a -> [Any (LockstepAction state)] -- | Default implementation for postcondition -- -- The default postcondition verifies that the real system and the model -- return the same results, up to " observability ". postcondition :: forall m state a. RunLockstep state m => (Lockstep state, Lockstep state) -> LockstepAction state a -> LookUp m -> Realized m a -> PostconditionM m Bool monitoring :: forall m state a. RunLockstep state m => Proxy m -> (Lockstep state, Lockstep state) -> LockstepAction state a -> LookUp m -> Realized m a -> Property -> Property instance Test.QuickCheck.StateModel.Variables.HasVariables (Test.QuickCheck.StateModel.Lockstep.API.Lockstep state) instance Test.QuickCheck.StateModel.Variables.HasVariables (Test.QuickCheck.StateModel.Action (Test.QuickCheck.StateModel.Lockstep.API.Lockstep state) a) -- | Lockstep-style testing using quickcheck-dynamic -- -- See -- https://well-typed.com/blog/2022/09/lockstep-with-quickcheck-dynamic/ -- for a tutorial. -- -- This module is intended for unqualified import alongside imports of -- Test.QuickCheck.StateModel. -- --
-- import Test.QuickCheck.StateModel -- import Test.QuickCheck.StateModel.Lockstep -- import Test.QuickCheck.StateModel.Lockstep.Run qualified as Lockstep -- import Test.QuickCheck.StateModel.Lockstep.Defaults qualified as Lockstep --module Test.QuickCheck.StateModel.Lockstep data Lockstep state -- | Inspect the model that resides inside the Lockstep state getModel :: Lockstep state -> state class (StateModel (Lockstep state), Typeable state, InterpretOp (ModelOp state) (ModelValue state), forall a. Show (ModelValue state a), forall a. Eq (Observable state a), forall a. Show (Observable state a)) => InLockstep state where { -- | Values in the mock environment -- -- ModelValue witnesses the relation between values returned by -- the real system and values returned by the model. -- -- In most cases, we expect the real system and the model to return the -- same value. However, for some things we must allow them to -- diverge: consider file handles for example. data ModelValue state a; -- | Observable responses -- -- The real system returns values of type a, and the model -- returns values of type MockValue a. Observable a -- defines the parts of those results that expect to be the same -- for both. data Observable state a; -- | Type of operations required on the results of actions -- -- Whenever an action has a result of type a, but we later need -- a variable of type b, we need a constructor -- --
-- GetB :: ModelOp state a b
--
--
-- in the ModelOp type. For many tests, the standard Op
-- type will suffice, but not always.
type ModelOp state :: Type -> Type -> Type;
type ModelOp state = Op;
}
-- | Extract the observable part of a response from the model
observeModel :: InLockstep state => ModelValue state a -> Observable state a
-- | All variables required by a command
usedVars :: InLockstep state => LockstepAction state a -> [AnyGVar (ModelOp state)]
-- | Step the model
--
-- The order of the arguments mimicks perform.
modelNextState :: InLockstep state => LockstepAction state a -> ModelLookUp state -> state -> (ModelValue state a, state)
-- | Generate an arbitrary action, given a way to find variables
arbitraryWithVars :: InLockstep state => ModelFindVariables state -> state -> Gen (Any (LockstepAction state))
-- | Shrink an action, given a way to find variables
--
-- This is optional; without an implementation of shrinkWithVars,
-- lists of actions will still be pruned, but individual actions
-- will not be shrunk.
shrinkWithVars :: InLockstep state => ModelFindVariables state -> state -> LockstepAction state a -> [Any (LockstepAction state)]
-- | Tag actions
--
-- Tagging is optional, but can help understand your test input data as
-- well as your shrinker (see tagActions).
tagStep :: InLockstep state => (state, state) -> LockstepAction state a -> ModelValue state a -> [String]
class (InLockstep state, RunModel (Lockstep state) m) => RunLockstep state m
observeReal :: RunLockstep state m => Proxy m -> LockstepAction state a -> Realized m a -> Observable state a
-- | Show responses from the real system
--
-- This method does not need to be implemented, but if it is,
-- counter-examples can include the real response in addition to the
-- observable response.
showRealResponse :: RunLockstep state m => Proxy m -> LockstepAction state a -> Maybe (Dict (Show (Realized m a)))
-- | An action in the lock-step model
type LockstepAction state = Action (Lockstep state)
-- | Find variables of the appropriate type
--
-- The type you pass must be the result type of (previously executed)
-- actions. If you want to change the type of the variable, see
-- map.
type ModelFindVariables state = forall a. Typeable a => Proxy a -> [GVar (ModelOp state) a]
-- | Look up a variable for model execution
--
-- The type of the variable is the type in the real system.
type ModelLookUp state = forall a. ModelVar state a -> ModelValue state a
-- | Variables with a "functor-esque" instance
type ModelVar state = GVar (ModelOp state)
-- | Generalized variables
--
-- The key difference between GVar and the standard Var
-- type is that GVar have a functor-esque structure: see
-- map.
data GVar op f
data AnyGVar op
[SomeGVar] :: GVar op y -> AnyGVar op
-- | Lookup GVar given a lookup function for Var
--
-- The variable must be in the environment and evaluation must succeed.
-- This is normally guaranteed by the default test precondition.
lookUpGVar :: InterpretOp op (WrapRealized m) => Proxy m -> LookUp m -> GVar op a -> Realized m a
mapGVar :: (forall x. op x a -> op x b) -> GVar op a -> GVar op b
class Operation op
opIdentity :: Operation op => op a a
class Operation op => InterpretOp op f
intOp :: InterpretOp op f => op a b -> f a -> Maybe (f b)
module Test.QuickCheck.StateModel.Lockstep.Op.SumProd
-- | Operations with support for products (pairs) and sums (Either)
data Op a b
[OpId] :: Op a a
[OpFst] :: Op (a, b) a
[OpSnd] :: Op (b, a) a
[OpLeft] :: Op (Either a b) a
[OpRight] :: Op (Either b a) a
[OpComp] :: Op b c -> Op a b -> Op a c
intOpId :: Op a b -> a -> Maybe b
instance Test.QuickCheck.StateModel.Lockstep.Op.Operation Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op
instance Test.QuickCheck.StateModel.Lockstep.Op.InterpretOp Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op (Test.QuickCheck.StateModel.Lockstep.Op.WrapRealized GHC.Types.IO)
instance Test.QuickCheck.StateModel.Lockstep.Op.InterpretOp Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op (Test.QuickCheck.StateModel.Lockstep.Op.WrapRealized m) => Test.QuickCheck.StateModel.Lockstep.Op.InterpretOp Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op (Test.QuickCheck.StateModel.Lockstep.Op.WrapRealized (Control.Monad.Trans.State.Lazy.StateT s m))
instance Test.QuickCheck.StateModel.Lockstep.Op.InterpretOp Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op (Test.QuickCheck.StateModel.Lockstep.Op.WrapRealized m) => Test.QuickCheck.StateModel.Lockstep.Op.InterpretOp Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op (Test.QuickCheck.StateModel.Lockstep.Op.WrapRealized (Control.Monad.Trans.Reader.ReaderT r m))
instance GHC.Classes.Eq (Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op a b)
instance GHC.Show.Show (Test.QuickCheck.StateModel.Lockstep.Op.SumProd.Op a b)
-- | Run lockstep tests
--
-- Intended for qualified import.
--
-- -- import Test.QuickCheck.StateModel.Lockstep.Run qualified as Lockstep --module Test.QuickCheck.StateModel.Lockstep.Run -- | Tag a list of actions -- -- This can be used together with QuickCheck's labelledExamples -- to test your tagging code as well as your shrinker (QuickCheck will -- try to produce minimal labelled examples). -- -- Unlike runActions, this does not require a RunModel -- instance; this is executed against the model only. tagActions :: forall state. InLockstep state => Proxy state -> Actions (Lockstep state) -> Property labelActions :: forall state. InLockstep state => Actions (Lockstep state) -> [String] runActions :: RunLockstep state IO => Proxy state -> Actions (Lockstep state) -> Property -- | Convenience runner with support for state initialization -- -- This is less general than runActions, but will be useful in -- many scenarios. -- -- For most lockstep-style tests, a suitable monad to run the tests in is -- ReaderT r IO. In this case, using -- runReaderT as the runner argument is a reasonable -- choice. runActionsBracket :: RunLockstep state m => Proxy state -> IO st -> (st -> IO ()) -> (m Property -> st -> IO Property) -> Actions (Lockstep state) -> Property