Source file STM.ml
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
(** Base module for specifying STM tests *)
open QCheck
type 'a ty = ..
type _ ty +=
| Unit : unit ty
| Bool : bool ty
| Char : char ty
| Int : int ty
| Int32 : int32 ty
| Int64 : int64 ty
| Float : float ty
| String : string ty
| Bytes : bytes ty
| Exn : exn ty
| Option : 'a ty -> 'a option ty
| Result : 'a ty * 'b ty -> ('a, 'b) result ty
| List : 'a ty -> 'a list ty
| Array : 'a ty -> 'a array ty
| Seq : 'a ty -> 'a Seq.t ty
type 'a ty_show = 'a ty * ('a -> string)
let unit = (Unit, QCheck.Print.unit)
let bool = (Bool, QCheck.Print.bool)
let char = (Char, QCheck.Print.char)
let int = (Int, QCheck.Print.int)
let int32 = (Int32, Int32.to_string)
let int64 = (Int64, Int64.to_string)
let float = (Float, QCheck.Print.float)
let string = (String, QCheck.Print.string)
let bytes = (Bytes, QCheck.Print.bytes)
let option spec =
let (ty,show) = spec in
(Option ty, QCheck.Print.option show)
let exn = (Exn, Printexc.to_string)
let show_result show_ok show_err = function
| Ok x -> Printf.sprintf "Ok (%s)" (show_ok x)
| Error y -> Printf.sprintf "Error (%s)" (show_err y)
let result spec_ok spec_err =
let (ty_ok, show_ok) = spec_ok in
let (ty_err, show_err) = spec_err in
(Result (ty_ok, ty_err), show_result show_ok show_err)
let list spec =
let (ty,show) = spec in
(List ty, QCheck.Print.list show)
let array spec =
let (ty,show) = spec in
(Array ty, QCheck.Print.array show)
let seq spec =
let (ty,show) = spec in
(Seq ty, fun s -> QCheck.Print.list show (List.of_seq s))
type res =
Res : 'a ty_show * 'a -> res
let show_res (Res ((_,show), v)) = show v
(** The specification of a state machine. *)
module type Spec =
sig
type cmd
(** The type of commands *)
type state
(** The type of the model's state *)
type sut
(** The type of the system under test *)
val arb_cmd : state -> cmd arbitrary
(** A command generator. Accepts a state parameter to enable state-dependent [cmd] generation. *)
val init_state : state
(** The model's initial state. *)
val show_cmd : cmd -> string
(** [show_cmd c] returns a string representing the command [c]. *)
val next_state : cmd -> state -> state
(** Move the internal state machine to the next state. *)
val init_sut : unit -> sut
(** Initialize the system under test. *)
val cleanup : sut -> unit
(** Utility function to clean up the [sut] after each test instance,
e.g., for closing sockets, files, or resetting global parameters*)
val precond : cmd -> state -> bool
(** [precond c s] expresses preconditions for command [c].
This is useful, e.g., to prevent the shrinker from breaking invariants when minimizing
counterexamples. *)
val run : cmd -> sut -> res
(** [run c i] should interpret the command [c] over the system under test (typically side-effecting). *)
val postcond : cmd -> state -> res -> bool
(** [postcond c s res] checks whether [res] arising from interpreting the
command [c] over the system under test with [run] agrees with the
model's result.
Note: [s] is in this case the model's state prior to command execution. *)
end
module type SpecExt =
sig
include Spec
val wrap_cmd_seq : (unit -> 'a) -> 'a
end
module SpecDefaults =
struct
let cleanup = ignore
let precond _ _ = true
let wrap_cmd_seq th = th ()
end
module Internal =
struct
module Make(Spec : Spec) = struct
let rec gen_cmds arb s fuel =
Gen.(if fuel = 0
then return []
else
(arb s).gen >>= fun c ->
let s' = try Spec.next_state c s with _ -> s in
(gen_cmds arb s' (fuel-1)) >>= fun cs ->
return (c::cs))
(** A fueled command list generator.
Accepts a state parameter to enable state-dependent [cmd] generation. *)
let rec cmds_ok s cs = match cs with
| [] -> true
| c::cs ->
Spec.precond c s &&
let s' = try Spec.next_state c s with _ -> s in
cmds_ok s' cs
let rec shrink_list_spine l yield =
let rec split l len acc = match len,l with
| _,[]
| 0,_ -> List.rev acc, l
| _,x::xs -> split xs (len-1) (x::acc) in
match l with
| [] -> ()
| [_] -> yield []
| [x;y] -> yield []; yield [x]; yield [y]
| [x;y;z] -> yield [x]; yield [x;y]; yield [x;z]; yield [y;z]
| [x;y;z;w] -> yield [x;y;z]; yield [x;y;w]; yield [x;z;w]; yield [y;z;w]
| _::_ ->
let len = List.length l in
let xs,ys = split l ((1 + len) / 2) [] in
yield xs;
shrink_list_spine xs (fun xs' -> yield (xs'@ys))
let shrink_list ?shrink l yield =
shrink_list_spine l yield;
match shrink with
| None -> ()
| Some shrink -> Shrink.list_elems shrink l yield
let gen_cmds_size gen s size_gen = Gen.sized_size size_gen (gen_cmds gen s)
let cmd_list_size_dist mean =
let skew = 0.75 in
Gen.map (fun p -> int_of_float (p +. skew)) (Gen.exponential mean)
let arb_cmds s =
let mean = 10. in
let cmds_gen = gen_cmds_size Spec.arb_cmd s (cmd_list_size_dist mean) in
let shrinker = shrink_list ?shrink:(Spec.arb_cmd s).shrink in
let ac = QCheck.make ~shrink:(Shrink.filter (cmds_ok Spec.init_state) shrinker) cmds_gen in
(match (Spec.arb_cmd s).print with
| None -> ac
| Some p -> set_print (Util.print_vertical p) ac)
let consistency_test ~count ~name =
Test.make ~name ~count (arb_cmds Spec.init_state) (cmds_ok Spec.init_state)
let rec interp_agree s sut cs = match cs with
| [] -> true
| c::cs ->
let res = Spec.run c sut in
let b = Spec.postcond c s res in
let s' = Spec.next_state c s in
b && interp_agree s' sut cs
let rec check_disagree s sut cs = match cs with
| [] -> None
| c::cs ->
let res = Spec.run c sut in
let b = Spec.postcond c s res in
if b
then
let s' = Spec.next_state c s in
match check_disagree s' sut cs with
| None -> None
| Some rest -> Some ((c,res)::rest)
else Some [c,res]
let rec all_interleavings_ok pref cs1 cs2 s =
match pref with
| c::pref' ->
Spec.precond c s &&
let s' = try Spec.next_state c s with _ -> s in
all_interleavings_ok pref' cs1 cs2 s'
| [] ->
match cs1,cs2 with
| [],[] -> true
| [],c2::cs2' ->
Spec.precond c2 s &&
let s' = try Spec.next_state c2 s with _ -> s in
all_interleavings_ok pref cs1 cs2' s'
| c1::cs1',[] ->
Spec.precond c1 s &&
let s' = try Spec.next_state c1 s with _ -> s in
all_interleavings_ok pref cs1' cs2 s'
| c1::cs1',c2::cs2' ->
(Spec.precond c1 s &&
let s' = try Spec.next_state c1 s with _ -> s in
all_interleavings_ok pref cs1' cs2 s')
&&
(Spec.precond c2 s &&
let s' = try Spec.next_state c2 s with _ -> s in
all_interleavings_ok pref cs1 cs2' s')
let rec check_obs pref cs1 cs2 s =
match pref with
| (c,res)::pref' ->
let b = Spec.postcond c s res in
b && check_obs pref' cs1 cs2 (Spec.next_state c s)
| [] ->
match cs1,cs2 with
| [],[] -> true
| [],(c2,res2)::cs2' ->
let b = Spec.postcond c2 s res2 in
b && check_obs pref cs1 cs2' (Spec.next_state c2 s)
| (c1,res1)::cs1',[] ->
let b = Spec.postcond c1 s res1 in
b && check_obs pref cs1' cs2 (Spec.next_state c1 s)
| (c1,res1)::cs1',(c2,res2)::cs2' ->
(let b1 = Spec.postcond c1 s res1 in
b1 && check_obs pref cs1' cs2 (Spec.next_state c1 s))
||
(let b2 = Spec.postcond c2 s res2 in
b2 && check_obs pref cs1 cs2' (Spec.next_state c2 s))
let shrink_cmd arb cmd state =
Option.value (arb state).shrink ~default:Shrink.nil @@ cmd
let rec shrink_cmd_list_elems arb cs state = match cs with
| [] -> Iter.empty
| c::cs ->
if Spec.precond c state
then
Iter.(
map (fun c -> c::cs) (shrink_cmd arb c state)
<+>
map (fun cs -> c::cs) (shrink_cmd_list_elems arb cs Spec.(next_state c state))
)
else Iter.empty
let shrink_triple_elems arb0 arb1 arb2 (seq,p1,p2) =
let shrink_prefix_elems cs state =
Iter.map (fun cs -> (cs,p1,p2)) (shrink_cmd_list_elems arb0 cs state)
in
let rec shrink_par_suffix_elems cs state = match cs with
| [] ->
Iter.(map (fun p1 -> (seq,p1,p2)) (shrink_cmd_list_elems arb1 p1 state)
<+>
map (fun p2 -> (seq,p1,p2)) (shrink_cmd_list_elems arb2 p2 state))
| c::cs ->
if Spec.precond c state
then shrink_par_suffix_elems cs Spec.(next_state c state)
else Iter.empty
in
match Spec.(arb_cmd init_state).shrink with
| None -> Iter.empty
| Some _ ->
Iter.(shrink_prefix_elems seq Spec.init_state
<+>
shrink_par_suffix_elems seq Spec.init_state)
let shrink_triple arb0 arb1 arb2 =
let open Iter in
Shrink.filter
(fun (seq,p1,p2) -> all_interleavings_ok seq p1 p2 Spec.init_state)
(fun ((seq,p1,p2) as triple) ->
(map (fun seq' -> (seq',p1,p2)) (shrink_list_spine seq))
<+>
(fun yield -> (match p1 with [] -> Iter.empty | c1::c1s -> Iter.return (seq@[c1],c1s,p2)) yield)
<+>
(fun yield -> (match p2 with [] -> Iter.empty | c2::c2s -> Iter.return (seq@[c2],p1,c2s)) yield)
<+>
(map (fun p1' -> (seq,p1',p2)) (shrink_list_spine p1))
<+>
(map (fun p2' -> (seq,p1,p2')) (shrink_list_spine p2))
<+>
(shrink_triple_elems arb0 arb1 arb2 triple))
let arb_triple seq_len par_len arb0 arb1 arb2 =
let seq_pref_gen = gen_cmds_size arb0 Spec.init_state (Gen.int_bound seq_len) in
let shrink_triple = shrink_triple arb0 arb1 arb2 in
let gen_triple =
Gen.(seq_pref_gen >>= fun seq_pref ->
int_range 2 (2*par_len) >>= fun dbl_plen ->
let spawn_state = List.fold_left (fun st c -> try Spec.next_state c st with _ -> st) Spec.init_state seq_pref in
let par_len1 = dbl_plen/2 in
let par_gen1 = gen_cmds_size arb1 spawn_state (return par_len1) in
let par_gen2 = gen_cmds_size arb2 spawn_state (return (dbl_plen - par_len1)) in
triple (return seq_pref) par_gen1 par_gen2) in
make ~print:(Util.print_triple_vertical Spec.show_cmd) ~shrink:shrink_triple gen_triple
let arb_cmds_triple seq_len par_len = arb_triple seq_len par_len Spec.arb_cmd Spec.arb_cmd Spec.arb_cmd
end
end
include Util