module QuickCheckHelpers
( generateRequests
, shrinkRequests
, generateParallelRequests
, shrinkParallelRequests
, monadicProcess
)
where
import Control.Arrow
(second)
import Control.Distributed.Process
(Process)
import Control.Monad.State
(StateT, evalStateT, get, lift, put, runStateT)
import Data.List
(permutations)
import Test.QuickCheck
(Gen, Property, Testable, choose, ioProperty,
shrinkList, sized, suchThat)
import Test.QuickCheck.Monadic
(PropertyM, monadic)
import Utils
generateRequestsStateT
:: (model -> Gen req)
-> (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> StateT model Gen [req]
generateRequestsStateT generator preconditions transitions =
go =<< lift (sized (\k -> choose (0, k)))
where
go 0 = return []
go size = do
model <- get
msg <- lift (generator model `suchThat` preconditions model)
put (transitions model (Left msg))
(msg :) <$> go (size - 1)
generateRequests
:: (model -> Gen req)
-> (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> Gen [req]
generateRequests generator preconditions transitions =
evalStateT (generateRequestsStateT generator preconditions transitions)
generateParallelRequests
:: (model -> Gen req)
-> (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> Gen ([req], [req])
generateParallelRequests generator preconditions transitions model = do
(prefix, model') <- runStateT (generateRequestsStateT generator preconditions transitions) model
suffix <- generateParallelSafeRequests generator preconditions transitions model'
return (prefix, suffix)
generateParallelSafeRequests
:: (model -> Gen req)
-> (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> Gen [req]
generateParallelSafeRequests generator preconditions transitions = go []
where
go reqs model = do
req <- generator model `suchThat` preconditions model
let reqs' = req : reqs
if length reqs' <= 6 && parallelSafe preconditions transitions model reqs'
then go reqs' model
else return (reverse reqs)
parallelSafe
:: (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> [req]
-> Bool
parallelSafe preconditions transitions model0
= all (preconditionsHold model0)
. permutations
where
preconditionsHold _ [] = True
preconditionsHold model (req : reqs) = preconditions model req &&
preconditionsHold (transitions model (Left req)) reqs
shrinkRequests
:: (model -> req -> [req])
-> (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> [req] -> [[req]]
shrinkRequests shrinker preconditions transitions model0
= filter (validRequests preconditions transitions model0)
. shrinkList (shrinker model0)
validRequests :: (model -> req -> Bool) -> (model -> Either req resp -> model) -> model -> [req] -> Bool
validRequests preconditions transitions = go
where
go _ [] = True
go model (req : reqs) = preconditions model req &&
go (transitions model (Left req)) reqs
validParallelRequests
:: (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> ([req], [req])
-> Bool
validParallelRequests preconditions transitions model (prefix, suffix)
= validRequests preconditions transitions model prefix
&& parallelSafe preconditions transitions model' suffix
where
model' = foldl transitions model (map Left prefix)
shrinkParallelRequests
:: (model -> req -> [req])
-> (model -> req -> Bool)
-> (model -> Either req resp -> model)
-> model
-> ([req], [req]) -> [([req], [req])]
shrinkParallelRequests shrinker preconditions transitions model (prefix, suffix)
= filter (validParallelRequests preconditions transitions model)
[ (prefix', suffix')
| (prefix', suffix') <- shrinkPair (shrinkList (shrinker model)) (prefix, suffix)
]
++
moveSuffixToPrefix
where
pickOneReturnRest :: [a] -> [(a, [a])]
pickOneReturnRest [] = []
pickOneReturnRest (x : xs) = (x, xs) : map (second (x :)) (pickOneReturnRest xs)
moveSuffixToPrefix =
[ (prefix ++ [prefix'], suffix')
| (prefix', suffix') <- pickOneReturnRest suffix
]
monadicProcess :: Testable a => PropertyM Process a -> Property
monadicProcess = monadic (ioProperty . runLocalProcess)
shrinkPair' :: (a -> [a]) -> (b -> [b]) -> ((a, b) -> [(a, b)])
shrinkPair' shrinkerA shrinkerB (x, y) =
[ (x', y) | x' <- shrinkerA x ] ++
[ (x, y') | y' <- shrinkerB y ]
shrinkPair :: (a -> [a]) -> ((a, a) -> [(a, a)])
shrinkPair shrinker = shrinkPair' shrinker shrinker