Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Testutil.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
(* Utilities for writing test suites for Alcotest. *) open Printf type test = string * (unit -> unit) (* We use '>' because '.' is common in files and we don't want to split 'foo.py' into 'foo' and 'py'. Alternatively, we could keep paths as string lists and avoid the issue of choosing a separator. *) let path_sep = '>' let path_sep_str = String.make 1 path_sep let pretty_path_sep_str = " > " let list_map f l = List.rev_map f l |> List.rev let list_flatten ll = List.fold_left (fun acc l -> List.rev_append l acc) [] ll |> List.rev let pack_tests suite_name (tests : test list) : test list = list_map (fun (path, func) -> (suite_name ^ path_sep_str ^ path, func)) tests let pack_suites suite_name (tests : test list list) : test list = tests |> list_flatten |> pack_tests suite_name (* Sort by path. For this, we split the paths on '>' and then take advantage of the polymorphic 'compare' which does the right thing. Compare: compare "a>b" "a b" = 1 (* wrong *) vs. compare ["a"; "b"] ["a b"] = -1 (* correct *) *) let sort (tests : test list) : test list = tests |> list_map (fun ((name, _func) as test) -> let k = String.split_on_char path_sep name in (k, test)) |> List.stable_sort (fun (a, _) (b, _) -> compare a b) |> list_map snd (* "Foo.Bar.hello" -> ("Foo.Bar", "hello") "hello" -> ("", "hello") "" -> ("", "") *) let split_path s = match String.rindex_opt s path_sep with | None -> ("", s) | Some dot_pos -> let left_len = dot_pos in let right_len = String.length s - left_len - 1 in (String.sub s 0 left_len, String.sub s (dot_pos + 1) right_len) (* Group pairs by the first value of the pair, preserving the original order as much as possible. *) let group_by_key key_value_list = let tbl = Hashtbl.create 100 in key_value_list |> List.iteri (fun pos (k, v) -> let tbl_v = match Hashtbl.find_opt tbl k with | None -> (pos, [ v ]) | Some (pos, vl) -> (pos, v :: vl) in Hashtbl.replace tbl k tbl_v); let clusters = Hashtbl.fold (fun k (pos, vl) acc -> (pos, (k, List.rev vl)) :: acc) tbl [] in clusters |> List.sort (fun (pos1, _) (pos2, _) -> compare pos1 pos2) |> list_map snd let use_pretty_path_separator path = path |> String.split_on_char path_sep |> String.concat pretty_path_sep_str let to_alcotest ?(speed_level = `Quick) tests : unit Alcotest.test list = tests |> list_map (fun (path, func) -> let category, name = split_path path in let category = match category with | "" -> name | s -> s in let pretty_category = use_pretty_path_separator category in (pretty_category, (name, speed_level, func))) |> group_by_key let make_pcre_filter pat = let re = try Re.Pcre.re pat |> Re.compile with | e -> failwith (Printf.sprintf "Cannot parse PCRE pattern '%s': %s" pat (Printexc.to_string e)) in fun s -> Re.matches re s <> [] let filter ?substring ?pcre tests = let has_substring = match substring with | None -> fun _ -> true | Some sub -> let re = Re.str sub |> Re.compile in fun s -> Re.matches re s <> [] in let matches_pcre = match pcre with | None -> fun _ -> true | Some pat -> make_pcre_filter pat in tests |> List.filter (fun (path, _test) -> let pretty_path = use_pretty_path_separator path in (has_substring path || has_substring pretty_path) && (matches_pcre path || matches_pcre pretty_path)) let run what f = printf "running %s...\n%!" what; Fun.protect ~finally:(fun () -> printf "done with %s.\n%!" what) f