mockcat-1.4.0.0: test/Property/ReinforcementProps.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
{-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}
{-# OPTIONS_GHC -fno-hpc #-}
{-# OPTIONS_GHC -Wno-deprecations #-}
module Property.ReinforcementProps (spec, prop_predicate_negative_not_counted, prop_lazy_partial_force_concurrency, prop_partial_order_interleaved_duplicates) where
import Test.QuickCheck
import Test.QuickCheck.Monadic (monadicIO, run, assert)
import Control.Exception (try, SomeException, evaluate)
import Control.Concurrent.Async (forConcurrently_)
import UnliftIO (withRunInIO)
import Test.MockCat hiding (any)
import Property.Generators (resetMockHistory)
import Control.Monad.IO.Class (liftIO)
import Test.Hspec (Spec, describe, it)
--------------------------------------------------------------------------------
-- 1. Predicate negative: failing inputs raise & are not counted
--------------------------------------------------------------------------------
prop_predicate_negative_not_counted :: Property
prop_predicate_negative_not_counted = forAll genVals $ \xs -> monadicIO $ do
let evens = length (filter even xs)
run $ withMock $ do
f <- mock (expect even "even" ~> True)
`expects` called (times evens)
outcomes <- liftIO $ mapM (\x -> (try (evaluate (f x)) :: IO (Either SomeException Bool))) xs
{- Analysis (optional checks) -}
liftIO $ do
let successes = length [ () | (x, Right _) <- zip xs outcomes, even x ]
oddSuccesses = [ x | (x, Right _) <- zip xs outcomes, odd x ]
failures = length [ () | (_, Left _) <- zip xs outcomes ]
odds = length (filter odd xs)
-- No odd value should succeed
if not (null oddSuccesses) then error "oddSuccesses not null" else pure ()
-- Failures count equals number of odd inputs
if failures /= odds then error "failures /= odds" else pure ()
-- Successes count equals even inputs
if successes /= evens then error "successes /= evens" else pure ()
assert True
where
genVals = resize 60 $ listOf (arbitrary :: Gen Int)
--------------------------------------------------------------------------------
-- 2. Lazy partial force with concurrency
--------------------------------------------------------------------------------
-- We reuse a unary typeclass mock to get laziness semantics (same pattern as LazyEvalProp).
class Monad m => ParLazyAction m where
parLazy :: Int -> m Int
makeAutoLiftMock [t|ParLazyAction|]
prop_lazy_partial_force_concurrency :: Property
prop_lazy_partial_force_concurrency = forAll genPlan $ \(arg, mask) -> monadicIO $ do
run resetMockHistory
let forcedCount = length (filter id mask)
run $ runMockT $ do
-- expectation: arg -> arg; count only forced executions
_parLazy ((param arg ~> arg))
`expects` do
called (times forcedCount)
-- prepare thunks (NOT executed yet)
let thunks = replicate (length mask) (parLazy arg)
withRunInIO $ \runIn -> do
forConcurrently_ (zip mask thunks) $ \(forceIt, action) ->
if forceIt then do
v <- runIn action
v `seq` pure ()
else pure () -- skip executing -> not counted
assert True
where
genPlan = do
size <- chooseInt (1,40)
arg <- arbitrary :: Gen Int
mask <- vectorOf size arbitrary
pure (arg, mask)
--------------------------------------------------------------------------------
-- 3. Interleaved duplicate partial-order spec (A B A pattern)
--------------------------------------------------------------------------------
prop_partial_order_interleaved_duplicates :: Property
prop_partial_order_interleaved_duplicates = forAll genPair $ \(a,b) -> a /= b ==> monadicIO $ do
-- Pattern a a b : [a,b] subsequence succeeds, [b,a] fails.
run $ withMock $ do
f <- mock (cases [ param a ~> True
, param a ~> True
, param b ~> True ])
`expects` calledInSequence [a, b]
liftIO $ f a `seq` f a `seq` f b `seq` pure ()
e <- run ((try $ withMock $ do
f <- mock (cases [ param a ~> True
, param a ~> True
, param b ~> True ])
`expects` calledInSequence [b, a]
liftIO $ f a `seq` f a `seq` f b `seq` pure ()
) :: IO (Either SomeException ()))
case e of
Left _ -> assert True
Right _ -> assert False
assert True
where
genPair = do
a <- arbitrary :: Gen Int
b <- arbitrary :: Gen Int
pure (a,b)
spec :: Spec
spec = do
describe "Property Reinforcement (Negative predicate / Partial force / Interleave)" $ do
it "predicate negative not counted" $ property prop_predicate_negative_not_counted
it "lazy partial force in concurrency counts only forced" $ property prop_lazy_partial_force_concurrency
it "interleaved duplicate partial order semantics" $ property prop_partial_order_interleaved_duplicates