-- 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.2.0 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 => Lockstep state -> Gen (Any (LockstepAction state)) -- | Default implementation for shrinkAction shrinkAction :: InLockstep state => 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 -> 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 -- | 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 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 (Control.Monad.Trans.Reader.ReaderT r GHC.Types.IO)) instance 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 GHC.Types.IO)) 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