package alcotest

  1. Overview
  2. Docs

Source file model.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
open Utils

type speed_level = [ `Quick | `Slow ]

(** Given a UTF-8 encoded string, escape any characters not considered
    "filesystem safe" as their [U+XXXX] notation form. *)
let escape str =
  let add_codepoint buf uchar =
    Uchar.to_int uchar |> Fmt.str "U+%04X" |> Buffer.add_string buf
  in
  let buf = Buffer.create (String.length str * 2) in
  let get_normalized_char _ _ u =
    match u with
    | `Uchar u ->
        if Uchar.is_char u then
          match Uchar.to_char u with
          | ('A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '-' | ' ' | '.') as c
            ->
              Buffer.add_char buf c
          | _ -> add_codepoint buf u
        else add_codepoint buf u
    | `Malformed _ -> Uutf.Buffer.add_utf_8 buf Uutf.u_rep
  in
  Uutf.String.fold_utf_8 get_normalized_char () str;
  Buffer.contents buf

module Test_name : sig
  type t

  val v : name:string -> index:int -> t
  val name : t -> string
  val index : t -> int

  val pp : t Fmt.t
  (** Pretty-print the unescaped test-case name *)

  val file : t -> string
  (** An escaped form of the test name with [.output] suffix. *)

  val length : t -> int
  (** The approximate number of terminal columns consumed by [pp_name]. *)

  val compare : t -> t -> int
  (** Order lexicographically by name, then by index. *)
end = struct
  type t = { name : string; file : string; index : int }

  let index { index; _ } = index

  let v ~name ~index =
    let file =
      let name = name |> escape |> function "" -> "" | n -> n ^ "." in
      Fmt.str "%s%03d.output" name index
    in
    { name; file; index }

  let pp = Fmt.using (fun { name; _ } -> name) Fmt.string
  let name { name; _ } = name
  let file { file; _ } = file
  let length = name >> Uutf.String.fold_utf_8 (fun a _ _ -> a + 1) 0

  let compare t t' =
    match String.compare t.name t'.name with
    | 0 -> (compare : int -> int -> int) t.index t'.index
    | n -> n
end

module Run_result = struct
  type t =
    [ `Ok
    | `Exn of Test_name.t * string * unit Fmt.t
    | `Error of Test_name.t * unit Fmt.t
    | `Skip
    | `Todo of string ]

  (** [is_failure] holds for test results that are error states. *)
  let is_failure : t -> bool = function
    | `Ok | `Skip -> false
    | `Error _ | `Exn _ | `Todo _ -> true
end

module Suite (M : Monad.S) : sig
  type 'a t

  type 'a test_case = {
    name : Test_name.t;
    speed_level : speed_level;
    fn : 'a -> Run_result.t M.t;
  }

  val v : name:string -> (_ t, [> `Empty_name ]) result
  (** Construct a new suite, given a non-empty [name]. Test cases must be added
      with {!add}. *)

  val name : _ t -> string
  (** An escaped form of the suite name. *)

  val pp_name : _ t Fmt.t
  (** Pretty-print the unescaped suite name. *)

  val add :
    'a t ->
    Test_name.t * string * speed_level * ('a -> Run_result.t M.t) ->
    ('a t, [ `Duplicate_test_path of string ]) result

  val tests : 'a t -> 'a test_case list
  val doc_of_test_name : 'a t -> Test_name.t -> string
end = struct
  module String_set = Set.Make (String)

  type 'a test_case = {
    name : Test_name.t;
    speed_level : speed_level;
    fn : 'a -> Run_result.t M.t;
  }

  type 'a t = {
    escaped_name : string;
    pp_name : unit Fmt.t;
    tests : 'a test_case list;
    (* caches computed from the library values. *)
    filepaths : String_set.t;
    doc : (Test_name.t, string) Hashtbl.t;
  }

  let v ~name =
    match String.length name with
    | 0 -> Error `Empty_name
    | _ ->
        let escaped_name = escape name in
        let pp_name = Fmt.(const string) name in
        let tests = [] in
        let filepaths = String_set.empty in
        let doc = Hashtbl.create 0 in
        Ok { escaped_name; pp_name; tests; filepaths; doc }

  let name { escaped_name; _ } = escaped_name
  let pp_name ppf { pp_name; _ } = pp_name ppf ()

  let check_path_is_unique t tname =
    match String_set.mem (Test_name.file tname) t.filepaths with
    | false -> Ok ()
    | true -> Error (`Duplicate_test_path (Fmt.to_to_string Test_name.pp tname))

  let add t (tname, doc, speed_level, fn) =
    match check_path_is_unique t tname with
    | Error _ as e -> e
    | Ok () ->
        let tests = { name = tname; speed_level; fn } :: t.tests in
        let filepaths = String_set.add (Test_name.file tname) t.filepaths in
        Hashtbl.add t.doc tname doc;
        Ok { t with tests; filepaths }

  let tests t = List.rev t.tests

  let doc_of_test_name t path =
    try Hashtbl.find t.doc path with Not_found -> ""
end
OCaml

Innovation. Community. Security.