packages feed

quickcheck-dynamic-3.0.0: src/Test/QuickCheck/DynamicLogic.hs

-- | Monadic interface for writing /Dynamic Logic/ properties.
--
-- This interface offers a much nicer experience than manipulating the
-- expressions it is implemented on top of, especially as it improves
-- readability. It's still possible to express properties as pure
-- expressions using the `Test.QuickCheck.DynamicLogic.Internal` module
-- and it might make sense depending on the context and the kind of
-- properties one wants to express.
module Test.QuickCheck.DynamicLogic (
  DL,
  action,
  anyAction,
  anyActions,
  anyActions_,
  stopping,
  weight,
  getSize,
  getModelStateDL,
  getVarContextDL,
  forAllVar,
  assert,
  assertModel,
  monitorDL,
  forAllQ,
  forAllDL,
  forAllMappedDL,
  forAllUniqueDL,
  DL.DynLogicModel (..),
  module Test.QuickCheck.DynamicLogic.Quantify,
) where

import Control.Applicative
import Control.Monad
import Data.Typeable
import Test.QuickCheck hiding (getSize)
import Test.QuickCheck.DynamicLogic.Internal qualified as DL
import Test.QuickCheck.DynamicLogic.Quantify
import Test.QuickCheck.StateModel

-- | The `DL` monad provides a nicer interface to dynamic logic formulae than the plain API.
--   It's a continuation monad producing a `DL.DynFormula` formula, with a state component (with
--   variable context) threaded through.
newtype DL s a = DL {unDL :: Annotated s -> (a -> Annotated s -> DL.DynFormula s) -> DL.DynFormula s}
  deriving (Functor)

instance Applicative (DL s) where
  pure x = DL $ \s k -> k x s
  (<*>) = ap

instance Alternative (DL s) where
  empty = DL $ \_ _ -> DL.ignore
  DL h <|> DL j = DL $ \s k -> h s k DL.||| j s k

instance Monad (DL s) where
  return = pure
  DL h >>= j = DL $ \s k -> h s $ \x s1 -> unDL (j x) s1 k

instance MonadFail (DL s) where
  fail = errorDL

action :: (Typeable a, Eq (Action s a), Show (Action s a)) => Action s a -> DL s (Var a)
action cmd = DL $ \_ k -> DL.after cmd k

anyAction :: DL s ()
anyAction = DL $ \_ k -> DL.afterAny $ k ()

anyActions :: Int -> DL s ()
anyActions n =
  stopping
    <|> pure ()
    <|> (weight (fromIntegral n) >> anyAction >> anyActions n)

-- average number of actions same as average length of a list
anyActions_ :: DL s ()
anyActions_ = do
  n <- getSize
  anyActions (n `div` 2 + 1)

stopping :: DL s ()
stopping = DL $ \s k -> DL.toStop (k () s)

weight :: Double -> DL s ()
weight w = DL $ \s k -> DL.weight w (k () s)

getSize :: DL s Int
getSize = DL $ \s k -> DL.withSize $ \n -> k n s

getModelStateDL :: DL s s
getModelStateDL = DL $ \s k -> k (underlyingState s) s

getVarContextDL :: DL s VarContext
getVarContextDL = DL $ \s k -> k (vars s) s

forAllVar :: forall a s. Typeable a => DL s (Var a)
forAllVar = do
  xs <- ctxAtType <$> getVarContextDL
  forAllQ $ elementsQ xs

errorDL :: String -> DL s a
errorDL name = DL $ \_ _ -> DL.errorDL name

-- | Fail if the boolean is @False@.
--
--   Equivalent to
--
-- @
-- assert msg b = unless b (fail msg)
-- @
assert :: String -> Bool -> DL s ()
assert name b = if b then return () else errorDL name

assertModel :: String -> (s -> Bool) -> DL s ()
assertModel name p = assert name . p =<< getModelStateDL

monitorDL :: (Property -> Property) -> DL s ()
monitorDL f = DL $ \s k -> DL.monitorDL f (k () s)

-- | Generate a random value using the given `Quantification` (or list/tuple of quantifications).
--   Generated values will only shrink to smaller values that could also have been generated.
forAllQ :: Quantifiable q => q -> DL s (Quantifies q)
forAllQ q = DL $ \s k -> DL.forAllQ q $ \x -> k x s

runDL :: Annotated s -> DL s () -> DL.DynFormula s
runDL s dl = unDL dl s $ \_ _ -> DL.passTest

forAllUniqueDL ::
  (DL.DynLogicModel s, Testable a) =>
  Int ->
  Annotated s ->
  DL s () ->
  (Actions s -> a) ->
  Property
forAllUniqueDL nextVar initState d = DL.forAllUniqueScripts nextVar initState (runDL initState d)

forAllDL ::
  (DL.DynLogicModel s, Testable a) =>
  DL s () ->
  (Actions s -> a) ->
  Property
forAllDL d = DL.forAllScripts (runDL initialAnnotatedState d)

forAllMappedDL ::
  (DL.DynLogicModel s, Testable a) =>
  (rep -> DL.DynLogicTest s) ->
  (DL.DynLogicTest s -> rep) ->
  (Actions s -> srep) ->
  DL s () ->
  (srep -> a) ->
  Property
forAllMappedDL to from fromScript d prop =
  DL.forAllMappedScripts to from (runDL initialAnnotatedState d) (prop . fromScript)