This repository has been archived by the owner on Jun 18, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 25
/
MemoryReference.hs
300 lines (263 loc) · 10.9 KB
/
MemoryReference.hs
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module MemoryReference
( prop_sequential
, prop_runSavedCommands
, prop_parallel
, prop_parallel'
, prop_nparallel
, prop_precondition
, prop_existsCommands
, Bug(..)
, prop_pairs_shrink_parallel_equivalence
, prop_pairs_shrinkAndValidate_equivalence
, prop_pairs_shrink_parallel
)
where
import Control.Concurrent
(threadDelay)
import Data.Functor.Classes
(Eq1)
import Data.IORef
(IORef, atomicModifyIORef', newIORef, readIORef,
writeIORef)
import GHC.Generics
(Generic, Generic1)
import Prelude
import System.Random
(randomIO, randomRIO)
import Test.QuickCheck
(Gen, Property, arbitrary, elements, frequency,
once, shrink, (===))
import Test.QuickCheck.Monadic
(monadicIO)
import Test.StateMachine
import Test.StateMachine.Parallel
(shrinkAndValidateNParallel,
shrinkAndValidateParallel, shrinkCommands',
shrinkNParallelCommands, shrinkParallelCommands)
import Test.StateMachine.Sequential
(ShouldShrink(..))
import Test.StateMachine.Types
(Commands(Commands), Reference(..), Symbolic(..),
Var(Var))
import qualified Test.StateMachine.Types as Types
import qualified Test.StateMachine.Types.Rank2 as Rank2
import Test.StateMachine.Utils
(Shrunk(..), shrinkListS, shrinkListS'',
shrinkPairS, shrinkPairS')
import Test.StateMachine.Z
------------------------------------------------------------------------
data Command r
= Create
| Read (Reference (Opaque (IORef Int)) r)
| Write (Reference (Opaque (IORef Int)) r) Int
| Increment (Reference (Opaque (IORef Int)) r)
deriving stock (Eq, Generic1)
deriving anyclass (Rank2.Functor, Rank2.Foldable, Rank2.Traversable, CommandNames)
deriving stock instance Show (Command Symbolic)
deriving stock instance Read (Command Symbolic)
deriving stock instance Show (Command Concrete)
data Response r
= Created (Reference (Opaque (IORef Int)) r)
| ReadValue Int
| Written
| Incremented
deriving stock (Eq, Generic1)
deriving anyclass Rank2.Foldable
deriving stock instance Show (Response Symbolic)
deriving stock instance Read (Response Symbolic)
deriving stock instance Show (Response Concrete)
newtype Model r = Model [(Reference (Opaque (IORef Int)) r, Int)]
deriving stock (Generic, Show)
instance ToExpr (Model Symbolic)
instance ToExpr (Model Concrete)
initModel :: Model r
initModel = Model empty
transition :: Eq1 r => Model r -> Command r -> Response r -> Model r
transition m@(Model model) cmd resp = case (cmd, resp) of
(Create, Created ref) -> Model ((ref, 0) : model)
(Read _, ReadValue _) -> m
(Write ref x, Written) -> Model (update ref x model)
(Increment ref, Incremented) -> case lookup ref model of
Just i -> Model (update ref (succ i) model)
Nothing -> error "transition: increment"
_ -> error "transition: impossible."
update :: Eq a => a -> b -> [(a, b)] -> [(a, b)]
update ref i m = (ref, i) : filter ((/= ref) . fst) m
precondition :: Model Symbolic -> Command Symbolic -> Logic
precondition (Model m) cmd = case cmd of
Create -> Top
Read ref -> ref `member` domain m
Write ref _ -> ref `member` domain m
Increment ref -> ref `member` domain m
postcondition :: Model Concrete -> Command Concrete -> Response Concrete -> Logic
postcondition (Model m) cmd resp = case (cmd, resp) of
(Create, Created ref) -> m' ! ref .== 0 .// "Create"
where
Model m' = transition (Model m) cmd resp
(Read ref, ReadValue v) -> v .== m ! ref .// "Read"
(Write _ref _x, Written) -> Top
(Increment _ref, Incremented) -> Top
_ -> Bot
data Bug
= None
| Logic
| Race
| Crash
| CrashAndLogic
deriving stock Eq
semantics :: Bug -> Command Concrete -> IO (Response Concrete)
semantics bug cmd = case cmd of
Create -> Created <$> (reference . Opaque <$> newIORef 0)
Read ref -> ReadValue <$> readIORef (opaque ref)
Write ref i ->
case bug of
-- One of the problems is a bug that writes a wrong value to the
-- reference.
Logic | i `elem` [5..10] -> Written <$ writeIORef (opaque ref) (i + 1)
-- There's also the possibility that the program gets killed or crashes.
Crash -> do
bool <- randomIO
if bool
then
error "Crash before writing!"
-- Written <$ writeIORef (opaque ref) i
else do
writeIORef (opaque ref) i
error "Crash after writing!"
CrashAndLogic -> do
writeIORef (opaque ref) (i + 1)
error "Crash after writing!"
_otherwise -> Written <$ writeIORef (opaque ref) i
Increment ref -> do
-- Another problem is that we introduce a possible race condition
-- when incrementing.
if bug == Race
then do
i <- readIORef (opaque ref)
threadDelay =<< randomRIO (0, 5000)
writeIORef (opaque ref) (i + 1)
else
atomicModifyIORef' (opaque ref) (\i -> (i + 1, ()))
return Incremented
mock :: Model Symbolic -> Command Symbolic -> GenSym (Response Symbolic)
mock (Model m) cmd = case cmd of
Create -> Created <$> genSym
Read ref -> ReadValue <$> pure (m ! ref)
Write _ _ -> pure Written
Increment _ -> pure Incremented
generator :: Model Symbolic -> Maybe (Gen (Command Symbolic))
generator m@(Model []) = Just (genCreate m)
generator m = Just $ frequency
[ (1, genCreate m)
, (4, genWrite m)
, (4, genRead m)
, (4, genIncr m)
]
genCreate, genRead, genWrite, genIncr :: Model Symbolic -> Gen (Command Symbolic)
genCreate _model = return Create
genRead (Model model) = Read <$> elements (domain model)
genWrite (Model model) = Write <$> elements (domain model) <*> arbitrary
genIncr (Model model) = Increment <$> elements (domain model)
shrinker :: Model Symbolic -> Command Symbolic -> [Command Symbolic]
shrinker _ (Write ref i) = [ Write ref i' | i' <- shrink i ]
shrinker _ _ = []
sm :: Bug -> StateMachine Model Command IO Response
sm bug = StateMachine initModel transition precondition postcondition
Nothing generator shrinker (semantics bug) mock noCleanup
prop_sequential :: Bug -> Property
prop_sequential bug = forAllCommands sm' Nothing $ \cmds -> monadicIO $ do
(hist, _model, res) <- runCommands sm' cmds
prettyCommands sm' hist (saveCommands "/tmp" cmds
(coverCommandNames cmds $ checkCommandNames cmds (res === Ok)))
where
sm' = sm bug
prop_runSavedCommands :: Bug -> FilePath -> Property
prop_runSavedCommands bug fp = monadicIO $ do
(_cmds, hist, _model, res) <- runSavedCommands (sm bug) fp
prettyCommands (sm bug) hist (res === Ok)
prop_parallel :: Bug -> Property
prop_parallel bug = forAllParallelCommands sm' Nothing $
\cmds -> checkCommandNamesParallel cmds $ monadicIO $
prettyParallelCommands cmds =<< runParallelCommands sm' cmds
where
sm' = sm bug
prop_parallel' :: Bug -> Property
prop_parallel' bug = forAllParallelCommands sm' Nothing $ \cmds -> monadicIO $ do
prettyParallelCommands cmds =<< runParallelCommands' sm' complete cmds
where
sm' = sm bug
complete :: Command Concrete -> Response Concrete
complete Create = Created (error "This reference will never be used.")
complete Read {} = ReadValue 0 -- Doesn't matter what value we read.
complete Write {} = Written
complete Increment {} = Incremented
prop_nparallel :: Bug -> Int -> Property
prop_nparallel bug np = forAllNParallelCommands sm' np $ \cmds ->
checkCommandNamesParallel cmds $ coverCommandNamesParallel cmds $ monadicIO $ do
prettyNParallelCommands cmds =<< runNParallelCommands sm' cmds
where
sm' = sm bug
prop_precondition :: Property
prop_precondition = once $ monadicIO $ do
(hist, _model, res) <- runCommands sm' cmds
prettyCommands sm' hist
(res === PreconditionFailed "PredicateC (NotMember (Reference (Symbolic (Var 0))) [])")
where
sm' = sm None
cmds = Commands
[ Types.Command (Read (Reference (Symbolic (Var 0)))) (ReadValue 0) [] ]
prop_existsCommands :: Property
prop_existsCommands = existsCommands sm' gens $ \cmds -> monadicIO $ do
(hist, _model, res) <- runCommands sm' cmds
prettyCommands sm' hist (checkCommandNames cmds (res === Ok))
where
sm' = sm None
gens =
[ genCreate
, genWrite
, genIncr
, genRead
]
{-------------------------------------------------------------------------------
Meta properties which test the testing framework.
-------------------------------------------------------------------------------}
prop_pairs_shrink_parallel_equivalence :: Property
prop_pairs_shrink_parallel_equivalence =
forAllParallelCommands (sm None) Nothing $ \pairCmds ->
let pairShrunk = shrinkParallelCommands (sm None) pairCmds
listCmds = Types.fromPair' pairCmds
listShrunk = shrinkNParallelCommands (sm None) listCmds
listShrunkPair = Types.toPairUnsafe' <$> listShrunk
in listShrunkPair === pairShrunk
prop_pairs_shrinkAndValidate_equivalence :: Property
prop_pairs_shrinkAndValidate_equivalence =
forAllParallelCommands (sm None) Nothing $ \pairCmds ->
let pairShrunk' = shrinkAndValidateParallel (sm None) DontShrink pairCmds
listCmds = Types.fromPair' pairCmds
listShrunk' = shrinkAndValidateNParallel (sm None) DontShrink listCmds
listShrunkPair' = Types.toPairUnsafe' <$> listShrunk'
in listShrunkPair' === pairShrunk'
prop_pairs_shrink_parallel :: Property
prop_pairs_shrink_parallel =
forAllParallelCommands (sm None) Nothing $ \cmds@(Types.ParallelCommands prefix suffixes) ->
let pair =
[ Shrunk s (Types.ParallelCommands prefix' (map Types.toPair suffixes'))
| Shrunk s (prefix', suffixes') <- shrinkPairS shrinkCommands' (shrinkListS (shrinkPairS' shrinkCommands'))
(prefix, map Types.fromPair suffixes)]
(Types.ParallelCommands _ listSuffixes) = Types.fromPair' cmds
list =
[ Shrunk s $ Types.toPairUnsafe' (Types.ParallelCommands prefix' suffixes')
| Shrunk s (prefix', suffixes') <- shrinkPairS shrinkCommands' (shrinkListS (shrinkListS'' shrinkCommands'))
(prefix, listSuffixes)]
in list == pair