Skip to content

Commit 9ee127e

Browse files
multiple threads + cleanup
1 parent 1cd8bba commit 9ee127e

File tree

4 files changed

+69
-73
lines changed

4 files changed

+69
-73
lines changed

quickcheck-dynamic/src/Test/QuickCheck/ParallelActions.hs

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
{-# LANGUAGE AllowAmbiguousTypes #-}
33
module Test.QuickCheck.ParallelActions where
44

5-
5+
import Data.Set qualified as Set
6+
import Control.Monad
67
import Control.Monad.Reader
78
import Control.Monad.Writer
89
import Data.Data
@@ -12,7 +13,7 @@ import Test.QuickCheck.Monadic
1213
import Test.QuickCheck.StateModel.Variables
1314
import Test.QuickCheck.StateModel
1415
import Control.Concurrent
15-
import Control.Arrow (first)
16+
import Control.Arrow (first, second)
1617
import Data.Tree
1718

1819
class RunModel state m => RunModelPar state m where
@@ -24,51 +25,49 @@ class RunModel state m => RunModelPar state m where
2425

2526
data ParallelActions state =
2627
ParallelActions { linearActions :: Actions state
27-
, threadATags :: [Int]
28-
, threadBTags :: [Int]
28+
, threads :: [[Int]]
2929
} deriving (Eq, Generic)
3030

3131
commonActions :: ParallelActions state -> [Step state]
3232
commonActions ParallelActions{linearActions = Actions steps, ..} =
33-
[ step | step@(v := _) <- steps, notElem (unsafeVarIndex v) $ threadATags ++ threadBTags ]
34-
35-
threadAActions :: ParallelActions state -> [Step state]
36-
threadAActions ParallelActions{linearActions = Actions steps, ..} =
37-
[ v := s{polarity = PosPolarity} | v := s <- steps, elem (unsafeVarIndex v) threadATags ]
33+
[ step | step@(v := _) <- steps, notElem (unsafeVarIndex v) $ concat threads ]
3834

39-
threadBActions :: ParallelActions state -> [Step state]
40-
threadBActions ParallelActions{linearActions = Actions steps, ..} =
41-
[ v := s{polarity = PosPolarity} | v := s <- steps, elem (unsafeVarIndex v) threadBTags ]
35+
threadActions :: ParallelActions state -> [[Step state]]
36+
threadActions ParallelActions{linearActions = Actions steps, ..} =
37+
[ [ v := s{polarity = PosPolarity}
38+
| v := s <- steps, elem (unsafeVarIndex v) thread ]
39+
| thread <- threads
40+
]
4241

4342
instance StateModel state => Show (ParallelActions state) where
4443
show pas =
45-
unlines [ "-- Common Prefix:"
46-
, showWithUsed (combineContexts (allVariables threadA) (allVariables threadB)) common
47-
, "-- Thread A"
48-
, show threadA
49-
, "-- Thread B"
50-
, show threadB
51-
]
44+
unlines $ [ "-- Common Prefix:"
45+
, showWithUsed (foldMap allVariables threads) common
46+
] ++ concat [ [ "-- Thread " ++ [n]
47+
, show thread
48+
]
49+
| (n, thread) <- zip ['A'..'Z'] threads
50+
]
5251
where
5352
common = Actions $ commonActions pas
54-
threadA = Actions $ threadAActions pas
55-
threadB = Actions $ threadBActions pas
53+
threads = Actions <$> threadActions pas
5654

5755
instance StateModel state => Arbitrary (ParallelActions state) where
5856
arbitrary = genParActions
5957

60-
shrink (ParallelActions actions as bs) =
61-
filter checkParallelActions $ [ ParallelActions actions as bs | actions <- shrink actions ] ++
62-
[ ParallelActions actions (tail as) bs | not $ null as ] ++
63-
[ ParallelActions actions as (tail bs) | not $ null bs ]
58+
shrink (ParallelActions actions trs) =
59+
filter checkParallelActions $ [ ParallelActions actions $ filter (not . null) $ map (filter (`Set.member` vars)) trs
60+
| actions <- shrink actions
61+
, let vars = unsafeIndexSet $ allVariables actions
62+
] ++
63+
[ ParallelActions actions $ trs ++ [ tr | not $ null tr ] ++ trs'
64+
| (trs, _:tr, trs') <- holes' trs ]
6465

6566
checkParallelActions :: StateModel state => ParallelActions state -> Bool
66-
checkParallelActions pas = checkWellTypedness commonCtx threadA && checkWellTypedness commonCtx threadB
67+
checkParallelActions pas = all (checkWellTypedness commonCtx) (threadActions pas)
6768
where
6869
commonCtx = allVariables common
6970
common = Actions $ commonActions pas
70-
threadA = threadAActions pas
71-
threadB = threadBActions pas
7271

7372
checkWellTypedness :: StateModel state => VarContext -> [Step state] -> Bool
7473
checkWellTypedness _ [] = True
@@ -82,24 +81,22 @@ genParActions = do
8281
split <- choose (max 0 (n - 20), n - 1)
8382
let (common, post) = splitAt split steps
8483
commonCtx = allVariables common
85-
(threadA, threadB) <- go post commonCtx commonCtx [] []
86-
return $ ParallelActions as threadA threadB
87-
where go :: [Step state] -> VarContext -> VarContext -> [Int] -> [Int] -> Gen ([Int], [Int])
88-
go [] _ctxA _ctxB tA tB = return (reverse tA, reverse tB)
89-
go ((v := a) : ss) ctxA ctxB tA tB = do
90-
let candidates = [ 'a' | a `wellTypedIn` ctxA ] ++ [ 'b' | a `wellTypedIn` ctxB ]
91-
i = unsafeVarIndex v
84+
tc <- choose (2, 5)
85+
threads <- go post $ replicate tc (commonCtx, [])
86+
return $ ParallelActions as $ filter (not . null) threads
87+
where go :: [Step state] -> [(VarContext, [Int])] -> Gen [[Int]]
88+
go [] trs = return $ map (reverse . snd) trs
89+
go ((v := a) : ss) trs = do
90+
let candidates = [ (ctx, tr, trs) | ((ctx, tr), trs) <- holes trs
91+
, a `wellTypedIn` ctx ]
9292
if null candidates
9393
-- This means we made a mistake earlier and split two actions whose
9494
-- result variables were used together later. At this point we just
9595
-- give up and don't extend the traces.
96-
then go [] ctxA ctxB tA tB
96+
then go [] trs
9797
else do
98-
c <- elements candidates
99-
case c of
100-
'a' -> go ss (extendContext ctxA v) ctxB (i : tA) tB
101-
'b' -> go ss ctxA (extendContext ctxB v) tA (i : tB)
102-
_ -> error "I'm the pope"
98+
(ctx, tr, trs) <- elements candidates
99+
go ss $ (extendContext ctx v, unsafeVarIndex v:tr) : trs
103100

104101
data TraceStep state m where
105102
TraceStep :: (Typeable a, Show a)
@@ -123,16 +120,16 @@ runTracing env ((v := ap):as) = do
123120
| otherwise = env
124121
(first (step :)) <$> runTracing env' as
125122

126-
class Monad m => ForkYou m where
123+
class Monad m => Forking m where
127124
forkThread :: m a -> m (m a)
128125

129-
instance ForkYou IO where
126+
instance Forking IO where
130127
forkThread io = do
131128
t <- newEmptyMVar
132129
forkIO $ io >>= putMVar t
133130
return $ takeMVar t
134131

135-
instance ForkYou m => ForkYou (ReaderT r m) where
132+
instance Forking m => Forking (ReaderT r m) where
136133
forkThread m = do
137134
reg <- ask
138135
lift $ fmap lift (forkThread $ runReaderT m reg)
@@ -141,22 +138,19 @@ runParActions :: ( StateModel state
141138
, RunModelPar state m
142139
, e ~ Error state m
143140
, forall a. IsPerformResult e a
144-
, ForkYou m
141+
, Forking m
145142
) => ParallelActions state -> PropertyM m ()
146143
runParActions pas = do
147144
(trC, env) <- run $ runTracing mempty $ commonActions pas
148-
joinA <- run $ forkThread $ runTracing env (threadAActions pas)
149-
joinB <- run $ forkThread $ runTracing env (threadBActions pas)
150-
(trA, _) <- run joinA
151-
(trB, _) <- run joinB
145+
joins <- mapM (run . forkThread . runTracing env) (threadActions pas)
146+
trs <- mapM (fmap fst . run) joins
152147
let used = varsUsedInActions $ linearActions pas
153148
monitor $ counterexample "-- Main thread:"
154149
monitorTrace used mempty trC
155-
monitor $ counterexample "\n-- Thread A:"
156-
monitorTrace used env trA
157-
monitor $ counterexample "\n-- Thread B:"
158-
monitorTrace used env trB
159-
let ilvs = prepend trC $ interleavings trA trB
150+
forM (zip ['A'..'Z'] trs) $ \ (n, tr) -> do
151+
monitor $ counterexample $ "\n-- Thread " ++ [n, ':']
152+
monitorTrace used env tr
153+
let ilvs = prepend trC $ interleavings trs
160154
monitor $ tabulate "TraceTree size" (map (bucket . length) ilvs)
161155
assert $ null ilvs || any (checkTrace initialAnnotatedState mempty) ilvs
162156
-- TODO: stats collection and cleanup
@@ -201,15 +195,21 @@ prepend :: [a] -> [Tree a] -> [Tree a]
201195
prepend [] ts = ts
202196
prepend (p:ps) ts = [Node p $ prepend ps ts]
203197

204-
interleavings :: [a] -> [a] -> [Tree a]
205-
interleavings [] bs = prepend bs []
206-
interleavings as [] = prepend as []
207-
interleavings (a:as) (b:bs) = [Node a (interleavings as (b:bs)), Node b (interleavings (a:as) bs)]
198+
interleavings :: [[a]] -> [Tree a]
199+
interleavings aas = do
200+
(a:as, os) <- holes aas
201+
pure $ Node a (interleavings (as:os))
202+
203+
holes :: [a] -> [(a, [a])]
204+
holes [] = []
205+
holes (a:as) = (a, as) : map (second (a:)) (holes as)
206+
207+
holes' :: [a] -> [([a], a, [a])]
208+
holes' [] = []
209+
holes' (a:as) = ([], a, as) : [ (a:bs, a', as') | (bs, a', as') <- holes' as ]
208210

209211
-- TODO:
210-
-- - Decide how much of the old QCD interface we can afford to break
211-
-- - Refactor normal QCD so that you don't have two implementations
212-
-- - More than two threads
213212
-- - More examples
213+
-- - Refactor normal QCD so that you don't have two implementations
214214
-- - Clean everything up
215215
-- - All the statistics and monitoring

quickcheck-dynamic/src/Test/QuickCheck/StateModel.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,11 @@ instance Eq (Actions state) where
352352
Actions as == Actions as' = as == as'
353353

354354
instance StateModel state => Show (Actions state) where
355-
show = showWithUsed emptyContext
355+
show = showWithUsed mempty
356356

357357
showWithUsed :: StateModel state => VarContext -> Actions state -> String
358358
showWithUsed ctx (Actions as) =
359-
let as' = WithUsedVars (combineContexts ctx $ varsUsedInActions (Actions as)) <$> as
359+
let as' = WithUsedVars (ctx <> varsUsedInActions (Actions as)) <$> as
360360
in intercalate "\n" $ zipWith (++) ("do " : repeat " ") (map show as' ++ ["pure ()" | null as'])
361361

