11(* *
22 Generate direct tests of the Thread module's create/join primitives.
3- Like [src/domainslib/tast_one_dep.ml]([src/domainslib/tast_one_dep.ml) it does so by generating
4- a random, acyclic dependency graph of [create]d [Thread.t]s each waiting
5- to [join] with its dependency.
3+ It does so by generating a random, acyclic dependency graph of
4+ [create]d [Thread.t]s each waiting to [join] with its dependency.
65 *)
76
87open QCheck
98
109(* Generates a sparse DAG of join dependencies *)
11- (* Each thread is represented by an array index w/at most 1 dep. each *)
10+ (* Each thread is represented by record with an optional array index
11+ to model at most 1 dependency each *)
1212(* This example DAG
1313
1414 A/0 <--- B/1
1515 ^.
1616 \
1717 `- C/2 <--- D/3
1818
19- is represented as: [| None; Some 0; Some 0; Some 2 |]
19+ is represented as:
20+ [| {dep=None ...}; {dep=Some 0 ...}; {dep=Some 0 ...}; {dep=Some 2 ...} |]
2021
2122 Since each thread can only be joined once, A/0 is joined by B/1 (not C/2)
2223*)
23- let gen_deps n st =
24- let a = Array. make n None in
25- for i= 1 to n-1 do
26- if Gen. bool st then a.(i) < - Some (Gen. int_bound (i-1 ) st)
27- done ;
28- a
24+
25+ type work_kind = Atomic_incr | Tak | Gc_minor
26+
27+ type node =
28+ {
29+ dep : int option ;
30+ work : work_kind
31+ }
2932
3033type test_input =
3134 {
3235 num_threads : int ;
33- dependencies : int option array
36+ dependencies : node array
3437 }
3538
39+
40+ let gen_deps gen_work n st =
41+ Array. init n
42+ (fun i ->
43+ let dep = if i<> 0 && Gen. bool st then Some (Gen. int_bound (i-1 ) st) else None in
44+ let work = gen_work st in
45+ { dep; work })
46+
47+ let show_work_kind w = match w with
48+ | Atomic_incr -> " Atomic_incr"
49+ | Tak -> " Tak"
50+ | Gc_minor -> " Gc_minor"
51+
52+ let pp_work_kind = Util.Pp. of_show show_work_kind
53+
54+ let pp_node par fmt {dep;work} =
55+ let open Util.Pp in
56+ pp_record par fmt
57+ [
58+ pp_field " dep" (pp_option pp_int) dep;
59+ pp_field " work" pp_work_kind work;
60+ ]
61+
3662let pp_test_input par fmt { num_threads; dependencies } =
3763 let open Util.Pp in
3864 pp_record par fmt
3965 [
4066 pp_field " num_threads" pp_int num_threads;
41- pp_field " dependencies" (pp_array (pp_option pp_int) ) dependencies;
67+ pp_field " dependencies" (pp_array pp_node ) dependencies;
4268 ]
4369
4470let show_test_input = Util.Pp. to_show pp_test_input
4571
72+ let shrink_node n = Iter. map (fun opt -> { n with dep = opt}) (Shrink. (option nil) n.dep)
4673let shrink_deps test_input =
4774 let ls = Array. to_list test_input.dependencies in
48- let is = Shrink. list ~shrink: Shrink. ( option nil) ls in
75+ let is = Shrink. list ~shrink: shrink_node ls in
4976 Iter. map
5077 (fun deps ->
5178 let len = List. length deps in
5279 let arr = Array. of_list deps in
53- let deps = Array. mapi (fun i j_opt -> match i,j_opt with
54- | 0 , _
55- | _ ,None -> None
56- | _ ,Some 0 -> Some 0
57- | _ , Some j ->
58- if j< 0 || j> = len || j> = i (* ensure reduced dep is valid *)
59- then Some ((j + i) mod i)
60- else Some j) arr in
80+ let deps = Array. mapi (fun i j_node ->
81+ let dep = match i,j_node.dep with
82+ | 0 , _
83+ | _ ,None -> None
84+ | _ ,Some 0 -> Some 0
85+ | _ , Some j ->
86+ if j< 0 || j> = len || j> = i (* ensure reduced dep is valid *)
87+ then Some ((j + i) mod i)
88+ else Some j in
89+ { j_node with dep }) arr in
6190 { num_threads= len; dependencies= deps }) is
6291
63- let arb_deps thread_bound =
92+ let arb_deps gen_work thread_bound =
6493 let gen_deps =
6594 Gen. (int_bound (thread_bound-1 ) >> = fun num_threads ->
6695 let num_threads = succ num_threads in
67- gen_deps num_threads >> = fun dependencies -> return { num_threads; dependencies }) in
96+ gen_deps gen_work num_threads >> = fun dependencies -> return { num_threads; dependencies }) in
6897 make ~print: show_test_input ~shrink: shrink_deps gen_deps
6998
7099(* let thread_id id i = Printf.sprintf "(thread %i, index %i)" id i*)
71100
72101let is_first_with_dep i dep deps =
73- [] = List. filteri (fun j opt -> j < i && opt = Some dep) (Array. to_list deps)
102+ [] = List. filteri (fun j node -> j < i && node.dep = Some dep) (Array. to_list deps)
103+
104+ (* a simple work item, from ocaml/testsuite/tests/misc/takc.ml *)
105+ let rec tak x y z =
106+ if x > y then tak (tak (x-1 ) y z) (tak (y-1 ) z x) (tak (z-1 ) x y)
107+ else z
108+
109+ let work () =
110+ for _ = 1 to 100 do
111+ assert (7 = tak 18 12 6 );
112+ done
113+
114+ let a = Atomic. make 0
74115
75- let build_dep_graph test_input f =
116+ let interp_work w = match w with
117+ | Atomic_incr -> Atomic. incr a
118+ | Tak -> work ()
119+ | Gc_minor -> Gc. minor ()
120+
121+ let build_dep_graph test_input =
76122 let rec build i thread_acc =
77123 if i= test_input.num_threads
78124 then List. rev thread_acc
79125 else
80- let p = (match test_input.dependencies.(i) with
126+ let p = (match test_input.dependencies.(i).dep with
81127 | None ->
82- Thread. create f ()
128+ Thread. create (fun () ->
129+ interp_work test_input.dependencies.(i).work
130+ ) ()
83131 | Some dep ->
84132 Thread. create (fun () ->
85- f() ;
133+ interp_work test_input.dependencies.(i).work;
134+ (* f();*)
86135 if is_first_with_dep i dep test_input.dependencies
87136 then
88137 let p' = List. nth thread_acc (i-1 - dep) in
@@ -92,46 +141,23 @@ let build_dep_graph test_input f =
92141 in
93142 build 0 []
94143
95- (* * In this first test each created thread calls [work] - and then optionally join. *)
96- (* a simple work item, from ocaml/testsuite/tests/misc/takc.ml *)
97- let rec tak x y z =
98- if x > y then tak (tak (x-1 ) y z) (tak (y-1 ) z x) (tak (z-1 ) x y)
99- else z
100-
101- let work () =
102- for _ = 1 to 100 do
103- assert (7 = tak 18 12 6 );
104- done
105-
106- let test_tak_work ~thread_bound =
107- Test. make ~name: " Thread.create/join - tak work" ~count: 100
108- (arb_deps thread_bound)
109- ((* Util.fork_prop_with_timeout 30*)
144+ let test_arb_work ~thread_bound =
145+ Test. make ~name: " Thread.create/join" ~count: 100
146+ (arb_deps (Gen. frequencyl [(10 ,Atomic_incr );
147+ (10 ,Tak );
148+ (1 ,Gc_minor )]) thread_bound)
110149 (fun test_input ->
111- (* Printf.printf "%s\n%!" (show_test_input test_input);*)
112- let ps = build_dep_graph test_input work in
113- List. iteri (fun i p -> if not (Array. mem (Some i) test_input.dependencies) then Thread. join p) ps;
114- true ))
115-
116- (* * In this test each created thread calls [Atomic.incr] - and then optionally join. *)
117- let test_atomic_work ~thread_bound =
118- Test. make ~name: " Thread.create/join - atomic" ~count: 500
119- (arb_deps thread_bound)
120- (fun test_input ->
121- let a = Atomic. make 0 in
122- let ps = build_dep_graph test_input (fun () -> Atomic. incr a) in
123- List. iteri (fun i p ->
124- if not (Array. mem (Some i) test_input.dependencies)
125- then
126- Thread. join p;
127- ) ps;
128- Atomic. get a = test_input.num_threads)
129-
130- let bound_tak = if Sys. word_size == 64 then 100 else 16
131- let bound_atomic = if Sys. word_size == 64 then 250 else 16
150+ Atomic. set a 0 ;
151+ let ps = build_dep_graph test_input in
152+ List. iteri
153+ (fun i p ->
154+ if not (Array. exists (fun n -> n.dep = Some i) test_input.dependencies)
155+ then Thread. join p) ps;
156+ Atomic. get a
157+ = Array. fold_left (fun a n -> if n.work = Atomic_incr then 1 + a else a) 0 test_input.dependencies)
158+
159+ let bound_arb = if Sys. word_size == 64 then 100 else 16
132160
133161;;
134162QCheck_base_runner. run_tests_main
135- [test_tak_work ~thread_bound: bound_tak;
136- test_atomic_work ~thread_bound: bound_atomic
137- ]
163+ [test_arb_work ~thread_bound: bound_arb; ]
0 commit comments