-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathModel.lhs
108 lines (90 loc) · 4.18 KB
/
Model.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
> {-# LANGUAGE InstanceSigs #-}
> {-# LANGUAGE NamedFieldPuns #-}
> {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
> -- | An classic example of model-checking an implementation of a ring buffer.
> --
> -- * Inspired by [q-s-m example](https://github.com/stevana/quickcheck-state-machine/blob/master/test/CircularBuffer.hs)
> -- * Also described by John Hughes [here](https://publications.lib.chalmers.se/records/fulltext/232550/local_232550.pdf)
> module CircularBuffer.Model where
> import Control.Monad.State (MonadState (..), StateT)
> import Control.Monad.Trans (lift)
> import Data.Maybe (fromJust)
> import GHC.Generics (Generic)
> import CircularBuffer.Buffer (Buffer, getBuffer, lenBuffer, newBuffer, putBuffer)
> import Test.QuickCheck (Arbitrary (..), Property, getPositive, oneof)
> import Test.QuickCheck.DynamicLogic (DL, DynLogicModel (..), action, forAllDL)
> import Test.QuickCheck.Extras (runPropertyStateT)
> import Test.QuickCheck.Monadic (monadicIO)
> import Test.QuickCheck.Monadic qualified as QC
> import Test.QuickCheck.StateModel (Actions, Any (..), HasVariables (..), RunModel (..), StateModel (..), Var, counterexamplePost, runActions)
> -- * Model
> -- | A simple model of a `CircularBuffer` implemented using a `List`.
> data CircularBufferModel
> = NoBuffer
> | CircularBufferModel
> { size :: Int
> , buffer :: [Var Int]
> }
> deriving (Eq, Show, Generic)
> instance StateModel CircularBufferModel where
> data Action CircularBufferModel a where
> -- Create new buffer of given capacity.
> New :: Int -> Action CircularBufferModel ()
> -- Put an element at the top of the buffer.
> Put :: Int -> Action CircularBufferModel Int
> -- Get an element out of the bottom of the buffer.
> Get :: Action CircularBufferModel Int
> -- Get the number of elements in the buffer.
> Len :: Action CircularBufferModel Int
> arbitraryAction ctx NoBuffer = Some . New . getPositive <$> arbitrary
> arbitraryAction ctx CircularBufferModel{} =
> oneof
> [ Some . Put <$> arbitrary
> , pure $ Some Get
> , pure $ Some Len
> ]
> initialState = NoBuffer
> precondition NoBuffer New{} = True
> precondition CircularBufferModel{buffer} Get{} = length buffer > 0
> precondition CircularBufferModel{buffer, size} Put{} = length buffer < size
> precondition CircularBufferModel{} Len{} = True
> precondition _ _ = False
> nextState NoBuffer (New size) var = CircularBufferModel{size, buffer = mempty}
> nextState buf@CircularBufferModel{size, buffer} Put{} var = buf{buffer = var : buffer}
> nextState buf@CircularBufferModel{buffer} Get _ = buf{buffer = init buffer}
> nextState st _ _ = st
> shrinkAction _ _ = \case
> New n -> Some . New <$> [i | i <- shrink n, i > 0]
> Put n -> Some . Put <$> shrink n
> _ -> []
> deriving instance Show (Action CircularBufferModel a)
> deriving instance Eq (Action CircularBufferModel a)
> instance HasVariables (Action CircularBufferModel a) where
> getAllVariables = const mempty
> instance DynLogicModel CircularBufferModel where
> restricted _ = False
> -- * RunModel
> instance RunModel CircularBufferModel (StateT (Maybe Buffer) IO) where
> perform _st action _lookup =
> case action of
> New n -> lift (newBuffer n) >>= put . Just
> Put v -> get >>= (lift . putBuffer v . fromJust) >> pure v
> Get -> get >>= lift . getBuffer . fromJust
> Len -> get >>= lift . lenBuffer . fromJust
> postcondition (CircularBufferModel{buffer}, after) Get lookup res =
> let v = lookup (last buffer)
> in do
> counterexamplePost ("Expected: " <> show v <> ", got: " <> show res)
> pure $ v == res
> postcondition (CircularBufferModel{buffer}, after) Len lookup res = do
> let len = length buffer
> counterexamplePost $ "Expected; " <> show len <> ", got: " <> show res
> pure $ res == len
> postcondition _ _ _ _ = pure True
> prop_CircularBuffer :: Actions CircularBufferModel -> Property
> prop_CircularBuffer s =
> monadicIO $ do
> runPropertyStateT (runActions @_ @(StateT (Maybe Buffer) IO) s) Nothing
> QC.assert True
> propDL :: DL CircularBufferModel () -> Property
> propDL d = forAllDL d prop_CircularBuffer