Skip to content

Commit 142b958

Browse files
authored
Merge pull request #100 from ocaml-multicore/add-property-based-tests
Add property based tests from multicoretests
2 parents 66646b0 + 6898d8a commit 142b958

File tree

7 files changed

+419
-1
lines changed

7 files changed

+419
-1
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020

2121
runs-on: ${{ matrix.os }}
2222

23+
env:
24+
QCHECK_MSG_INTERVAL: '60'
25+
2326
steps:
2427
- name: Checkout code
2528
uses: actions/checkout@v2
@@ -45,4 +48,4 @@ jobs:
4548

4649
- run: opam exec -- make all
4750

48-
- run: opam exec -- make run_test
51+
- run: opam exec -- make run_test

domainslib.opam

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ depends: [
1313
"dune" {>= "3.0"}
1414
"lockfree" { >= "0.2.0"}
1515
"mirage-clock-unix" {with-test}
16+
"qcheck-core" {with-test & >= "0.20"}
17+
"qcheck-multicoretests-util" {with-test & >= "0.1"}
18+
"qcheck-stm" {with-test & >= "0.1"}
1619
]
1720
depopts: []
1821
build: [

test/chan_stm_tests.ml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
open QCheck
2+
open Domainslib
3+
open STM
4+
5+
(** This contains sequential and parallel model-based tests of [Domainslib.Chan] *)
6+
7+
module ChConf =
8+
struct
9+
type state = int list
10+
type sut = int Domainslib.Chan.t
11+
type cmd =
12+
| Send of int
13+
| Send_poll of int
14+
| Recv
15+
| Recv_poll
16+
17+
let show_cmd c = match c with
18+
| Send i -> "Send" ^ (string_of_int i)
19+
| Send_poll i -> "Send_poll" ^ (string_of_int i)
20+
| Recv -> "Recv"
21+
| Recv_poll -> "Recv_poll"
22+
23+
let capacity = 8
24+
25+
let arb_cmd s =
26+
let int_gen = Gen.nat in
27+
QCheck.make ~print:show_cmd
28+
(if s=[]
29+
then
30+
Gen.oneof
31+
[Gen.map (fun i -> Send i) int_gen;
32+
Gen.map (fun i -> Send_poll i) int_gen;
33+
Gen.return Recv_poll] (* don't generate blocking Recv cmds on an empty channel *)
34+
else
35+
if List.length s >= capacity
36+
then
37+
Gen.oneof
38+
[Gen.map (fun i -> Send_poll i) int_gen;
39+
Gen.return Recv;
40+
Gen.return Recv_poll] (* don't generate blocking Send cmds on a full channel *)
41+
else
42+
Gen.oneof
43+
[Gen.map (fun i -> Send i) int_gen;
44+
Gen.map (fun i -> Send_poll i) int_gen;
45+
Gen.return Recv;
46+
Gen.return Recv_poll])
47+
let init_state = []
48+
let init_sut () = Chan.make_bounded capacity
49+
let cleanup _ = ()
50+
51+
let next_state c s = match c with
52+
| Send i -> if List.length s < capacity then s@[i] else s
53+
| Send_poll i -> if List.length s < capacity then s@[i] else s
54+
| Recv -> begin match s with [] -> [] | _::s' -> s' end
55+
| Recv_poll -> begin match s with [] -> [] | _::s' -> s' end
56+
57+
let precond c s = match c,s with
58+
| Recv, [] -> false
59+
| Send _, _ -> List.length s < capacity
60+
| _, _ -> true
61+
62+
let run c chan =
63+
match c with
64+
| Send i -> Res (unit, Chan.send chan i)
65+
| Send_poll i -> Res (bool, Chan.send_poll chan i)
66+
| Recv -> Res (int, Chan.recv chan)
67+
| Recv_poll -> Res (option int, Chan.recv_poll chan)
68+
69+
let postcond c s res = match c,res with
70+
| Send _, Res ((Unit,_),_) -> (List.length s < capacity)
71+
| Send_poll _, Res ((Bool,_),res) -> res = (List.length s < capacity)
72+
| Recv, Res ((Int,_),res) -> (match s with [] -> false | res'::_ -> Int.equal res res')
73+
| Recv_poll, Res ((Option Int,_),opt) -> (match s with [] -> None | res'::_ -> Some res') = opt
74+
| _,_ -> false
75+
end
76+
77+
78+
module ChT_seq = STM_sequential.Make(ChConf)
79+
module ChT_dom = STM_domain.Make(ChConf)
80+
81+
let () =
82+
let count = 500 in
83+
QCheck_base_runner.run_tests_main [
84+
ChT_seq.agree_test ~count ~name:"STM Domainslib.Chan test sequential";
85+
ChT_dom.agree_test_par ~count ~name:"STM Domainslib.Chan test parallel";
86+
]

test/dune

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,31 @@
110110
(libraries domainslib)
111111
(modules off_by_one)
112112
(modes native))
113+
114+
;; Custom property-based tests using QCheck
115+
116+
(test
117+
(name task_one_dep)
118+
(modules task_one_dep)
119+
(libraries qcheck-multicoretests-util qcheck-core qcheck-core.runner domainslib)
120+
(action (run %{test} --verbose)))
121+
122+
(test
123+
(name task_more_deps)
124+
(modules task_more_deps)
125+
(libraries qcheck-multicoretests-util qcheck-core qcheck-core.runner domainslib)
126+
(action (run %{test} --verbose)))
127+
128+
(test
129+
(name task_parallel)
130+
(modules task_parallel)
131+
(libraries qcheck-multicoretests-util qcheck-core qcheck-core.runner domainslib)
132+
(action (run %{test} --verbose)))
133+
134+
;; STM_sequential and STM_domain test of Domainslib.Chan
135+
136+
(test
137+
(name chan_stm_tests)
138+
(modules chan_stm_tests)
139+
(libraries qcheck-stm.sequential qcheck-stm.domain domainslib)
140+
(action (run %{test} --verbose)))

test/task_more_deps.ml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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

Comments
 (0)