22{-# LANGUAGE AllowAmbiguousTypes #-}
33module Test.QuickCheck.ParallelActions where
44
5-
5+ import Data.Set qualified as Set
6+ import Control.Monad
67import Control.Monad.Reader
78import Control.Monad.Writer
89import Data.Data
@@ -12,7 +13,7 @@ import Test.QuickCheck.Monadic
1213import Test.QuickCheck.StateModel.Variables
1314import Test.QuickCheck.StateModel
1415import Control.Concurrent
15- import Control.Arrow (first )
16+ import Control.Arrow (first , second )
1617import Data.Tree
1718
1819class RunModel state m => RunModelPar state m where
@@ -24,51 +25,49 @@ class RunModel state m => RunModelPar state m where
2425
2526data ParallelActions state =
2627 ParallelActions { linearActions :: Actions state
27- , threadATags :: [Int ]
28- , threadBTags :: [Int ]
28+ , threads :: [[Int ]]
2929 } deriving (Eq , Generic )
3030
3131commonActions :: ParallelActions state -> [Step state ]
3232commonActions 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
4342instance 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
5755instance 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
6566checkParallelActions :: 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
7372checkWellTypedness :: StateModel state => VarContext -> [Step state ] -> Bool
7473checkWellTypedness _ [] = 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
104101data 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 ()
146143runParActions 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]
201195prepend [] ts = ts
202196prepend (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
0 commit comments