mockcat-1.4.0.0: test/Property/AdditionalProps.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -O0 #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
{-# OPTIONS_GHC -fno-hpc #-}
{-# OPTIONS_GHC -Wno-deprecations #-}
module Property.AdditionalProps
( spec
, prop_predicate_param_match_counts
, prop_multicase_progression
, prop_runMockT_isolation
, prop_partial_order_duplicates
) where
import Test.QuickCheck hiding (once)
import Test.QuickCheck.Monadic (monadicIO, run, assert)
import Control.Exception (try, SomeException, evaluate)
import Control.Monad (forM, forM_)
import Data.List (nub)
import Test.MockCat
import Control.Monad.IO.Class (liftIO)
import Property.Generators (resetMockHistory)
import Test.Hspec (Spec, describe, it)
spec :: Spec
spec = do
describe "Property Additional (Predicate / Multi-case / Isolation / Duplicates)" $ do
it "predicate param counts match" $ property prop_predicate_param_match_counts
it "multi-case progression saturates" $ property prop_multicase_progression
it "runMockT isolation of counts" $ property prop_runMockT_isolation
it "partial order with duplicates behaves" $ property prop_partial_order_duplicates
perCall :: Int -> a -> a
perCall i x = case i of
_ -> x
--------------------------------------------------------------------------------
-- 21. PredicateParam property
--------------------------------------------------------------------------------
-- | Property: A predicate Param (expect even) accepts only matching values and
-- the total recorded calls equals count of even inputs attempted.
-- | Property: A predicate Param (expect even) accepts only matching values and
-- the total recorded calls equals count of even inputs attempted.
prop_predicate_param_match_counts :: Property
prop_predicate_param_match_counts = forAll genVals $ \xs -> monadicIO $ do
-- Use withMock
let expected = length (filter even xs)
run $ withMock $ do
-- build predicate mock
f <- mock (expect even "even" ~> True)
`expects` called (times expected)
results <- liftIO $ mapM (\x -> try (evaluate (f x)) :: IO (Either SomeException Bool)) xs
let successes = length [ () | Right _ <- results ]
-- sanity checks (optional, but runMockT handles main verification)
liftIO $ do
if successes /= expected
then error $ "Success count mismatch: " ++ show successes ++ " vs " ++ show expected
else pure ()
assert True
where
genVals = resize 40 $ listOf (arbitrary :: Gen Int)
--------------------------------------------------------------------------------
-- 22. Multi-case progression property
--------------------------------------------------------------------------------
-- | For identical argument, successive cases yield successive results then
-- saturate at the last value.
prop_multicase_progression :: Property
prop_multicase_progression = forAll genSeq $ \(arg, rs, extra) -> monadicIO $ do
let totalCalls = length rs + extra
run $ withMock $ do
f <- mock (cases [ param arg ~> r | r <- rs ])
`expects` called (times totalCalls)
-- Run loop
vals <- liftIO $
forM [1 .. totalCalls] $ \i ->
evaluate (perCall i (f arg))
liftIO $ do
let (prefix, suffix) = splitAt (length rs) vals
if prefix /= rs || not (all (== last rs) suffix)
then error "Result sequence mismatch"
else pure ()
assert True
where
genSeq = do
arg <- arbitrary :: Gen Int
Positive len <- arbitrary :: Gen (Positive Int)
let capped = 1 + len `mod` 5 -- keep small (1..5)
rs <- vectorOf capped (arbitrary :: Gen Int)
extra <- chooseInt (0,3)
pure (arg, rs, extra)
--------------------------------------------------------------------------------
-- 23. runMockT isolation property
--------------------------------------------------------------------------------
-- | Two independent runMockT invocations must not leak call counts.
-- The first run enforces a single invocation; the second expects zero for a fresh mock.
prop_runMockT_isolation :: Property
prop_runMockT_isolation = monadicIO $ do
run resetMockHistory
-- Run 1: expect one call
r1 <- run ((try $ runMockT $ do
f <- mock (param (1 :: Int) ~> True)
`expects` called once
liftIO $ f 1 `seq` pure ()
) :: IO (Either SomeException ()))
case r1 of
Left (_ :: SomeException) -> assert False
Right () -> assert True
-- Run 2: expect zero (if leaked, would see 1 and fail)
r2 <- run ((try $ runMockT $ do
_f <- mock (param (1 :: Int) ~> True)
`expects` called never
pure ()
) :: IO (Either SomeException ()))
case r2 of
Left (_ :: SomeException) -> assert False
Right () -> assert True
--------------------------------------------------------------------------------
-- 25. Partial order duplicates property
--------------------------------------------------------------------------------
-- | For a sequence with duplicates, the partial order check succeeds with the
-- list of first occurrences; reversing that list (when length >=2) fails.
prop_partial_order_duplicates :: Property
prop_partial_order_duplicates = forAll genDupScript $ \xs -> length xs >= 2 ==> monadicIO $ do
let uniques = nub xs
-- success case
run $ withMock $ do
f <- mock (cases [ param x ~> True | x <- xs ])
`expects` calledInSequence uniques
liftIO $ forM_ xs $ \x -> f x `seq` pure ()
assert True
-- failure case (if we have at least two unique values)
case uniques of
(_:_:_) | isClusterOrdered uniques xs -> do
let reversed = reverse uniques
e <- run ((try $ withMock $ do
f <- mock (cases [ param x ~> True | x <- xs ])
`expects` calledInSequence reversed
liftIO $ forM_ xs $ \x -> f x `seq` pure ()
) :: IO (Either SomeException ()))
case e of
Left _ -> assert True
Right _ -> assert False
_ -> assert True
where
genDupScript = do
Positive len <- arbitrary :: Gen (Positive Int)
let size = 2 + len `mod` 6 -- 2..7
base <- vectorOf size (arbitrary :: Gen Int)
-- ensure at least one duplicate by forcing first element copy if all distinct
pure $ ensureDup base
ensureDup ys = if length (nub ys) == length ys && not (null ys) then (case ys of (h:_) -> h : ys; [] -> ys) else ys
-- Check that for each earlier unique value all its indices precede all indices of later unique values.
isClusterOrdered us seqVals = all pairOrdered pairs
where
idxs v = [ i | (i,a) <- zip [0..] seqVals, a == v ]
pairs = [ (u1,u2) | (i,u1) <- zip [0..] us, u2 <- drop (i+1) us ]
pairOrdered (u1,u2) = case (idxs u1, idxs u2) of
([],_) -> True
(_,[]) -> True
(is1,is2) -> maximum is1 < minimum is2