-
Notifications
You must be signed in to change notification settings - Fork 16
[WIP] Add STM tests for Sys module #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
ec4860d
add commands fileexists, mkdir, rmdir, readdir with version 1 of type…
Lucccyo 61eb2d7
working version 2 of type filesys
Lucccyo 60af52b
adding touch command, new interface STM_base, some cleanup
Lucccyo 24eeda9
add windows os support
Lucccyo fd28eef
style adjustment, make touch return an integer, dune file update
Lucccyo 06ce34d
temporary fixing mingW Windows bug
Lucccyo 36bfaa2
separate error messages between unix and win32
Lucccyo 8f7442e
[TRY] fix error handling from Win32 on mkdir (not a directory => no s…
Lucccyo 0f40582
modifying touch - need to know how windows manage
Lucccyo c7c4c01
track windows error
Lucccyo c953955
separate OS
Lucccyo 192d6a2
make touch's postcond 'true' bc it is not a feature tested
Lucccyo e1914c5
separate para tests for unix based and negative para tests for windows
Lucccyo ac12bd4
put negative tests to Windows and MacOS machine
Lucccyo ab83b4c
update path generator, 50% to generate an existing path, 50% to gener…
Lucccyo 8b9c267
whitespace changes
jmid 0e1a64c
attempt to clean-up Windows CI logs by silencing its stderr
jmid 79054f0
pass environment
jmid df59d77
try redirecting output instead
jmid d0e425a
Compute the sandbox root only once
shym 573fb27
Use Map.Make.update to update the Map_names
shym 8030e74
Use Sys.mkdir with safer permissions for the sandbox root
shym 9e5454c
Use safer removal commands
shym 23faa61
Remove all permissions
shym 670f395
Use 'echo' to implement Touch on all OSes
shym 1f42255
Use a type `path` to clarify `cmd` signature
shym cd3c5ea
Convert Touch into a Mkfile
shym 47e65f4
Use a relative path for the sandbox
shym 29fb75c
Add other error messages spotted on Windows and macOS
shym a4f2721
Reclaim all resources for the `uname` process
shym 4d7900e
add pair_gen for generating path and name together
jmid 88ccf15
refactor pair_gen generator
jmid f932eda
remove commented out code
jmid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| ;; Test of the Sys library | ||
|
|
||
| (test | ||
| (name stm_tests) | ||
| (modules stm_tests) | ||
| (package multicoretests) | ||
| (libraries qcheck-stm.sequential qcheck-stm.domain) | ||
| (preprocess (pps ppx_deriving.show)) | ||
| (action (run %{test} --verbose)) | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,319 @@ | ||
| open QCheck | ||
| open STM | ||
|
|
||
| module SConf = | ||
| struct | ||
| type path = string list [@@deriving show] | ||
|
|
||
| type cmd = | ||
| | File_exists of path | ||
| | Mkdir of path * string | ||
| | Rmdir of path * string | ||
| | Readdir of path | ||
| | Mkfile of path * string | ||
| [@@deriving show { with_path = false }] | ||
|
|
||
| module Map_names = Map.Make (String) | ||
|
|
||
| type filesys = | ||
| | Directory of {fs_map: filesys Map_names.t} | ||
| | File | ||
|
|
||
| type state = filesys | ||
|
|
||
| type sut = unit | ||
|
|
||
| let (/) = Filename.concat | ||
|
|
||
| let update_map_name map_name k v = Map_names.update k (fun _ -> Some v) map_name | ||
|
|
||
| (* var gen_existing_path : filesys -> path Gen.t *) | ||
| let rec gen_existing_path fs = | ||
| match fs with | ||
| | File -> Gen.return [] | ||
| | Directory d -> | ||
| (match Map_names.bindings d.fs_map with | ||
| | [] -> Gen.return [] | ||
| | bindings -> Gen.(oneofl bindings >>= fun (n, sub_fs) -> | ||
| Gen.oneof [ | ||
| Gen.return [n]; | ||
| Gen.map (fun l -> n::l) (gen_existing_path sub_fs)] | ||
| ) | ||
| ) | ||
|
|
||
| (* var gen_existing_pair : filesys -> (path * string) option Gen.t *) | ||
| let rec gen_existing_pair fs = match fs with | ||
| | File -> Gen.return None (*failwith "no sandbox directory"*) | ||
| | Directory d -> | ||
| (match Map_names.bindings d.fs_map with | ||
| | [] -> Gen.return None | ||
| | bindings -> | ||
| Gen.(oneofl bindings >>= fun (n, sub_fs) -> | ||
| oneof [ | ||
| return (Some ([],n)); | ||
| map (function None -> Some ([],n) | ||
| | Some (path,name) -> Some (n::path,name)) (gen_existing_pair sub_fs)] | ||
| ) | ||
| ) | ||
|
|
||
| let name_gen = Gen.oneofl ["aaa" ; "bbb" ; "ccc" ; "ddd" ; "eee"] | ||
| let path_gen s = Gen.(oneof [gen_existing_path s; list_size (int_bound 5) name_gen]) (* can be empty *) | ||
| let pair_gen s = | ||
| let fresh_pair_gen = Gen.(pair (list_size (int_bound 5) name_gen)) name_gen in | ||
| Gen.(oneof [ | ||
| fresh_pair_gen; | ||
| (gen_existing_pair s >>= function None -> fresh_pair_gen | ||
| | Some (p,_) -> map (fun n -> (p,n)) name_gen); | ||
| (gen_existing_pair s >>= function None -> fresh_pair_gen | ||
| | Some (p,n) -> return (p,n)); | ||
| ]) | ||
|
|
||
| let arb_cmd s = | ||
| QCheck.make ~print:show_cmd | ||
| Gen.(oneof [ | ||
| map (fun path -> File_exists path) (path_gen s); | ||
| map (fun (path,new_dir_name) -> Mkdir (path, new_dir_name)) (pair_gen s); | ||
| map (fun (path,delete_dir_name) -> Rmdir (path, delete_dir_name)) (pair_gen s); | ||
| map (fun path -> Readdir path) (path_gen s); | ||
| map (fun (path,new_file_name) -> Mkfile (path, new_file_name)) (pair_gen s); | ||
| ]) | ||
|
|
||
| let sandbox_root = "_sandbox" | ||
|
|
||
| let init_state = Directory {fs_map = Map_names.empty} | ||
|
|
||
| let rec find_opt_model fs path = | ||
| match fs with | ||
| | File -> | ||
| if path = [] | ||
| then Some fs | ||
| else None | ||
| | Directory d -> | ||
| (match path with | ||
| | [] -> Some (Directory d) | ||
| | hd :: tl -> | ||
| (match Map_names.find_opt hd d.fs_map with | ||
| | None -> None | ||
| | Some fs -> find_opt_model fs tl)) | ||
|
|
||
| let mem_model fs path = find_opt_model fs path <> None | ||
|
|
||
| let rec mkdir_model fs path new_dir_name = | ||
| match fs with | ||
| | File -> fs | ||
| | Directory d -> | ||
| (match path with | ||
| | [] -> | ||
| let new_dir = Directory {fs_map = Map_names.empty} in | ||
| Directory {fs_map = Map_names.add new_dir_name new_dir d.fs_map} | ||
| | next_in_path :: tl_path -> | ||
| (match Map_names.find_opt next_in_path d.fs_map with | ||
| | None -> fs | ||
| | Some sub_fs -> | ||
| let nfs = mkdir_model sub_fs tl_path new_dir_name in | ||
| if nfs = sub_fs | ||
| then fs | ||
| else | ||
| let new_map = Map_names.remove next_in_path d.fs_map in | ||
| let new_map = Map_names.add next_in_path nfs new_map in | ||
| Directory {fs_map = new_map})) | ||
|
|
||
| let readdir_model fs path = | ||
| match find_opt_model fs path with | ||
| | None -> None | ||
| | Some fs -> | ||
| (match fs with | ||
| | File -> None | ||
| | Directory d -> Some (Map_names.fold (fun k _ l -> k::l) d.fs_map [])) | ||
|
|
||
| let rec rmdir_model fs path delete_dir_name = | ||
| match fs with | ||
| | File -> fs | ||
| | Directory d -> | ||
| (match path with | ||
| | [] -> | ||
| (match Map_names.find_opt delete_dir_name d.fs_map with | ||
| | Some (Directory target) when Map_names.is_empty target.fs_map -> | ||
| Directory {fs_map = Map_names.remove delete_dir_name d.fs_map} | ||
| | None | Some File | Some (Directory _) -> fs) | ||
| | next_in_path :: tl_path -> | ||
| (match Map_names.find_opt next_in_path d.fs_map with | ||
| | None -> fs | ||
| | Some sub_fs -> | ||
| let nfs = rmdir_model sub_fs tl_path delete_dir_name in | ||
| if nfs = sub_fs | ||
| then fs | ||
| else Directory {fs_map = (update_map_name d.fs_map next_in_path nfs)})) | ||
|
|
||
| let rec mkfile_model fs path new_file_name = | ||
| match fs with | ||
| | File -> fs | ||
| | Directory d -> | ||
| (match path with | ||
| | [] -> | ||
| let new_file = File in | ||
| Directory {fs_map = Map_names.add new_file_name new_file d.fs_map} | ||
| | next_in_path :: tl_path -> | ||
| (match Map_names.find_opt next_in_path d.fs_map with | ||
| | None -> fs | ||
| | Some sub_fs -> | ||
| let nfs = mkfile_model sub_fs tl_path new_file_name in | ||
| if nfs = sub_fs | ||
| then fs | ||
| else Directory {fs_map = update_map_name d.fs_map next_in_path nfs})) | ||
|
|
||
| let next_state c fs = | ||
| match c with | ||
| | File_exists _path -> fs | ||
| | Mkdir (path, new_dir_name) -> | ||
| if mem_model fs (path @ [new_dir_name]) | ||
| then fs | ||
| else mkdir_model fs path new_dir_name | ||
| | Rmdir (path,delete_dir_name) -> | ||
| if mem_model fs (path @ [delete_dir_name]) | ||
| then rmdir_model fs path delete_dir_name | ||
| else fs | ||
| | Readdir _path -> fs | ||
| | Mkfile (path, new_file_name) -> | ||
| if mem_model fs (path @ [new_file_name]) | ||
| then fs | ||
| else mkfile_model fs path new_file_name | ||
|
|
||
| let init_sut () = | ||
| try Sys.mkdir sandbox_root 0o700 | ||
| with Sys_error msg when msg = sandbox_root ^ ": File exists" -> () | ||
|
|
||
| let cleanup _ = | ||
| match Sys.os_type with | ||
| | "Unix" -> ignore (Sys.command ("rm -r " ^ Filename.quote sandbox_root)) | ||
| | "Win32" -> ignore (Sys.command ("rd /s /q " ^ Filename.quote sandbox_root)) | ||
| | v -> failwith ("Sys tests not working with " ^ v) | ||
|
|
||
| let precond _c _s = true | ||
|
|
||
| let p path = (List.fold_left (/) sandbox_root path) | ||
|
|
||
| let mkfile filepath = | ||
| let flags = [Open_wronly; Open_creat; Open_excl] in | ||
| Out_channel.with_open_gen flags 0o666 filepath (fun _ -> ()) | ||
|
|
||
| let run c _file_name = | ||
| match c with | ||
| | File_exists path -> Res (bool, Sys.file_exists (p path)) | ||
| | Mkdir (path, new_dir_name) -> | ||
| Res (result unit exn, protect (Sys.mkdir ((p path) / new_dir_name)) 0o755) | ||
| | Rmdir (path, delete_dir_name) -> | ||
| Res (result unit exn, protect (Sys.rmdir) ((p path) / delete_dir_name)) | ||
| | Readdir path -> | ||
| Res (result (array string) exn, protect (Sys.readdir) (p path)) | ||
| | Mkfile (path, new_file_name) -> | ||
| Res (result unit exn, protect mkfile (p path / new_file_name)) | ||
|
|
||
| let fs_is_a_dir fs = match fs with | Directory _ -> true | File -> false | ||
|
|
||
| let path_is_a_dir fs path = | ||
| match find_opt_model fs path with | ||
| | None -> false | ||
| | Some target_fs -> fs_is_a_dir target_fs | ||
|
|
||
| let postcond c (fs: filesys) res = | ||
| match c, res with | ||
| | File_exists path, Res ((Bool,_),b) -> b = mem_model fs path | ||
| | Mkdir (path, new_dir_name), Res ((Result (Unit,Exn),_), res) -> | ||
| let complete_path = (path @ [new_dir_name]) in | ||
| (match res with | ||
| | Error err -> | ||
| (match err with | ||
| | Sys_error s -> | ||
| (s = (p complete_path) ^ ": Permission denied") || | ||
| (s = (p complete_path) ^ ": File exists" && mem_model fs complete_path) || | ||
| ((s = (p complete_path) ^ ": No such file or directory" | ||
| || s = (p complete_path) ^ ": Invalid argument") && not (mem_model fs path)) || | ||
| if Sys.win32 && not (path_is_a_dir fs complete_path) | ||
| then s = (p complete_path) ^ ": No such file or directory" | ||
| else s = (p complete_path) ^ ": Not a directory" | ||
| | _ -> false) | ||
| | Ok () -> mem_model fs path && path_is_a_dir fs path && not (mem_model fs complete_path)) | ||
| | Rmdir (path, delete_dir_name), Res ((Result (Unit,Exn),_), res) -> | ||
| let complete_path = (path @ [delete_dir_name]) in | ||
| (match res with | ||
| | Error err -> | ||
| (match err with | ||
| | Sys_error s -> | ||
| (s = (p complete_path) ^ ": Permission denied") || | ||
| (s = (p complete_path) ^ ": Directory not empty" && not (readdir_model fs complete_path = Some [])) || | ||
| (s = (p complete_path) ^ ": No such file or directory" && not (mem_model fs complete_path)) || | ||
| if Sys.win32 && not (path_is_a_dir fs complete_path) (* if not a directory *) | ||
| then s = (p complete_path) ^ ": Invalid argument" | ||
| else s = (p complete_path) ^ ": Not a directory" | ||
| | _ -> false) | ||
| | Ok () -> | ||
| mem_model fs complete_path && path_is_a_dir fs complete_path && readdir_model fs complete_path = Some []) | ||
| | Readdir path, Res ((Result (Array String,Exn),_), res) -> | ||
| (match res with | ||
| | Error err -> | ||
| (match err with | ||
| | Sys_error s -> | ||
| (s = (p path) ^ ": Permission denied") || | ||
| (s = (p path) ^ ": No such file or directory" && not (mem_model fs path)) || | ||
| if Sys.win32 && not (path_is_a_dir fs path) (* if not a directory *) | ||
| then s = (p path) ^ ": Invalid argument" | ||
| else s = (p path) ^ ": Not a directory" | ||
| | _ -> false) | ||
| | Ok array_of_subdir -> | ||
| (* Temporary work around for mingW, see https://github.com/ocaml/ocaml/issues/11829 *) | ||
| if Sys.win32 && not (mem_model fs path) | ||
| then array_of_subdir = [||] | ||
| else | ||
| (mem_model fs path && path_is_a_dir fs path && | ||
| (match readdir_model fs path with | ||
| | None -> false | ||
| | Some l -> | ||
| List.sort String.compare l | ||
| = List.sort String.compare (Array.to_list array_of_subdir)))) | ||
| | Mkfile (path, new_file_name), Res ((Result (Unit,Exn),_),res) -> ( | ||
| let complete_path = path @ [ new_file_name ] in | ||
| let concatenated_path = p complete_path in | ||
| let match_msg err msg = err = concatenated_path ^ ": " ^ msg in | ||
| let match_msgs err = List.exists (match_msg err) in | ||
| let msgs_already_exists = ["File exists"; "Permission denied"] | ||
| (* Permission denied: seen (sometimes?) on Windows *) | ||
| and msgs_non_existent_dir = ["No such file or directory"; | ||
| "Invalid argument"; | ||
| "Permission denied"] | ||
| (* Invalid argument: seen on macOS | ||
| Permission denied: seen on Windows *) | ||
| and msg_path_not_dir = | ||
| match Sys.os_type with | ||
| | "Unix" -> "Not a directory" | ||
| | "Win32" -> "No such file or directory" | ||
| | v -> failwith ("Sys tests not working with " ^ v) | ||
| in | ||
| match res with | ||
| | Error err -> ( | ||
| match err with | ||
| | Sys_error s -> | ||
| (mem_model fs complete_path && match_msgs s msgs_already_exists) | ||
| || (not (mem_model fs path) && match_msgs s msgs_non_existent_dir) | ||
| || (not (path_is_a_dir fs path) && match_msg s msg_path_not_dir) | ||
| | _ -> false) | ||
| | Ok () -> path_is_a_dir fs path && not (mem_model fs complete_path)) | ||
| | _,_ -> false | ||
| end | ||
|
|
||
| let uname_os () = | ||
| let ic = Unix.open_process_in "uname -s" in | ||
| let os = In_channel.input_line ic in | ||
| ignore (Unix.close_process_in ic); | ||
| os | ||
|
|
||
| module Sys_seq = STM_sequential.Make(SConf) | ||
| module Sys_dom = STM_domain.Make(SConf) | ||
|
|
||
| ;; | ||
| QCheck_base_runner.run_tests_main [ | ||
| Sys_seq.agree_test ~count:1000 ~name:"STM Sys test sequential"; | ||
| if Sys.unix && uname_os () = Some "Linux" | ||
| then Sys_dom.agree_test_par ~count:200 ~name:"STM Sys test parallel" | ||
| else Sys_dom.neg_agree_test_par ~count:1000 ~name:"STM Sys test parallel" | ||
| ] | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn’t find in the PR or commit message why the test behave differently under mac and linux, so I was curious what happens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was purely based on empirical observations - we have not been able to trigger when running these under Linux 🤷♂️