package qcheck-stm
State-machine testing library for sequential and parallel model-based tests
Install
Dune Dependency
Authors
Maintainers
Sources
0.8.tar.gz
md5=8e7634814a61bf765ac6989f7fdc49cb
sha512=dfa53117ecbf2e466f6ecddfa91d8eb63a3156fe9e1c5a68fd0da26a4c810312581d9ace4c00c4ab1947614f7fb1d6b686003a09da418d2940ac79a7b744a8eb
doc/src/qcheck-stm.stm/STM.ml.html
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 (* This is an adaption of [QCheck.Shrink.list_spine] with more base cases added *) 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)) (* This is an adaption of [QCheck.Shrink.list] *) let shrink_list ?shrink l yield = shrink_list_spine l yield; match shrink with | None -> () (* no elem. shrinker provided *) | 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 (* to avoid too many empty cmd lists *) Gen.map (fun p -> int_of_float (p +. skew)) (Gen.exponential mean) let arb_cmds s = let mean = 10. in (* generate on average ~10 cmds, ignoring skew *) 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 (* pass opt. elem. shrinker *) 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] (* checks that all interleavings of a cmd triple satisfies all preconditions *) 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)) (* Shrinks a single cmd, starting in the given state *) let shrink_cmd arb cmd state = Option.value (arb state).shrink ~default:Shrink.nil @@ cmd (* Shrinks cmd list elements, starting in the given state *) 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 (* Shrinks cmd elements in triples *) 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 | [] -> (* try only one option: p1s or p2s first - both valid interleavings *) 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 -> (* walk seq prefix (again) to advance state *) 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 (* stop early if no cmd shrinker is available *) | Some _ -> Iter.(shrink_prefix_elems seq Spec.init_state <+> shrink_par_suffix_elems seq Spec.init_state) (* General shrinker of cmd triples *) 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) -> (* Shrinking heuristic: First reduce the cmd list sizes as much as possible, since the interleaving is most costly over long cmd lists. *) (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)) <+> (* Secondly reduce the cmd data of individual list elements *) (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
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>