362362
varsUsedInActions :: forall state. StateModel state => Actions state -> VarContext

quickcheck-dynamic/src/Test/QuickCheck/StateModel/Variables.hs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ module Test.QuickCheck.StateModel.Variables (
1616
isWellTyped,
1717
allVariables,
1818
isEmptyCtx,
19+
wellTypedIn,
1920
unsafeVarIndex,
2021
unsafeCoerceVar,
2122
unsafeNextVarIndex,
22-
wellTypedIn,
23-
combineContexts,
24-
emptyContext,
23+
unsafeIndexSet,
2524
) where
2625

2726
import Data.Data
@@ -114,6 +113,9 @@ instance Show VarContext where
114113
-- The use of typeRep here is on purpose to avoid printing `Var` unnecessarily.
115114
showBinding (Some v) = show v ++ " :: " ++ show (typeRep v)
116115

116+
unsafeIndexSet :: VarContext -> Set Int
117+
unsafeIndexSet (VarCtx ctx) = Set.map (\(Some v) -> unsafeVarIndex v) ctx
118+
117119
isEmptyCtx :: VarContext -> Bool
118120
isEmptyCtx (VarCtx ctx) = null ctx
119121

@@ -132,12 +134,6 @@ wellTypedIn a ctx = all (\(Some v) -> v `isWellTyped` ctx) (getAllVariables a)
132134
extendContext :: (Typeable a, Show a) => VarContext -> Var a -> VarContext
133135
extendContext (VarCtx ctx) v = VarCtx $ Set.insert (Some v) ctx
134136

135-
emptyContext :: VarContext
136-
emptyContext = VarCtx mempty
137-
138-
combineContexts :: VarContext -> VarContext -> VarContext
139-
combineContexts (VarCtx c0) (VarCtx c1) = VarCtx $ c0 <> c1
140-
141137
allVariables :: HasVariables a => a -> VarContext
142138
allVariables = VarCtx . getAllVariables
143139

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ showDictAction KillThread{} = ShowDict
181181
instance GoodMonad m => DynLogicModel (RegState m) where
182182
restricted _ = False
183183

184-
instance ForkYou (IOSim s) where
184+
instance Forking (IOSim s) where
185185
forkThread io = do
186186
t <- atomically newEmptyTMVar
187187
forkIO $ io >>= atomically . putTMVar t

0 commit comments

Comments
 (0)