Skip to content

Commit 1dc53af

Browse files
authored
Merge pull request #129 from Lucccyo/sys-stm-tests
[WIP] Add STM tests for Sys module
2 parents 90eabb3 + f932eda commit 1dc53af

File tree

2 files changed

+329
-0
lines changed

2 files changed

+329
-0
lines changed

src/sys/dune

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
;; Test of the Sys library
2+
3+
(test
4+
(name stm_tests)
5+
(modules stm_tests)
6+
(package multicoretests)
7+
(libraries qcheck-stm.sequential qcheck-stm.domain)
8+
(preprocess (pps ppx_deriving.show))
9+
(action (run %{test} --verbose))
10+
)

src/sys/stm_tests.ml

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
open QCheck
2+
open STM
3+
4+
module SConf =
5+
struct
6+
type path = string list [@@deriving show]
7+
8+
type cmd =
9+
| File_exists of path
10+
| Mkdir of path * string
11+
| Rmdir of path * string
12+
| Readdir of path
13+
| Mkfile of path * string
14+
[@@deriving show { with_path = false }]
15+
16+
module Map_names = Map.Make (String)
17+
18+
type filesys =
19+
| Directory of {fs_map: filesys Map_names.t}
20+
| File
21+
22+
type state = filesys
23+
24+
type sut = unit
25+
26+
let (/) = Filename.concat
27+
28+
let update_map_name map_name k v = Map_names.update k (fun _ -> Some v) map_name
29+
30+
(* var gen_existing_path : filesys -> path Gen.t *)
31+
let rec gen_existing_path fs =
32+
match fs with
33+
| File -> Gen.return []
34+
| Directory d ->
35+
(match Map_names.bindings d.fs_map with
36+
| [] -> Gen.return []
37+
| bindings -> Gen.(oneofl bindings >>= fun (n, sub_fs) ->
38+
Gen.oneof [
39+
Gen.return [n];
40+
Gen.map (fun l -> n::l) (gen_existing_path sub_fs)]
41+
)
42+
)
43+
44+
(* var gen_existing_pair : filesys -> (path * string) option Gen.t *)
45+
let rec gen_existing_pair fs = match fs with
46+
| File -> Gen.return None (*failwith "no sandbox directory"*)
47+
| Directory d ->
48+
(match Map_names.bindings d.fs_map with
49+
| [] -> Gen.return None
50+
| bindings ->
51+
Gen.(oneofl bindings >>= fun (n, sub_fs) ->
52+
oneof [
53+
return (Some ([],n));
54+
map (function None -> Some ([],n)
55+
| Some (path,name) -> Some (n::path,name)) (gen_existing_pair sub_fs)]
56+
)
57+
)
58+
59+
let name_gen = Gen.oneofl ["aaa" ; "bbb" ; "ccc" ; "ddd" ; "eee"]
60+
let path_gen s = Gen.(oneof [gen_existing_path s; list_size (int_bound 5) name_gen]) (* can be empty *)
61+
let pair_gen s =
62+
let fresh_pair_gen = Gen.(pair (list_size (int_bound 5) name_gen)) name_gen in
63+
Gen.(oneof [
64+
fresh_pair_gen;
65+
(gen_existing_pair s >>= function None -> fresh_pair_gen
66+
| Some (p,_) -> map (fun n -> (p,n)) name_gen);
67+
(gen_existing_pair s >>= function None -> fresh_pair_gen
68+
| Some (p,n) -> return (p,n));
69+
])
70+
71+
let arb_cmd s =
72+
QCheck.make ~print:show_cmd
73+
Gen.(oneof [
74+
map (fun path -> File_exists path) (path_gen s);
75+
map (fun (path,new_dir_name) -> Mkdir (path, new_dir_name)) (pair_gen s);
76+
map (fun (path,delete_dir_name) -> Rmdir (path, delete_dir_name)) (pair_gen s);
77+
map (fun path -> Readdir path) (path_gen s);
78+
map (fun (path,new_file_name) -> Mkfile (path, new_file_name)) (pair_gen s);
79+
])
80+
81+
let sandbox_root = "_sandbox"
82+
83+
let init_state = Directory {fs_map = Map_names.empty}
84+
85+
let rec find_opt_model fs path =
86+
match fs with
87+
| File ->
88+
if path = []
89+
then Some fs
90+
else None
91+
| Directory d ->
92+
(match path with
93+
| [] -> Some (Directory d)
94+
| hd :: tl ->
95+
(match Map_names.find_opt hd d.fs_map with
96+
| None -> None
97+
| Some fs -> find_opt_model fs tl))
98+
99+
let mem_model fs path = find_opt_model fs path <> None
100+
101+
let rec mkdir_model fs path new_dir_name =
102+
match fs with
103+
| File -> fs
104+
| Directory d ->
105+
(match path with
106+
| [] ->
107+
let new_dir = Directory {fs_map = Map_names.empty} in
108+
Directory {fs_map = Map_names.add new_dir_name new_dir d.fs_map}
109+
| next_in_path :: tl_path ->
110+
(match Map_names.find_opt next_in_path d.fs_map with
111+
| None -> fs
112+
| Some sub_fs ->
113+
let nfs = mkdir_model sub_fs tl_path new_dir_name in
114+
if nfs = sub_fs
115+
then fs
116+
else
117+
let new_map = Map_names.remove next_in_path d.fs_map in
118+
let new_map = Map_names.add next_in_path nfs new_map in
119+
Directory {fs_map = new_map}))
120+
121+
let readdir_model fs path =
122+
match find_opt_model fs path with
123+
| None -> None
124+
| Some fs ->
125+
(match fs with
126+
| File -> None
127+
| Directory d -> Some (Map_names.fold (fun k _ l -> k::l) d.fs_map []))
128+
129+
let rec rmdir_model fs path delete_dir_name =
130+
match fs with
131+
| File -> fs
132+
| Directory d ->
133+
(match path with
134+
| [] ->
135+
(match Map_names.find_opt delete_dir_name d.fs_map with
136+
| Some (Directory target) when Map_names.is_empty target.fs_map ->
137+
Directory {fs_map = Map_names.remove delete_dir_name d.fs_map}
138+
| None | Some File | Some (Directory _) -> fs)
139+
| next_in_path :: tl_path ->
140+
(match Map_names.find_opt next_in_path d.fs_map with
141+
| None -> fs
142+
| Some sub_fs ->
143+
let nfs = rmdir_model sub_fs tl_path delete_dir_name in
144+
if nfs = sub_fs
145+
then fs
146+
else Directory {fs_map = (update_map_name d.fs_map next_in_path nfs)}))
147+
148+
let rec mkfile_model fs path new_file_name =
149+
match fs with
150+
| File -> fs
151+
| Directory d ->
152+
(match path with
153+
| [] ->
154+
let new_file = File in
155+
Directory {fs_map = Map_names.add new_file_name new_file d.fs_map}
156+
| next_in_path :: tl_path ->
157+
(match Map_names.find_opt next_in_path d.fs_map with
158+
| None -> fs
159+
| Some sub_fs ->
160+
let nfs = mkfile_model sub_fs tl_path new_file_name in
161+
if nfs = sub_fs
162+
then fs
163+
else Directory {fs_map = update_map_name d.fs_map next_in_path nfs}))
164+
165+
let next_state c fs =
166+
match c with
167+
| File_exists _path -> fs
168+
| Mkdir (path, new_dir_name) ->
169+
if mem_model fs (path @ [new_dir_name])
170+
then fs
171+
else mkdir_model fs path new_dir_name
172+
| Rmdir (path,delete_dir_name) ->
173+
if mem_model fs (path @ [delete_dir_name])
174+
then rmdir_model fs path delete_dir_name
175+
else fs
176+
| Readdir _path -> fs
177+
| Mkfile (path, new_file_name) ->
178+
if mem_model fs (path @ [new_file_name])
179+
then fs
180+
else mkfile_model fs path new_file_name
181+
182+
let init_sut () =
183+
try Sys.mkdir sandbox_root 0o700
184+
with Sys_error msg when msg = sandbox_root ^ ": File exists" -> ()
185+
186+
let cleanup _ =
187+
match Sys.os_type with
188+
| "Unix" -> ignore (Sys.command ("rm -r " ^ Filename.quote sandbox_root))
189+
| "Win32" -> ignore (Sys.command ("rd /s /q " ^ Filename.quote sandbox_root))
190+
| v -> failwith ("Sys tests not working with " ^ v)
191+
192+
let precond _c _s = true
193+
194+
let p path = (List.fold_left (/) sandbox_root path)
195+
196+
let mkfile filepath =
197+
let flags = [Open_wronly; Open_creat; Open_excl] in
198+
Out_channel.with_open_gen flags 0o666 filepath (fun _ -> ())
199+
200+
let run c _file_name =
201+
match c with
202+
| File_exists path -> Res (bool, Sys.file_exists (p path))
203+
| Mkdir (path, new_dir_name) ->
204+
Res (result unit exn, protect (Sys.mkdir ((p path) / new_dir_name)) 0o755)
205+
| Rmdir (path, delete_dir_name) ->
206+
Res (result unit exn, protect (Sys.rmdir) ((p path) / delete_dir_name))
207+
| Readdir path ->
208+
Res (result (array string) exn, protect (Sys.readdir) (p path))
209+
| Mkfile (path, new_file_name) ->
210+
Res (result unit exn, protect mkfile (p path / new_file_name))
211+
212+
let fs_is_a_dir fs = match fs with | Directory _ -> true | File -> false
213+
214+
let path_is_a_dir fs path =
215+
match find_opt_model fs path with
216+
| None -> false
217+
| Some target_fs -> fs_is_a_dir target_fs
218+
219+
let postcond c (fs: filesys) res =
220+
match c, res with
221+
| File_exists path, Res ((Bool,_),b) -> b = mem_model fs path
222+
| Mkdir (path, new_dir_name), Res ((Result (Unit,Exn),_), res) ->
223+
let complete_path = (path @ [new_dir_name]) in
224+
(match res with
225+
| Error err ->
226+
(match err with
227+
| Sys_error s ->
228+
(s = (p complete_path) ^ ": Permission denied") ||
229+
(s = (p complete_path) ^ ": File exists" && mem_model fs complete_path) ||
230+
((s = (p complete_path) ^ ": No such file or directory"
231+
|| s = (p complete_path) ^ ": Invalid argument") && not (mem_model fs path)) ||
232+
if Sys.win32 && not (path_is_a_dir fs complete_path)
233+
then s = (p complete_path) ^ ": No such file or directory"
234+
else s = (p complete_path) ^ ": Not a directory"
235+
| _ -> false)
236+
| Ok () -> mem_model fs path && path_is_a_dir fs path && not (mem_model fs complete_path))
237+
| Rmdir (path, delete_dir_name), Res ((Result (Unit,Exn),_), res) ->
238+
let complete_path = (path @ [delete_dir_name]) in
239+
(match res with
240+
| Error err ->
241+
(match err with
242+
| Sys_error s ->
243+
(s = (p complete_path) ^ ": Permission denied") ||
244+
(s = (p complete_path) ^ ": Directory not empty" && not (readdir_model fs complete_path = Some [])) ||
245+
(s = (p complete_path) ^ ": No such file or directory" && not (mem_model fs complete_path)) ||
246+
if Sys.win32 && not (path_is_a_dir fs complete_path) (* if not a directory *)
247+
then s = (p complete_path) ^ ": Invalid argument"
248+
else s = (p complete_path) ^ ": Not a directory"
249+
| _ -> false)
250+
| Ok () ->
251+
mem_model fs complete_path && path_is_a_dir fs complete_path && readdir_model fs complete_path = Some [])
252+
| Readdir path, Res ((Result (Array String,Exn),_), res) ->
253+
(match res with
254+
| Error err ->
255+
(match err with
256+
| Sys_error s ->
257+
(s = (p path) ^ ": Permission denied") ||
258+
(s = (p path) ^ ": No such file or directory" && not (mem_model fs path)) ||
259+
if Sys.win32 && not (path_is_a_dir fs path) (* if not a directory *)
260+
then s = (p path) ^ ": Invalid argument"
261+
else s = (p path) ^ ": Not a directory"
262+
| _ -> false)
263+
| Ok array_of_subdir ->
264+
(* Temporary work around for mingW, see https://github.com/ocaml/ocaml/issues/11829 *)
265+
if Sys.win32 && not (mem_model fs path)
266+
then array_of_subdir = [||]
267+
else
268+
(mem_model fs path && path_is_a_dir fs path &&
269+
(match readdir_model fs path with
270+
| None -> false
271+
| Some l ->
272+
List.sort String.compare l
273+
= List.sort String.compare (Array.to_list array_of_subdir))))
274+
| Mkfile (path, new_file_name), Res ((Result (Unit,Exn),_),res) -> (
275+
let complete_path = path @ [ new_file_name ] in
276+
let concatenated_path = p complete_path in
277+
let match_msg err msg = err = concatenated_path ^ ": " ^ msg in
278+
let match_msgs err = List.exists (match_msg err) in
279+
let msgs_already_exists = ["File exists"; "Permission denied"]
280+
(* Permission denied: seen (sometimes?) on Windows *)
281+
and msgs_non_existent_dir = ["No such file or directory";
282+
"Invalid argument";
283+
"Permission denied"]
284+
(* Invalid argument: seen on macOS
285+
Permission denied: seen on Windows *)
286+
and msg_path_not_dir =
287+
match Sys.os_type with
288+
| "Unix" -> "Not a directory"
289+
| "Win32" -> "No such file or directory"
290+
| v -> failwith ("Sys tests not working with " ^ v)
291+
in
292+
match res with
293+
| Error err -> (
294+
match err with
295+
| Sys_error s ->
296+
(mem_model fs complete_path && match_msgs s msgs_already_exists)
297+
|| (not (mem_model fs path) && match_msgs s msgs_non_existent_dir)
298+
|| (not (path_is_a_dir fs path) && match_msg s msg_path_not_dir)
299+
| _ -> false)
300+
| Ok () -> path_is_a_dir fs path && not (mem_model fs complete_path))
301+
| _,_ -> false
302+
end
303+
304+
let uname_os () =
305+
let ic = Unix.open_process_in "uname -s" in
306+
let os = In_channel.input_line ic in
307+
ignore (Unix.close_process_in ic);
308+
os
309+
310+
module Sys_seq = STM_sequential.Make(SConf)
311+
module Sys_dom = STM_domain.Make(SConf)
312+
313+
;;
314+
QCheck_base_runner.run_tests_main [
315+
Sys_seq.agree_test ~count:1000 ~name:"STM Sys test sequential";
316+
if Sys.unix && uname_os () = Some "Linux"
317+
then Sys_dom.agree_test_par ~count:200 ~name:"STM Sys test parallel"
318+
else Sys_dom.neg_agree_test_par ~count:1000 ~name:"STM Sys test parallel"
319+
]

0 commit comments

Comments
 (0)