|
2 | 2 | {-# LANGUAGE QuantifiedConstraints #-} |
3 | 3 | {-# LANGUAGE UndecidableInstances #-} |
4 | 4 |
|
5 | | --- | Simple (stateful) Model-Based Testing library for use with Haskell QuickCheck. |
| 5 | +-- | Model-Based Testing library for use with Haskell QuickCheck. |
6 | 6 | -- |
7 | 7 | -- This module provides the basic machinery to define a `StateModel` from which /traces/ can |
8 | 8 | -- be generated and executed against some /actual/ implementation code to define monadic `Property` |
@@ -32,6 +32,7 @@ module Test.QuickCheck.StateModel ( |
32 | 32 | runActions, |
33 | 33 | lookUpVar, |
34 | 34 | lookUpVarMaybe, |
| 35 | + viewAtType, |
35 | 36 | initialAnnotatedState, |
36 | 37 | computeNextState, |
37 | 38 | computePrecondition, |
@@ -192,6 +193,13 @@ newtype PostconditionM m a = PostconditionM {runPost :: WriterT (Endo Property, |
192 | 193 | instance MonadTrans PostconditionM where |
193 | 194 | lift = PostconditionM . lift |
194 | 195 |
|
| 196 | +evaluatePostCondition :: Monad m => PostconditionM m Bool -> PropertyM m () |
| 197 | +evaluatePostCondition post = do |
| 198 | + (b, (Endo mon, Endo onFail)) <- run . runWriterT . runPost $ post |
| 199 | + monitor mon |
| 200 | + unless b $ monitor onFail |
| 201 | + assert b |
| 202 | + |
195 | 203 | -- | Apply the property transformation to the property after evaluating |
196 | 204 | -- the postcondition. Useful for collecting statistics while avoiding |
197 | 205 | -- duplication between `monitoring` and `postcondition`. |
@@ -239,23 +247,6 @@ class (forall a. Show (Action state a), Monad m) => RunModel state m where |
239 | 247 | monitoringFailure :: state -> Action state a -> LookUp m -> Error state -> Property -> Property |
240 | 248 | monitoringFailure _ _ _ _ prop = prop |
241 | 249 |
|
242 | | -computePostcondition |
243 | | - :: forall m state a |
244 | | - . RunModel state m |
245 | | - => (state, state) |
246 | | - -> ActionWithPolarity state a |
247 | | - -> LookUp m |
248 | | - -> Either (Error state) (Realized m a) |
249 | | - -> PostconditionM m Bool |
250 | | -computePostcondition ss (ActionWithPolarity a p) l r |
251 | | - | p == PosPolarity = case r of |
252 | | - Right ra -> postcondition ss a l ra |
253 | | - -- NOTE: this is actually redundant as this handled |
254 | | - -- at the call site for this function, but this is |
255 | | - -- good hygiene? |
256 | | - Left _ -> pure False |
257 | | - | otherwise = postconditionOnFailure ss a l r |
258 | | - |
259 | 250 | type LookUp m = forall a. Typeable a => Var a -> Realized m a |
260 | 251 |
|
261 | 252 | type Env m = [EnvEntry m] |
@@ -515,47 +506,82 @@ runActions |
515 | 506 | ) |
516 | 507 | => Actions state |
517 | 508 | -> PropertyM m (Annotated state, Env m) |
518 | | -runActions (Actions_ rejected (Smart _ actions)) = loop initialAnnotatedState [] actions |
| 509 | +runActions (Actions_ rejected (Smart _ actions)) = do |
| 510 | + (finalState, env) <- runSteps initialAnnotatedState [] actions |
| 511 | + unless (null rejected) $ |
| 512 | + monitor $ |
| 513 | + tabulate "Actions rejected by precondition" rejected |
| 514 | + return (finalState, env) |
| 515 | + |
| 516 | +-- | Core function to execute a sequence of `Step` given some initial `Env`ironment |
| 517 | +-- and `Annotated` state. |
| 518 | +runSteps |
| 519 | + :: forall state m e |
| 520 | + . ( StateModel state |
| 521 | + , RunModel state m |
| 522 | + , e ~ Error state |
| 523 | + , forall a. IsPerformResult e a |
| 524 | + ) |
| 525 | + => Annotated state |
| 526 | + -> Env m |
| 527 | + -> [Step state] |
| 528 | + -> PropertyM m (Annotated state, Env m) |
| 529 | +runSteps s env [] = return (s, reverse env) |
| 530 | +runSteps s env ((v := act) : as) = do |
| 531 | + pre $ computePrecondition s act |
| 532 | + ret <- run $ performResultToEither <$> perform (underlyingState s) action (lookUpVar env) |
| 533 | + let name = show polar ++ actionName action |
| 534 | + monitor $ tabulate "Actions" [name] |
| 535 | + monitor $ tabulate "Action polarity" [show polar] |
| 536 | + case (polar, ret) of |
| 537 | + (PosPolarity, Left err) -> |
| 538 | + positiveActionFailed err |
| 539 | + (PosPolarity, Right val) -> do |
| 540 | + (s', env') <- positiveActionSucceeded ret val |
| 541 | + runSteps s' env' as |
| 542 | + (NegPolarity, _) -> do |
| 543 | + (s', env') <- negativeActionResult ret |
| 544 | + runSteps s' env' as |
519 | 545 | where |
520 | | - loop :: Annotated state -> Env m -> [Step state] -> PropertyM m (Annotated state, Env m) |
521 | | - loop _s env [] = do |
522 | | - unless (null rejected) $ |
523 | | - monitor $ |
524 | | - tabulate "Actions rejected by precondition" rejected |
525 | | - return (_s, reverse env) |
526 | | - loop s env ((v := act) : as) = do |
527 | | - pre $ computePrecondition s act |
528 | | - ret <- run $ performResultToEither <$> perform (underlyingState s) (polarAction act) (lookUpVar env) |
529 | | - let name = show (polarity act) ++ actionName (polarAction act) |
530 | | - monitor $ tabulate "Actions" [name] |
531 | | - monitor $ tabulate "Action polarity" [show $ polarity act] |
532 | | - if |
533 | | - | polarity act == PosPolarity |
534 | | - , Left err <- ret -> do |
535 | | - monitor $ |
536 | | - monitoringFailure @state @m |
537 | | - (underlyingState s) |
538 | | - (polarAction act) |
539 | | - (lookUpVar env) |
540 | | - err |
541 | | - stop False |
542 | | - | otherwise -> do |
543 | | - let var = unsafeCoerceVar v |
544 | | - s' = computeNextState s act var |
545 | | - env' |
546 | | - | Right val <- ret = (var :== val) : env |
547 | | - | otherwise = env |
548 | | - monitor $ monitoring @state @m (underlyingState s, underlyingState s') (polarAction act) (lookUpVar env') ret |
549 | | - (b, (Endo mon, Endo onFail)) <- |
550 | | - run |
551 | | - . runWriterT |
552 | | - . runPost |
553 | | - $ computePostcondition @m |
554 | | - (underlyingState s, underlyingState s') |
555 | | - act |
556 | | - (lookUpVar env) |
557 | | - ret |
558 | | - monitor mon |
559 | | - unless b $ monitor onFail |
560 | | - assert b |
561 | | - loop s' env' as |
| 546 | + polar = polarity act |
| 547 | + |
| 548 | + action = polarAction act |
| 549 | + |
| 550 | + positiveActionFailed err = do |
| 551 | + monitor $ |
| 552 | + monitoringFailure @state @m |
| 553 | + (underlyingState s) |
| 554 | + action |
| 555 | + (lookUpVar env) |
| 556 | + err |
| 557 | + stop False |
| 558 | + |
| 559 | + positiveActionSucceeded ret val = do |
| 560 | + (s', env', stateTransition) <- computeNewState ret |
| 561 | + evaluatePostCondition $ |
| 562 | + postcondition |
| 563 | + stateTransition |
| 564 | + action |
| 565 | + (lookUpVar env) |
| 566 | + val |
| 567 | + pure (s', env') |
| 568 | + |
| 569 | + negativeActionResult ret = do |
| 570 | + (s', env', stateTransition) <- computeNewState ret |
| 571 | + evaluatePostCondition $ |
| 572 | + postconditionOnFailure |
| 573 | + stateTransition |
| 574 | + action |
| 575 | + (lookUpVar env) |
| 576 | + ret |
| 577 | + pure (s', env') |
| 578 | + |
| 579 | + computeNewState ret = do |
| 580 | + let var = unsafeCoerceVar v |
| 581 | + s' = computeNextState s act var |
| 582 | + env' |
| 583 | + | Right val <- ret = (var :== val) : env |
| 584 | + | otherwise = env |
| 585 | + stateTransition = (underlyingState s, underlyingState s') |
| 586 | + monitor $ monitoring @state @m stateTransition action (lookUpVar env') ret |
| 587 | + pure (s', env', stateTransition) |
0 commit comments