@@ -68,6 +68,7 @@ import Test.QuickCheck.Monadic
6868import Test.QuickCheck.StateModel.Variables
6969import Control.Concurrent
7070import Control.Monad.Identity
71+ import Control.Arrow (first )
7172
7273-- | The typeclass users implement to define a model against which to validate some implementation.
7374--
@@ -259,6 +260,9 @@ class RunModel state m => RunModelPar state m where
259260
260261 postconditionPar :: (state , state ) -> Action state a -> LookUp -> a -> PostconditionM Identity Bool
261262
263+ postconditionOnFailurePar :: (state , state ) -> Action state a -> LookUp -> Either (Error state m ) a -> PostconditionM Identity Bool
264+ postconditionOnFailurePar _ _ _ _ = pure True
265+
262266 monitoringPar :: Action state a -> LookUp -> Either (Error state m ) a -> Property -> Property
263267 monitoringPar _ _ _ = id
264268
@@ -423,10 +427,10 @@ moreActions r p =
423427
424428-- NOTE: indexed on state for forwards compatibility, e.g. when we
425429-- want to give an explicit initial state
426- data Options state = Options { actionLengthMultiplier :: Rational , negativeActions :: Bool }
430+ data Options state = Options { actionLengthMultiplier :: Rational }
427431
428432defaultOptions :: Options state
429- defaultOptions = Options {actionLengthMultiplier = 1 , negativeActions = True }
433+ defaultOptions = Options {actionLengthMultiplier = 1 }
430434
431435-- | Generate arbitrary actions with the `GenActionsOptions`. More flexible than using the type-based
432436-- modifiers.
@@ -454,15 +458,14 @@ generateActionsWithOptions Options{..} = do
454458 return (reverse steps, rejected)
455459 else return (reverse steps, rejected)
456460 where
457- correctPolarity polarity = negativeActions || polarity == PosPolarity
458461 satisfyPrecondition = sized $ \ n -> go n (2 * n) [] -- idea copied from suchThatMaybe
459462 go m n rej
460463 | m > n = return (Nothing , rej)
461464 | otherwise = do
462465 a <- resize m $ computeArbitraryAction s
463466 case a of
464467 Some act@ ActionWithPolarity {.. } ->
465- if computePrecondition s act && correctPolarity polarity
468+ if computePrecondition s act
466469 then return (Just (Some act), rej)
467470 else go (m + 1 ) n (actionName polarAction : rej)
468471
@@ -693,8 +696,6 @@ instance StateModel state => Show (ParallelActions state) where
693696 , show threadA
694697 , " -- Thread B"
695698 , show threadB
696- , " -- Indices"
697- , show (threadATags pas, threadBTags pas)
698699 ]
699700 where
700701 common = Actions $ commonActions pas
@@ -705,13 +706,24 @@ instance StateModel state => Arbitrary (ParallelActions state) where
705706 arbitrary = genParActions
706707
707708 shrink (ParallelActions actions as bs) =
708- [ ParallelActions actions as bs | actions <- shrinkActionsWithOptions opts actions ]
709- where opts = defaultOptions{ negativeActions = False }
709+ filter checkParallelActions [ ParallelActions actions as bs | actions <- QC. shrink actions ]
710+
711+ checkParallelActions :: StateModel state => ParallelActions state -> Bool
712+ checkParallelActions pas = checkWellTypedness commonCtx threadA && checkWellTypedness commonCtx threadB
713+ where
714+ commonCtx = allVariables common
715+ common = Actions $ commonActions pas
716+ threadA = threadAActions pas
717+ threadB = threadBActions pas
718+
719+ checkWellTypedness :: StateModel state => VarContext -> [Step state ] -> Bool
720+ checkWellTypedness _ [] = True
721+ checkWellTypedness ctx ((v := a) : ss) = a `wellTypedIn` ctx && checkWellTypedness (extendContext ctx v) ss
710722
711723genParActions :: forall state . StateModel state => Gen (ParallelActions state )
712724genParActions = do
713725 -- The ~ works around a bug in ghc (https://gitlab.haskell.org/ghc/ghc/-/issues/22004) (which is not in all ghc versions ?!)
714- as@ (~ (Actions steps)) <- generateActionsWithOptions @ state $ defaultOptions { negativeActions = False }
726+ as@ (~ (Actions steps)) <- QC. arbitrary
715727 split <- QC. choose (0 , length steps - 1 )
716728 let (common, post) = splitAt split steps
717729 commonCtx = allVariables common
@@ -746,15 +758,14 @@ type Trace state m = [TraceStep state m]
746758runTracing :: ( RunModelPar state m
747759 , e ~ Error state m
748760 , forall a . IsPerformResult e a
749- ) => Env -> [Step state ] -> m (Trace state m )
750- runTracing env [] = return []
761+ ) => Env -> [Step state ] -> m (Trace state m , Env )
762+ runTracing env [] = return ( [] , env)
751763runTracing env ((v := ap): as) = do
752764 r <- performResultToEither <$> performPar (polarAction ap) (lookUpVar env)
753765 let step = TraceStep r v ap
754- case r of
755- -- Fail early because we don't support negative actions
756- Left err -> return [step]
757- Right res -> (step : ) <$> runTracing ((v :== res) : env) as
766+ env' | Right val <- r = (v :== val) : env
767+ | otherwise = env
768+ (first (step : )) <$> runTracing env' as
758769
759770class Monad m => ForkYou m where
760771 forkThread :: m a -> m (m a )
@@ -777,40 +788,51 @@ runParActions :: ( StateModel state
777788 , ForkYou m
778789 ) => ParallelActions state -> PropertyM m ()
779790runParActions pas = do
780- (s, env) <- runActions $ Actions $ commonActions pas
791+ monitor $ counterexample " -- Monitoring main thread"
792+ (trC, env) <- run $ runTracing mempty $ commonActions pas
781793 joinA <- run $ forkThread $ runTracing env (threadAActions pas)
782794 joinB <- run $ forkThread $ runTracing env (threadBActions pas)
783- trA <- run joinA
784- trB <- run joinB
785- monitor $ monitorTrace env trA
786- monitor $ monitorTrace env trB
787- let ilvs = interleavings trA trB
788- assert $ any (checkTrace s env) ilvs
795+ (trA, _) <- run joinA
796+ (trB, _) <- run joinB
797+ monitor $ counterexample " -- Monitoring thread A"
798+ monitorTrace env trA
799+ monitor $ counterexample " -- Monitoring thread B"
800+ monitorTrace env trB
801+ let ilvs = map (trC ++ ) $ interleavings trA trB
802+ assert $ any (checkTrace initialAnnotatedState mempty ) ilvs
789803 -- TODO: stats collection and cleanup
790804
791805monitorTrace :: forall state m . (StateModel state , RunModelPar state m )
792- => Env -> Trace state m -> Property -> Property
793- monitorTrace _env [] = id
794- monitorTrace env (TraceStep r v step : tr) =
795- monitorTrace env' tr . monitoringPar @ state @ m (polarAction step) (lookUpVar env) r
806+ => Env -> Trace state m -> PropertyM m ()
807+ monitorTrace _env [] = pure ()
808+ monitorTrace env (TraceStep r v step : tr) = do
809+ monitor $ monitoringPar @ state @ m (polarAction step) (lookUpVar env) r
810+ monitorTrace env' tr
796811 where
797812 env' | Right val <- r = (v :== val) : env
798813 | otherwise = env
799814
800815checkTrace :: forall state m . (StateModel state , RunModelPar state m ) => Annotated state -> Env -> Trace state m -> Bool
801816checkTrace _ _ [] = True
802- -- NOTE that we don't support negative actions
803- checkTrace _ _ (TraceStep Left {} _ _ : _) = False
804- checkTrace s env (TraceStep (Right val) v act : tr) =
805- let pre = computePrecondition s act
806- s' = computeNextState s act v
807- env' = (v :== val) : env
808- (b, _) = runWriter . runPost $ postconditionPar @ state @ m
809- (underlyingState s, underlyingState s')
810- (polarAction act)
811- (lookUpVar env')
812- val
813- in pre && b && checkTrace s' env' tr
817+ checkTrace s env (TraceStep r v (ActionWithPolarity a _) : tr) =
818+ -- NOTE: we need to re-compute the polarity of `a` here because it may be that the failure can be explained,
819+ -- but only by the action failing when it was previous successful
820+ let act = actionWithPolarity s a
821+ s' = computeNextState s act v
822+ env' | Right val <- r = (v :== val) : env
823+ | otherwise = env
824+ checkPost | Right val <- r, polarity act == PosPolarity = fst . runWriter . runPost $ postconditionPar @ state @ m
825+ (underlyingState s, underlyingState s')
826+ (polarAction act)
827+ (lookUpVar env')
828+ val
829+ | Left {} <- r, polarity act == PosPolarity = False
830+ | otherwise = fst . runWriter . runPost $ postconditionOnFailurePar @ state @ m
831+ (underlyingState s, underlyingState s')
832+ (polarAction act)
833+ (lookUpVar env')
834+ r
835+ in computePrecondition s act && checkPost && checkTrace s' env' tr
814836
815837interleavings :: [a ] -> [a ] -> [[a ]]
816838interleavings [] bs = [bs]
@@ -822,6 +844,4 @@ interleavings (a:as) (b:bs) =
822844-- TODO:
823845-- - Negative actions (because the actual interleaving might involve legitimate failures
824846-- even if the linear sequence doesn't)
825- -- - Remove negativeActions flag
826- -- - Shrinking
827- -- - Check scoping after shrinking linear actions
847+ -- - Check negative postcondition and correct precondition in `checkTrace`
0 commit comments