package hegel
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Hegel property-based testing library for OCaml
Install
dune-project
Dependency
Authors
Maintainers
Sources
hegel-0.24.1-opam.tar.gz
md5=d6985126c61aec88003bc03555a0d763
sha512=03e73b752dfb9ea711bc95fd87a777c068a789e7094650c5297fd37ad023292528a6fc69ea88986c02ea3f24c5c24d28353c220fd87595ce047521eecf94e0a8
doc/src/hegel/stateful.ml.html
Source file stateful.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 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426# 1 "./stateful.ml.in" (** Stateful property-based testing for Hegel. See [stateful.mli]. Sequential and concurrent runners have some common code that is duplicated, but we have to keep them separate to prevent sequential stateful tests needing synchronized state for no reason to satisfy the OxCaml mode checker *) # 27 "./stateful.ml.in" module Pool_gen = Generators.Int_pool module Concurrent_pool_gen = Generators.Int_pool_concurrent module Pool = struct type 'a t = 'a Pool_gen.t let create ?(clone = Fun.id) tc = Pool_gen.create tc ~clone let add = Pool_gen.add let size = Pool_gen.size let values_reusable t = Pool_gen.pool_values t ~consume:false let values_consumed t = Pool_gen.pool_values t ~consume:true end module Concurrent_pool = struct # 45 "./stateful.ml.in" type 'a t = 'a Concurrent_pool_gen.t # 48 "./stateful.ml.in" let create ?(clone = Fun.id) tc = Concurrent_pool_gen.create tc ~clone let add = Concurrent_pool_gen.add let size = Concurrent_pool_gen.size let values_reusable t = Concurrent_pool_gen.pool_values t ~consume:false let values_consumed t = Concurrent_pool_gen.pool_values t ~consume:true end module Rule = struct type 'state t = { name : string ; weight : float ; step : Internal.test_case -> 'state -> unit } let create ~name ?(weight = 1.0) ~step () = { name; weight; step } let name t = t.name let weight t = t.weight end module Concurrent_rule = struct type ('ctx, 'state) t # 68 "./stateful.ml.in" # 68 "./stateful.ml.in" = { name : string ; group : string ; weight : float ; step : (Internal.test_case -> 'ctx # 73 "./stateful.ml.in" # 73 "./stateful.ml.in" -> 'state # 73 "./stateful.ml.in" # 73 "./stateful.ml.in" -> unit) # 73 "./stateful.ml.in" # 74 "./stateful.ml.in" } let create ?(group = "<anonymous>") ?(weight = 1.0) ~name ~step () = { name; group; weight; step } ;; let name t = t.name let group t = t.group let weight t = t.weight end module Invariant = struct type 'state t = { name : string ; inv : Internal.test_case -> 'state -> unit ; always_check : bool } let create ~name ~inv ?(always_check = false) () = { name; inv; always_check } let name invariant = invariant.name end let run_rules tc ~state_machine ~worker_index # 96 "./stateful.ml.in" ~ctx # 96 "./stateful.ml.in" ~heading ~rule = let rec loop # 97 "./stateful.ml.in" ctx # 97 "./stateful.ml.in" rejected = match Internal.state_machine_next_rule_for_worker tc ~state_machine ~worker_index with | None -> rejected | Some rule_index -> let name, body = rule rule_index in Internal.note tc (heading name); (match if Internal.should_print tc then Internal.with_block tc ~indent:2 (fun tc -> body tc ctx) else body tc ctx with | () -> loop ctx rejected | exception Internal.Assume_rejected -> Internal.state_machine_rule_rejected_for_worker tc ~state_machine ~worker_index; Internal.note tc "Rule stopped early due to violated assumption."; loop ctx true) in loop ctx false ;; let unique_groups groups = List.fold_left (fun names group -> if List.mem group names then names else group :: names) [] groups |> List.rev ;; let group_indices groups names = List.map (fun group -> List.find_index (String.equal group) names |> Option.get) groups ;; let dispatch_round # 129 "./stateful.ml.in" (concurrency : _ Concurrency.t) # 129 "./stateful.ml.in" tc ~num_workers ~work = let cases = List.init num_workers (fun worker_index -> let worker_tc = Internal.clone tc in Internal.set_worker_index worker_tc worker_index; worker_tc) in concurrency.spawn_join_n ~n:num_workers ~f:(fun ctx worker_index -> match work ctx ~worker_index (List.nth cases worker_index) with | () -> None | exception exn -> Some (exn, Printexc.get_raw_backtrace ())) ;; let reraise_worker_failure (outcomes : Concurrency.outcome list) = let find predicate = List.find_map (function | Some (exn, _) as failure when predicate exn -> failure | _ -> None) outcomes in let is_control_exception = function | Internal.Usage_error _ | Internal.Internal_error _ -> true | _ -> false in let is_overrun = function | Internal.Stop_test -> true | _ -> false in let is_invalid = function | Internal.Assume_rejected | Internal.Flaky_strategy -> true | _ -> false in let is_test_failure = Fun.const true in (* invalidated or exhausted rules can cause in other workers. error precedence from greatest to least: usage/internal errors, overrun, invalidation, actual test failure. within each category, first worker raises. *) let failure = List.find_map find [ is_control_exception; is_overrun; is_invalid; is_test_failure ] in Option.iter (fun (exn, backtrace) -> Printexc.raise_with_backtrace exn backtrace) failure ;; let run_machine tc ~rule_names ~rule_groups ~rule_weights ~invariants ~step_count ~min_concurrency ~max_concurrency ~sexp_of_state ~init run_steps = let state_machine, num_workers = Internal.new_state_machine_with_concurrency tc ~rule_names ~rule_groups ~rule_weights ~invariant_names:(List.map Invariant.name invariants) ~invariants_always_check: (List.map (fun inv -> inv.Invariant.always_check) invariants) ~step_count ~min_concurrency ~max_concurrency in let print_state () = Option.iter (fun sexp_of -> Internal.print_line tc [ Text "state = "; Value (sexp_of init) ]) sexp_of_state in let check_invariants ~where ~sample = List.iteri (fun i invariant -> if (not sample) || Internal.state_machine_should_check_invariant tc ~state_machine ~invariant_index:i then ( match if Internal.should_print tc then Internal.with_block tc ~indent:2 (fun tc -> invariant.Invariant.inv tc init) else invariant.Invariant.inv tc init with | () -> () | exception e -> Internal.note tc (Printf.sprintf "Invariant %s violated %s." (Invariant.name invariant) where); raise e)) invariants in (* Can't use Fun.protect because its body must be a global closure *) let free () = Internal.state_machine_free tc ~state_machine in let run_test () = if num_workers > 1 then Internal.note tc (Printf.sprintf "Concurrency level: %d" num_workers); print_state (); check_invariants ~where:"in the initial state" ~sample:false; run_steps ~state_machine ~num_workers ~print_state ~check_invariants; check_invariants ~where:"in the final state" ~sample:false in match run_test () with | () -> free () | exception exn -> free (); raise exn ;; let run_internal ~init ~rules ~invariants ?sexp_of_state ?(step_count = 50) tc = let run_steps ~state_machine ~num_workers:_ ~print_state ~check_invariants = let rec loop steps_attempted = Internal.start_span ~label:Generators.Private.Labels.stateful_rule tc; match Internal.state_machine_next_group tc ~state_machine with | None -> Internal.stop_span tc | Some _group -> let step = steps_attempted + 1 in let rejected = match run_rules tc ~state_machine ~worker_index:0 ~ctx:() ~heading:(fun name -> Printf.sprintf "Step %d: %s" step name) ~rule:(fun rule_index -> let rule = List.nth rules rule_index in rule.Rule.name, fun tc () -> rule.Rule.step tc init) with | rejected -> rejected | exception e -> Internal.stop_span tc; raise e in Internal.stop_span ~discard:rejected tc; print_state (); check_invariants ~where:(Printf.sprintf "after step %d" step) ~sample:true; loop step in loop 0 in run_machine tc ~rule_names:(List.map Rule.name rules) ~rule_groups:(List.map (fun _ -> 0) rules) ~rule_weights:(List.map Rule.weight rules) ~invariants ~step_count ~min_concurrency:1 ~max_concurrency:1 ~sexp_of_state ~init run_steps ;; module type State_machine = sig type state val rules : state Rule.t list val invariants : state Invariant.t list end let run (type s) ?step_count ?sexp_of_state tc (module M : State_machine with type state = s) ~(init : s) = run_internal ~init ~rules:M.rules ~invariants:M.invariants ?sexp_of_state ?step_count tc ;; module type Concurrent_state_machine = sig type ctx type state val rules : (ctx, state) Concurrent_rule.t list val invariants : state Invariant.t list end let run_concurrent_internal ~init ~rules ~invariants # 326 "./stateful.ml.in" ~(concurrency : _ Concurrency.t) # 327 "./stateful.ml.in" ?(min_concurrency = 1) ?(max_concurrency = min_concurrency) ?sexp_of_state ?(step_count = 50) tc = let rule_groups = List.map Concurrent_rule.group rules in let group_names = unique_groups rule_groups in let run_steps ~state_machine ~num_workers ~print_state ~check_invariants = let work # 336 "./stateful.ml.in" ctx # 336 "./stateful.ml.in" ~worker_index tc = (* returns if a rule was rejected or not, doesn't matter for concurrent *) let (_ : bool) = run_rules tc ~state_machine ~worker_index ~ctx ~heading:(fun name -> Printf.sprintf "Rule: %s" name) ~rule:(fun rule_index -> let rule = List.nth rules rule_index in rule.Concurrent_rule.name, fun tc ctx -> rule.Concurrent_rule.step tc ctx init) in () in (* [concurrency] is passed down rather than captured, so it may be local. *) let rec loop concurrency round = match Internal.state_machine_next_group tc ~state_machine with | None -> () | Some group -> Internal.note tc (Printf.sprintf "---------------- Round %d: group %S ----------------" round (List.nth group_names group)); reraise_worker_failure (dispatch_round concurrency tc ~num_workers ~work); print_state (); check_invariants ~where:(Printf.sprintf "after round %d" round) ~sample:true; loop concurrency (round + 1) in loop concurrency 1 in run_machine tc ~rule_names:(List.map Concurrent_rule.name rules) ~rule_groups:(group_indices rule_groups group_names) ~rule_weights:(List.map Concurrent_rule.weight rules) ~invariants ~step_count ~min_concurrency ~max_concurrency ~sexp_of_state ~init run_steps # 381 "./stateful.ml.in" # 382 "./stateful.ml.in" ;; let run_concurrent (type c s) ~concurrency ?min_concurrency ?max_concurrency ?step_count ?sexp_of_state tc (module M : Concurrent_state_machine with type ctx = c and type state = s) ~(init : s) = run_concurrent_internal ~init ~rules:M.rules ~invariants:M.invariants ~concurrency ?min_concurrency ?max_concurrency ?sexp_of_state ?step_count tc ;;
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>