|
| 1 | +(** |
| 2 | + Generate tests of async+await from Domainslib.Task. |
| 3 | + It does so by generating a random, acyclic dependency graph of [async] tasks, |
| 4 | + each [await]ing on its dependency. |
| 5 | + *) |
| 6 | + |
| 7 | +open QCheck |
| 8 | +open Domainslib |
| 9 | + |
| 10 | +(* a simple work item, from ocaml/testsuite/tests/misc/takc.ml *) |
| 11 | +let rec tak x y z = |
| 12 | + if x > y then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) |
| 13 | + else z |
| 14 | + |
| 15 | +let work () = |
| 16 | + for _ = 1 to 200 do |
| 17 | + assert (7 = tak 18 12 6); |
| 18 | + done |
| 19 | + |
| 20 | +(* Generates a DAG of dependencies *) |
| 21 | +(* Each task is represented by an array index w/a deps.list *) |
| 22 | +(* This example DAG |
| 23 | +
|
| 24 | + A/0 <--- B/1 < |
| 25 | + ^. \ |
| 26 | + \ \ |
| 27 | + `- C/2 <--- D/3 |
| 28 | +
|
| 29 | + is represented as: [| []; [0]; [0]; [1;2] |] *) |
| 30 | +let gen_dag n st = |
| 31 | + Array.init n (fun i -> |
| 32 | + let deps = ref [] in |
| 33 | + for dep = 0 to i-1 do |
| 34 | + if Gen.bool st then deps := dep :: !deps |
| 35 | + done; |
| 36 | + List.rev !deps) |
| 37 | + |
| 38 | +type test_input = |
| 39 | + { |
| 40 | + num_domains : int; |
| 41 | + length : int; |
| 42 | + dependencies : int list array |
| 43 | + } |
| 44 | + |
| 45 | +let show_test_input t = |
| 46 | + Printf.sprintf |
| 47 | + "{ num_domains : %i\n length : %i\n dependencies : %s }" |
| 48 | + t.num_domains t.length Print.(array (list int) t.dependencies) |
| 49 | + |
| 50 | +let shrink_deps test_input = |
| 51 | + let ls = Array.to_list test_input.dependencies in |
| 52 | + let is = Shrink.list ~shrink:Shrink.list ls in |
| 53 | + Iter.map |
| 54 | + (fun deps -> |
| 55 | + let len = List.length deps in |
| 56 | + let arr = Array.of_list deps in |
| 57 | + let deps = Array.mapi (fun i i_deps -> match i,i_deps with |
| 58 | + | 0, _ |
| 59 | + | _,[] -> [] |
| 60 | + | _,[0] -> [0] |
| 61 | + | _, _ -> |
| 62 | + List.map (fun j -> |
| 63 | + if j<0 || j>=len || j>=i (* ensure reduced dep is valid *) |
| 64 | + then ((j + i) mod i) |
| 65 | + else j) i_deps) arr in |
| 66 | + { test_input with length=len; dependencies=deps }) is |
| 67 | + |
| 68 | +let arb_deps domain_bound promise_bound = |
| 69 | + let gen_deps = |
| 70 | + Gen.(pair (int_bound (domain_bound-1)) (int_bound promise_bound) >>= fun (num_domains,length) -> |
| 71 | + let num_domains = succ num_domains in |
| 72 | + let length = succ length in |
| 73 | + gen_dag length >>= fun dependencies -> return { num_domains; length; dependencies }) in |
| 74 | + make ~print:show_test_input ~shrink:(shrink_deps) gen_deps |
| 75 | + |
| 76 | +let build_dep_graph pool test_input = |
| 77 | + let len = test_input.length in |
| 78 | + let deps = test_input.dependencies in |
| 79 | + let rec build i promise_acc = |
| 80 | + if i=len |
| 81 | + then promise_acc |
| 82 | + else |
| 83 | + let p = (match deps.(i) with |
| 84 | + | [] -> |
| 85 | + Task.async pool work |
| 86 | + | deps -> |
| 87 | + Task.async pool (fun () -> |
| 88 | + work (); |
| 89 | + List.iter (fun dep -> Task.await pool (List.nth promise_acc (i-1-dep))) deps)) in |
| 90 | + build (i+1) (p::promise_acc) |
| 91 | + in |
| 92 | + build 0 [] |
| 93 | + |
| 94 | +let test_one_pool ~domain_bound ~promise_bound = |
| 95 | + Test.make ~name:"Domainslib.Task.async/await, more deps, 1 work pool" ~count:100 |
| 96 | + (arb_deps domain_bound promise_bound) |
| 97 | + (Util.repeat 10 |
| 98 | + (fun test_input -> |
| 99 | + let pool = Task.setup_pool ~num_domains:test_input.num_domains () in |
| 100 | + Task.run pool (fun () -> |
| 101 | + let ps = build_dep_graph pool test_input in |
| 102 | + List.iter (fun p -> Task.await pool p) ps); |
| 103 | + Task.teardown_pool pool; |
| 104 | + true)) |
| 105 | + |
| 106 | +let () = |
| 107 | + QCheck_base_runner.run_tests_main [test_one_pool ~domain_bound:8 ~promise_bound:10] |
0 commit comments