Skip to content

Commit 79eba84

Browse files
committed
QLS: generalise and use runActionsBracket for IO and IOSim
1 parent 147a775 commit 79eba84

File tree

2 files changed

+70
-66
lines changed

2 files changed

+70
-66
lines changed

test/Test/Database/LSMTree/StateMachine.hs

Lines changed: 34 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ import Control.ActionRegistry (AbortActionRegistryError (..),
5353
import Control.Concurrent.Class.MonadMVar.Strict
5454
import Control.Concurrent.Class.MonadSTM.Strict
5555
import Control.Exception (assert)
56-
import Control.Monad (forM_, void, (<=<))
56+
import Control.Monad (forM_, (<=<))
57+
import Control.Monad.Class.MonadST (MonadST)
5758
import Control.Monad.Class.MonadThrow (Exception (..), Handler (..),
58-
MonadCatch (..), MonadThrow (..), SomeException, catches,
59-
displayException, fromException)
59+
MonadCatch (..), MonadMask, MonadThrow (..), SomeException,
60+
catches, displayException, fromException)
6061
import Control.Monad.IOSim
6162
import Control.Monad.Primitive
6263
import Control.Monad.Reader (ReaderT (..))
63-
import qualified Control.Monad.ST.Lazy as ST
6464
import Control.RefCount (RefException, checkForgottenRefs,
6565
ignoreForgottenRefs)
6666
import Control.Tracer (Tracer, nullTracer)
@@ -120,10 +120,6 @@ import Test.Database.LSMTree.StateMachine.Op (HasBlobRef (getBlobRef),
120120
Op (..))
121121
import qualified Test.QuickCheck as QC
122122
import Test.QuickCheck (Arbitrary, Gen, Property)
123-
import qualified Test.QuickCheck.Extras as QD
124-
import qualified Test.QuickCheck.Monadic as QC
125-
import Test.QuickCheck.Monadic (PropertyM)
126-
import qualified Test.QuickCheck.StateModel as QD
127123
import Test.QuickCheck.StateModel hiding (Var)
128124
import Test.QuickCheck.StateModel.Lockstep
129125
import qualified Test.QuickCheck.StateModel.Lockstep.Defaults as Lockstep.Defaults
@@ -365,53 +361,37 @@ propLockstep_RealImpl_MockFS_IO tr cleanupFlag fsFlag refsFlag (QC.Fixed salt) =
365361
)
366362
tagFinalState'
367363

368-
-- We can not use @bracket@ inside @PropertyM@, so @acquire_RealImpl_MockFS@ and
369-
-- @release_RealImpl_MockFS@ are not run in a masked state and it is not
370-
-- guaranteed that the latter runs if the former succeeded. Therefore, if
371-
-- @runActions@ fails (with exceptions), then not having @bracket@ might lead to
372-
-- more exceptions, which can obfuscate the original reason that the property
373-
-- failed. Because of this, if @prop@ fails, it's probably best to also try
374-
-- running the @IO@ version of this property with the failing seed, and compare
375-
-- the counterexamples to see which one is more interesting.
376364
propLockstep_RealImpl_MockFS_IOSim ::
377365
(forall s. Tracer (IOSim s) R.LSMTreeTrace)
378366
-> CheckCleanup
379367
-> CheckFS
380368
-> CheckRefs
381369
-> QC.Fixed R.Salt
370+
-> Actions (Lockstep (ModelState (IOSim RealWorld) R.Table))
382371
-> QC.Property
383372
propLockstep_RealImpl_MockFS_IOSim tr cleanupFlag fsFlag refsFlag (QC.Fixed salt) =
384-
flip QC.monadic prop $ \x -> QC.ioProperty $ do
385-
trac <- ST.stToIO $ runSimTraceST x
386-
case traceResult False trac of
387-
Left e -> pure $ QC.counterexample (show e) False
388-
Right p -> pure p
389-
where
390-
prop :: forall s. Typeable s => PropertyM (IOSim s) Property
391-
prop = do
392-
actions <- QC.pick QC.arbitrary
393-
(fsVar, session, errsVar, logVar) <- QC.run (acquire_RealImpl_MockFS tr salt)
394-
faultsVar <- QC.run $ newMutVar []
395-
let
396-
env :: RealEnv R.Table (IOSim s)
397-
env = RealEnv {
398-
envSession = session
399-
, envHandlers = realErrorHandlers @(IOSim s)
400-
, envErrors = errsVar
401-
, envErrorsLog = logVar
402-
, envInjectFaultResults = faultsVar
403-
}
404-
void $ QD.runPropertyReaderT
405-
(QD.runActions @(Lockstep (ModelState (IOSim s) R.Table)) actions)
406-
env
407-
faults <- QC.run $ readMutVar faultsVar
408-
p <- QC.run $ propCleanup cleanupFlag $
409-
release_RealImpl_MockFS fsFlag (fsVar, session, errsVar, logVar)
410-
p' <- QC.run $ propRefs refsFlag
411-
pure
412-
$ tagFinalState actions tagFinalState'
413-
$ QC.tabulate "Fault results" (fmap show faults)
414-
$ p QC..&&. p'
373+
runActionsBracket
374+
(Proxy @(ModelState (IOSim RealWorld) R.Table))
375+
cleanupFlag
376+
refsFlag
377+
(acquire_RealImpl_MockFS tr salt)
378+
(release_RealImpl_MockFS fsFlag)
379+
(\r (_, session, errsVar, logVar) -> do
380+
faultsVar <- newMutVar []
381+
let
382+
env :: RealEnv R.Table (IOSim RealWorld)
383+
env = RealEnv {
384+
envSession = session
385+
, envHandlers = realErrorHandlers @(IOSim RealWorld)
386+
, envErrors = errsVar
387+
, envErrorsLog = logVar
388+
, envInjectFaultResults = faultsVar
389+
}
390+
prop <- runReaderT r env
391+
faults <- readMutVar faultsVar
392+
pure $ QC.tabulate "Fault results" (fmap show faults) prop
393+
)
394+
tagFinalState'
415395

