{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Test.StateMachine.Utils
( liftProperty
, whenFailM
, anyP
, suchThatEither
, collects
, Shrunk(..)
, shrinkS
, shrinkListS
, shrinkListS'
, shrinkListS''
, shrinkPairS
, shrinkPairS'
, pickOneReturnRest
, pickOneReturnRest2
, pickOneReturnRestL
, mkModel
)
where
import Prelude
import Control.Arrow
((***))
import Data.List
(foldl')
import Test.QuickCheck
(Arbitrary, Gen, Property, collect, resize, shrink,
shrinkList, sized, whenFail)
import Test.QuickCheck.Monadic
(PropertyM(MkPropertyM))
import Test.QuickCheck.Property
(property, (.&&.), (.||.))
import Test.StateMachine.Types
liftProperty :: Monad m => Property -> PropertyM m ()
liftProperty prop = MkPropertyM (\k -> fmap (prop .&&.) <$> k ())
whenFailM :: Monad m => IO () -> Property -> PropertyM m ()
whenFailM m prop = liftProperty (m `whenFail` prop)
anyP :: (a -> Property) -> [a] -> Property
anyP p = foldr (\x ih -> p x .||. ih) (property False)
suchThatEither :: forall a. Gen a -> (a -> Bool) -> Gen (Either [a] a)
gen `suchThatEither` p = sized (try [] 0 . max 100)
where
try :: [a] -> Int -> Int -> Gen (Either [a] a)
try ces _ 0 = return (Left (reverse ces))
try ces k n = do
x <- resize (2 * k + n) gen
if p x
then return (Right x)
else try (x : ces) (k + 1) (n - 1)
collects :: Show a => [a] -> Property -> Property
collects = repeatedly collect
where
repeatedly :: (a -> b -> b) -> ([a] -> b -> b)
repeatedly = flip . foldl' . flip
data Shrunk a = Shrunk { wasShrunk :: Bool, shrunk :: a }
deriving stock (Eq, Show, Functor)
shrinkS :: Arbitrary a => a -> [Shrunk a]
shrinkS a = map (Shrunk True) (shrink a) ++ [Shrunk False a]
shrinkListS :: forall a. (a -> [Shrunk a]) -> [a] -> [Shrunk [a]]
shrinkListS f = \xs -> concat [
map (Shrunk True) (shrinkList (const []) xs)
, shrinkOne xs
, [Shrunk False xs]
]
where
shrinkOne :: [a] -> [Shrunk [a]]
shrinkOne [] = []
shrinkOne (x:xs) = [Shrunk True (x' : xs) | Shrunk True x' <- f x]
++ [Shrunk True (x : xs') | Shrunk True xs' <- shrinkOne xs]
shrinkListS' :: [a] -> [Shrunk [a]]
shrinkListS' = shrinkListS (\a -> [Shrunk False a])
shrinkListS'' :: forall a. (a -> [Shrunk a]) -> [a] -> [Shrunk [a]]
shrinkListS'' f xs =
let shr = shrinkListS f xs
len = length xs
in filter (\s -> length (shrunk s) == len) shr
shrinkPairS :: (a -> [Shrunk a])
-> (b -> [Shrunk b])
-> (a, b) -> [Shrunk (a, b)]
shrinkPairS f g (a, b) =
[Shrunk True (a', b) | Shrunk True a' <- f a ]
++ [Shrunk True (a, b') | Shrunk True b' <- g b ]
++ [Shrunk False (a, b)]
shrinkPairS' :: (a -> [Shrunk a]) -> (a, a) -> [Shrunk (a, a)]
shrinkPairS' f = shrinkPairS f f
pickOneReturnRest2 :: ([a], [a]) -> [(a, ([a],[a]))]
pickOneReturnRest2 (xs, ys) =
map (id *** flip (,) ys) (pickOneReturnRest xs) ++
map (id *** (,) xs) (pickOneReturnRest ys)
pickOneReturnRest :: [a] -> [(a, [a])]
pickOneReturnRest [] = []
pickOneReturnRest (x : xs) = (x, xs) : map (id *** (x :)) (pickOneReturnRest xs)
pickOneReturnRestL :: [[a]] -> [(a, [[a]])]
pickOneReturnRestL ls = concatMap
(\(prev, as, next) -> fmap (\(a, rest) -> (a, prev ++ [rest] ++ next)) $ pickOneReturnRest as)
$ splitEach ls
where
splitEach :: [a] -> [([a], a, [a])]
splitEach [] = []
splitEach (a : as) = fmap (\(prev, a', next) -> (prev, a', next)) $
go' [([], a, as)] ([], a, as)
where
go' :: [([a], a, [a])] -> ([a], a, [a]) -> [([a], a, [a])]
go' acc (_, _, []) = reverse acc
go' acc (prev, a', b : next) =
let newElem = (a' : prev, b, next)
in go' (newElem : acc) newElem
mkModel :: StateMachine model cmd m resp -> History cmd resp -> model Concrete
mkModel StateMachine {transition, initModel} =
go initModel . operationsPath . interleavings . unHistory
where
go m [] = m
go m (Operation cmd resp _ : rest) = go (transition m cmd resp) rest
go m (Crash _ _ _ : rest) = go m rest