Skip to content

Commit 98a84f0

Browse files
WIP iosim stuff
1 parent 0125c5d commit 98a84f0

File tree

2 files changed

+86
-39
lines changed

2 files changed

+86
-39
lines changed

quickcheck-dynamic/test/Spec/DynamicLogic/Counters.hs

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
{-# LANGUAGE NamedFieldPuns #-}
2+
{-# LANGUAGE ImpredicativeTypes #-}
23

34
-- | Define several variant models of /counters/ which are useful to
45
-- test or use examples for various behaviours of the runtime.
56
module Spec.DynamicLogic.Counters where
67

7-
import Control.Concurrent
88
import Control.Monad.Reader
9-
import Data.IORef
10-
import Test.QuickCheck (frequency, Property)
9+
import Test.QuickCheck hiding (Some)
10+
import Test.QuickCheck.Gen.Unsafe
1111
import Test.QuickCheck.StateModel
1212
import Test.QuickCheck.ParallelActions
1313
import Test.QuickCheck.Extras
1414
import Test.QuickCheck.Monadic
1515

16+
import Control.Concurrent.Class.MonadSTM
17+
import Control.Monad.Class.MonadFork
18+
import Control.Monad.Class.MonadThrow
19+
import Control.Monad.Class.MonadTimer
20+
import Control.Monad.Class.MonadTest
21+
import Control.Monad.IOSim
22+
1623
-- A very simple model with a single action that always succeed in
1724
-- predictable way. This model is useful for testing the runtime.
1825
newtype SimpleCounter = SimpleCounter {count :: Int}
@@ -33,10 +40,13 @@ instance StateModel SimpleCounter where
3340

3441
nextState SimpleCounter{count} IncSimple _ = SimpleCounter (count + 1)
3542

36-
instance RunModel SimpleCounter (ReaderT (IORef Int) IO) where
43+
instance RunModel SimpleCounter (ReaderT (CounterState IO) IO) where
3744
perform _ IncSimple _ = do
38-
ref <- ask
39-
lift $ atomicModifyIORef' ref (\count -> (succ count, count))
45+
ref <- asks counterState
46+
lift $ atomically $ do
47+
c <- readTVar ref
48+
writeTVar ref (c + 1)
49+
return c
4050

4151
-- A very simple model with a single action whose postcondition fails in a
4252
-- predictable way. This model is useful for testing the runtime.
@@ -58,10 +68,13 @@ instance StateModel FailingCounter where
5868

5969
nextState FailingCounter{failingCount} Inc' _ = FailingCounter (failingCount + 1)
6070

61-
instance RunModel FailingCounter (ReaderT (IORef Int) IO) where
71+
instance RunModel FailingCounter (ReaderT (CounterState IO) IO) where
6272
perform _ Inc' _ = do
63-
ref <- ask
64-
lift $ atomicModifyIORef' ref (\count -> (succ count, count))
73+
ref <- asks counterState
74+
lift $ atomically $ do
75+
c <- readTVar ref
76+
writeTVar ref (c + 1)
77+
return c
6578

6679
postcondition (_, FailingCounter{failingCount}) _ _ _ = pure $ failingCount < 4
6780

@@ -86,37 +99,74 @@ instance StateModel Counter where
8699
nextState (Counter n) Inc _ = Counter (n + 1)
87100
nextState _ Reset _ = Counter 0
88101

89-
instance RunModel Counter (ReaderT (IORef Int) IO) where
102+
newtype CounterState m = CounterState { counterState :: TVar m Int }
103+
104+
setupCounterState :: MonadSTM m => m (CounterState m)
105+
setupCounterState = CounterState <$> atomically (newTVar 0)
106+
107+
instance (MonadSTM m) => RunModel Counter (ReaderT (CounterState m) m) where
90108
perform _ Inc _ = do
91-
ref <- ask
109+
ref <- asks counterState
92110
lift $ do
93-
n <- readIORef ref
94-
threadDelay 1000
95-
writeIORef ref (n + 1)
111+
n <- atomically $ readTVar ref
112+
atomically $ writeTVar ref (n + 1)
96113
perform _ Reset _ = do
97-
ref <- ask
114+
ref <- asks counterState
98115
lift $ do
99-
n <- readIORef ref
100-
threadDelay 1000
101-
writeIORef ref 0
116+
n <- atomically $ readTVar ref
117+
atomically $ writeTVar ref 0
102118
pure n
103119

104120
postcondition (Counter n, _) Reset _ res = pure $ n == res
105121
postcondition _ _ _ _ = pure True
106122

107-
instance RunModelPar Counter (ReaderT (IORef Int) IO) where
123+
instance MonadSTM m => RunModelPar Counter (ReaderT (CounterState m) m) where
108124
performPar Inc _ = do
109-
ref <- ask
110-
-- lift $ atomicModifyIORef ref (\ c -> (c+1, ()))
125+
ref <- asks counterState
126+
-- lift $ atomically $ modifyTVar ref (\ c -> (c+1, ()))
111127
lift $ do
112-
n <- readIORef ref
113-
threadDelay 5000
114-
writeIORef ref (n + 1)
128+
n <- atomically $ readTVar ref
129+
atomically $ writeTVar ref (n + 1)
115130
performPar Reset _ = do
116-
ref <- ask
117-
lift $ atomicModifyIORef ref (\ c -> (0, c))
131+
ref <- asks counterState
132+
lift $ atomically $ do
133+
c <- readTVar ref
134+
writeTVar ref 0
135+
return c
136+
137+
prop_counter :: Actions Counter -> Property
138+
prop_counter as = monadicIO $ do
139+
ref <- lift $ atomically $ newTVar (0 :: Int)
140+
runPropertyReaderT (runActions as) (CounterState ref :: CounterState IO)
141+
assert True
118142

119143
prop_counter_par :: ParallelActions Counter -> Property
120144
prop_counter_par as = always 10 $ monadicIO $ do
121-
ref <- lift $ newIORef (0 :: Int)
145+
ref <- lift setupCounterState
122146
runPropertyReaderT (runParActions as) ref
147+
148+
prop_counter_parIOSimPor :: ParallelActions Counter -> Property
149+
prop_counter_parIOSimPor as =
150+
monadicIOSimPOR_ prop
151+
where
152+
prop :: forall s. PropertyM (IOSim s) ()
153+
prop = do
154+
ref <- lift $ atomically $ newTVar (0 :: Int)
155+
lift $ exploreRaces
156+
runPropertyReaderT (runParActions as) (CounterState ref :: CounterState (IOSim s))
157+
158+
monadicIOSimPOR_ :: Testable a => (forall s. PropertyM (IOSim s) a) -> Property
159+
monadicIOSimPOR_ prop = forAllBlind prop' $ \ p -> exploreSimTrace id p $ \ _ tr ->
160+
either (flip counterexample False . show) id $ traceResult False tr
161+
where
162+
prop' :: Gen (forall s. IOSim s Property)
163+
prop' = do
164+
Capture eval <- capture
165+
pure $ eval $ monadic' prop
166+
167+
instance Forking (IOSim s) where
168+
forkThread io = do
169+
t <- atomically newEmptyTMVar
170+
forkIO $ io >>= atomically . putTMVar t
171+
return $ atomically $ takeTMVar t
172+

quickcheck-dynamic/test/Test/QuickCheck/StateModelSpec.hs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
module Test.QuickCheck.StateModelSpec where
55

66
import Control.Monad.Reader (lift)
7-
import Data.IORef (newIORef)
87
import Data.List (isInfixOf)
9-
import Spec.DynamicLogic.Counters (Counter (..), FailingCounter, SimpleCounter (..))
10-
import Test.QuickCheck (Property, Result (..), Testable, chatty, checkCoverage, choose, counterexample, cover, noShrinking, property, stdArgs)
8+
import Spec.DynamicLogic.Counters (FailingCounter, SimpleCounter (..), prop_counter, prop_counter_parIOSimPor, setupCounterState)
9+
import Test.QuickCheck
1110
import Test.QuickCheck.Extras (runPropertyReaderT)
1211
import Test.QuickCheck.Monadic (assert, monadicIO, monitor, pick)
1312
import Test.QuickCheck.StateModel (
@@ -43,30 +42,28 @@ tests =
4342
, testProperty
4443
"moreActions introduces long sequences of actions"
4544
prop_longSequences
45+
, testProperty
46+
"IOSimPor finds counterexample in parallel counters"
47+
$ expectFailure prop_counter_parIOSimPor
48+
4649
]
4750

4851
captureTerminal :: Testable p => p -> IO Result
4952
captureTerminal p =
5053
withState stdArgs{chatty = False} $ \st ->
5154
test st (property p)
5255

53-
prop_counter :: Actions Counter -> Property
54-
prop_counter as = monadicIO $ do
55-
ref <- lift $ newIORef (0 :: Int)
56-
runPropertyReaderT (runActions as) ref
57-
assert True
58-
5956
prop_returnsFinalState :: Actions SimpleCounter -> Property
6057
prop_returnsFinalState actions@(Actions as) =
6158
monadicIO $ do
62-
ref <- lift $ newIORef (0 :: Int)
59+
ref <- lift $ setupCounterState
6360
(s, _) <- runPropertyReaderT (runActions actions) ref
6461
assert $ count (underlyingState s) == length as
6562

6663
prop_variablesIndicesAre1Based :: Actions SimpleCounter -> Property
6764
prop_variablesIndicesAre1Based actions@(Actions as) =
6865
noShrinking $ monadicIO $ do
69-
ref <- lift $ newIORef (0 :: Int)
66+
ref <- lift setupCounterState
7067
(_, env) <- runPropertyReaderT (runActions actions) ref
7168
act <- pick $ choose (0, length as - 1)
7269
monitor $
@@ -81,7 +78,7 @@ prop_variablesIndicesAre1Based actions@(Actions as) =
8178
prop_failsOnPostcondition :: Actions FailingCounter -> Property
8279
prop_failsOnPostcondition actions =
8380
monadicIO $ do
84-
ref <- lift $ newIORef (0 :: Int)
81+
ref <- lift setupCounterState
8582
runPropertyReaderT (runActions actions) ref
8683
assert True
8784

0 commit comments

Comments
 (0)