416396
acquire_RealImpl_MockFS ::
417397
R.IOLike m
@@ -2862,18 +2842,21 @@ tagFinalState' (getModel -> ModelState finalState finalStats) = concat [
28622842
-- count how often something happens over the course of running these actions,
28632843
-- then we would want to only tag the final state, not intermediate steps.
28642844
runActionsBracket ::
2865-
forall state st m e prop. (
2845+
forall state st m n e prop. (
28662846
RunLockstep state m
28672847
, e ~ Error (Lockstep state) m
28682848
, forall a. IsPerformResult e a
28692849
, QC.Testable prop
2850+
, MonadMask n
2851+
, MonadST n
2852+
, QLS.IOProperty n
28702853
)
28712854
=> Proxy state
28722855
-> CheckCleanup
28732856
-> CheckRefs
2874-
-> IO st
2875-
-> (st -> IO prop)
2876-
-> (m QC.Property -> st -> IO QC.Property)
2857+
-> n st
2858+
-> (st -> n prop)
2859+
-> (m QC.Property -> st -> n QC.Property)
28772860
-> (Lockstep state -> [(String, [FinalTag])])
28782861
-> Actions (Lockstep state) -> QC.Property
28792862
runActionsBracket p cleanupFlag refsFlag init cleanup runner tagger actions =

test/Test/Util/QLS.hs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
-- TODO: it might be nice to upstream these utilities to @quickcheck-lockstep@
77
-- at some point.
88
module Test.Util.QLS (
9-
runActionsBracket
9+
IOProperty (..)
10+
, runActionsBracket
1011
) where
1112

1213
import Prelude hiding (init)
1314

14-
import Control.Exception
1515
import Control.Monad (void)
16+
import Control.Monad.Class.MonadST
17+
import Control.Monad.Class.MonadThrow
18+
import Control.Monad.IOSim (IOSim, runSimTraceST, traceResult)
19+
import Control.Monad.Primitive (RealWorld)
20+
import qualified Control.Monad.ST.Lazy as ST
1621
import Data.Typeable
1722
import qualified Test.QuickCheck as QC
1823
import Test.QuickCheck (Property, Testable)
@@ -21,41 +26,57 @@ import qualified Test.QuickCheck.StateModel as StateModel
2126
import Test.QuickCheck.StateModel hiding (runActions)
2227
import Test.QuickCheck.StateModel.Lockstep
2328

29+
class IOProperty m where
30+
ioProperty :: m Property -> Property
31+
32+
instance IOProperty IO where
33+
ioProperty = QC.ioProperty
34+
35+
instance IOProperty (IOSim RealWorld) where
36+
ioProperty x = QC.ioProperty $ do
37+
trac <- ST.stToIO $ runSimTraceST x
38+
case traceResult False trac of
39+
Left e -> pure $ QC.counterexample (show e) False
40+
Right p -> pure p
41+
2442
runActionsBracket ::
2543
( RunLockstep state m
2644
, e ~ Error (Lockstep state) m
2745
, forall a. IsPerformResult e a
2846
, Testable prop
47+
, MonadMask n
48+
, MonadST n
49+
, IOProperty n
2950
)
3051
=> Proxy state
31-
-> IO st -- ^ Initialisation
32-
-> (st -> IO prop) -- ^ Cleanup
33-
-> (m Property -> st -> IO Property) -- ^ Runner
52+
-> n st -- ^ Initialisation
53+
-> (st -> n prop) -- ^ Cleanup
54+
-> (m Property -> st -> n Property) -- ^ Runner
3455
-> Actions (Lockstep state)
3556
-> Property
3657
runActionsBracket _ init cleanup runner actions =
3758
monadicBracketIO init cleanup runner $
3859
void $ StateModel.runActions actions
3960

4061
ioPropertyBracket ::
41-
(Testable a, Testable b)
42-
=> IO st
43-
-> (st -> IO b)
44-
-> (m a -> st -> IO a)
62+
(Testable a, Testable b, MonadMask n, MonadST n, IOProperty n)
63+
=> n st
64+
-> (st -> n b)
65+
-> (m a -> st -> n a)
4566
-> m a
4667
-> Property
4768
ioPropertyBracket init cleanup runner prop =
48-
QC.ioProperty $ mask $ \restore -> do
69+
ioProperty $ mask $ \restore -> do
4970
st <- init
5071
a <- restore (runner prop st) `onException` cleanup st
5172
b <- cleanup st
5273
pure $ a QC..&&. b
5374

54-
monadicBracketIO :: forall st a b m.
55-
(Monad m, Testable a, Testable b)
56-
=> IO st
57-
-> (st -> IO b)
58-
-> (m Property -> st -> IO Property)
75+
monadicBracketIO :: forall st a b m n.
76+
(Monad m, Testable a, Testable b, MonadMask n, MonadST n, IOProperty n)
77+
=> n st
78+
-> (st -> n b)
79+
-> (m Property -> st -> n Property)
5980
-> PropertyM m a
6081
-> Property
6182
monadicBracketIO init cleanup runner =

0 commit comments

Comments
 (0)