Skip to content

Commit 876ecb2

Browse files
committed
Refactor configuration processing
1 parent e3cfed9 commit 876ecb2

11 files changed

+376
-318
lines changed

alcotest.opam

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ depends: [
2828
"re"
2929
"stdlib-shims"
3030
"uutf"
31+
"ocaml-syntax-shims"
3132
]
3233
build: [
3334
["dune" "subst"] {pinned}

dune-project

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ tests to run.
3131
uuidm
3232
re
3333
stdlib-shims
34-
uutf))
34+
uutf
35+
ocaml-syntax-shims))
3536

3637
(package
3738
(name alcotest-async)

src/alcotest-engine/cli.ml

+46-269
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
*)
1717

18+
open Utils
1819
open Cmdliner
1920
open Astring
2021

@@ -58,24 +59,11 @@ struct
5859
module C = Core.Make (P) (M)
5960
include C
6061
module P = P (M)
61-
62-
let set_color color_flag =
63-
let style_renderer =
64-
match color_flag with
65-
| Some `Auto -> None
66-
| Some (`Ansi_tty | `None) as a -> a
67-
| None -> (
68-
try
69-
(* Default to [always] when running inside Dune *)
70-
let (_ : string) = Sys.getenv "INSIDE_DUNE" in
71-
Some `Ansi_tty
72-
with Not_found -> None)
73-
in
74-
P.setup_std_outputs ?style_renderer ()
62+
open Cmdliner_syntax
7563

7664
let set_color =
7765
let env = Arg.env_var "ALCOTEST_COLOR" in
78-
let style_renderer =
66+
let+ color_flag =
7967
let enum = [ ("auto", `Auto); ("always", `Ansi_tty); ("never", `None) ] in
8068
let color = Arg.enum enum in
8169
let enum_alts = Arg.doc_alts_enum enum in
@@ -85,278 +73,63 @@ struct
8573
running inside Dune, otherwise defaults to %s."
8674
enum_alts (Arg.doc_quote "always") (Arg.doc_quote "auto")
8775
in
88-
8976
Arg.(
9077
value & opt (some color) None & info [ "color" ] ~env ~doc ~docv:"WHEN")
9178
in
92-
Term.(const set_color $ style_renderer)
93-
94-
type runtime_options = {
95-
verbose : bool option;
96-
compact : bool option;
97-
tail_errors : [ `Unlimited | `Limit of int ] option;
98-
show_errors : bool option;
99-
quick_only : bool option;
100-
json : bool option;
101-
log_dir : string option;
102-
bail : bool option;
103-
}
104-
105-
(* Merge two ['a option]s with a left [Some] taking priority *)
106-
let ( ||* ) a b = match (a, b) with Some a, _ -> Some a | None, b -> b
107-
108-
let v_runtime_flags ~defaults (`Verbose verbose) (`Compact compact)
109-
(`Tail_errors tail_errors) (`Show_errors show_errors)
110-
(`Quick_only quick_only) (`Json json) (`Log_dir log_dir) (`Bail bail) =
111-
let verbose = verbose ||* defaults.verbose in
112-
let compact = compact ||* defaults.compact in
113-
let show_errors = show_errors ||* defaults.show_errors in
114-
let quick_only = quick_only ||* defaults.quick_only in
115-
let json = json ||* defaults.json in
116-
let log_dir = Some log_dir in
117-
let tail_errors = tail_errors ||* defaults.tail_errors in
118-
let bail = bail ||* defaults.bail in
119-
{
120-
verbose;
121-
compact;
122-
tail_errors;
123-
show_errors;
124-
quick_only;
125-
json;
126-
log_dir;
127-
bail;
128-
}
129-
130-
let run_test ?and_exit
131-
{
132-
verbose;
133-
compact;
134-
tail_errors;
135-
show_errors;
136-
quick_only;
137-
json;
138-
log_dir;
139-
bail;
140-
} (`Test_filter filter) () tests name args =
141-
run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only
142-
?show_errors ?json ?filter ?log_dir ?bail name tests args
143-
144-
let fmap f x = Term.(app (const f) x)
145-
146-
(* If a Cmdliner flag is _not_ set, we interpret it as 'use the program
147-
default' rather than an explicit 'disable'. This changes the type of
148-
{!Cmdliner.Arg.flag} to reflect that fact. *)
149-
let to_tristate = fmap (function true -> Some true | false -> None)
150-
151-
let json =
152-
let doc = "Display JSON for the results, to be used by a script." in
153-
Arg.(value & flag & info [ "json" ] ~docv:"" ~doc)
154-
|> to_tristate
155-
|> fmap (fun x -> `Json x)
156-
157-
let log_dir =
158-
let fname_concat l = List.fold_left Filename.concat "" l in
159-
let default_dir = fname_concat [ P.getcwd (); "_build"; "_tests" ] in
160-
let doc = "Where to store the log files of the tests." in
161-
Arg.(value & opt dir default_dir & info [ "o" ] ~docv:"DIR" ~doc)
162-
|> fmap (fun x -> `Log_dir x)
163-
164-
let verbose =
165-
let env = Arg.env_var "ALCOTEST_VERBOSE" in
166-
let doc =
167-
"Display the test outputs. $(b,WARNING:) when using this option the \
168-
output logs will not be available for further inspection."
169-
in
170-
Arg.(value & flag & info ~env [ "v"; "verbose" ] ~docv:"" ~doc)
171-
|> to_tristate
172-
|> fmap (fun x -> `Verbose x)
173-
174-
let compact =
175-
let env = Arg.env_var "ALCOTEST_COMPACT" in
176-
let doc = "Compact the output of the tests." in
177-
Arg.(value & flag & info ~env [ "c"; "compact" ] ~docv:"" ~doc)
178-
|> to_tristate
179-
|> fmap (fun x -> `Compact x)
180-
181-
let bail =
182-
let env = Arg.env_var "ALCOTEST_BAIL" in
183-
let doc = "Stop running tests after the first failure." in
184-
Arg.(value & flag & info ~env [ "bail" ] ~docv:"" ~doc)
185-
|> to_tristate
186-
|> fmap (fun x -> `Bail x)
187-
188-
let limit_parser s =
189-
match s with
190-
| "unlimited" -> Ok `Unlimited
191-
| s -> (
192-
try
193-
let n = int_of_string s in
194-
if n < 0 then
195-
Error (`Msg "numeric limit must be nonnegative or 'unlimited'")
196-
else Ok (`Limit n)
197-
with Failure _ -> Error (`Msg "invalid numeric limit"))
198-
199-
let limit_printer ppf limit =
200-
match limit with
201-
| `Unlimited -> Fmt.pf ppf "unlimited"
202-
| `Limit n -> Fmt.pf ppf "%i" n
203-
204-
(* Parse/print a nonnegative number of lines or "unlimited". *)
205-
let limit = Cmdliner.Arg.conv (limit_parser, limit_printer)
206-
207-
let tail_errors =
208-
let env = Arg.env_var "ALCOTEST_TAIL_ERRORS" in
209-
let doc =
210-
"Show only the last $(docv) lines of output in case of an error."
211-
in
212-
Arg.(
213-
value & opt (some limit) None & info ~env [ "tail-errors" ] ~docv:"N" ~doc)
214-
|> fmap (fun x -> `Tail_errors x)
215-
216-
let show_errors =
217-
let env = Arg.env_var "ALCOTEST_SHOW_ERRORS" in
218-
let doc = "Display the test errors." in
219-
Arg.(value & flag & info ~env [ "e"; "show-errors" ] ~docv:"" ~doc)
220-
|> to_tristate
221-
|> fmap (fun x -> `Show_errors x)
222-
223-
let quick_only =
224-
let env = Arg.env_var "ALCOTEST_QUICK_TESTS" in
225-
let doc = "Run only the quick tests." in
226-
Arg.(value & flag & info ~env [ "q"; "quick-tests" ] ~docv:"" ~doc)
227-
|> to_tristate
228-
|> fmap (fun x -> `Quick_only x)
229-
230-
let flags_with_defaults defaults =
231-
Term.(
232-
pure (v_runtime_flags ~defaults)
233-
$ verbose
234-
$ compact
235-
$ tail_errors
236-
$ show_errors
237-
$ quick_only
238-
$ json
239-
$ log_dir
240-
$ bail)
241-
242-
let regex =
243-
let parse s =
244-
try Ok Re.(compile @@ Pcre.re s) with
245-
| Re.Perl.Parse_error -> Error (`Msg "Perl-compatible regexp parse error")
246-
| Re.Perl.Not_supported -> Error (`Msg "unsupported regexp feature")
247-
in
248-
let print = Re.pp_re in
249-
Arg.conv (parse, print)
250-
251-
exception Invalid_format
252-
253-
let int_range_list : int list Cmdliner.Arg.conv =
254-
let parse s =
255-
let rec range lower upper acc =
256-
if lower > upper then acc else range (succ lower) upper (lower :: acc)
257-
in
258-
let process_range acc s =
259-
String.cuts ~sep:".." s |> List.map String.to_int |> function
260-
| [ Some i ] -> i :: acc
261-
| [ Some lower; Some upper ] when lower <= upper ->
262-
range lower upper acc
263-
| _ -> raise Invalid_format
264-
in
265-
let ranges = String.cuts ~sep:"," s in
266-
match List.fold_left process_range [] ranges with
267-
| list -> Ok list
268-
| exception Invalid_format ->
269-
Error
270-
(`Msg "must be a comma-separated list of integers / integer ranges")
271-
in
272-
let print ppf set = Fmt.(braces @@ list ~sep:comma int) ppf set in
273-
Arg.conv (parse, print)
274-
275-
let test_filter =
276-
let name_regex =
277-
let doc = "A regular expression matching the names of tests to run" in
278-
Arg.(value & pos 0 (some regex) None & info [] ~doc ~docv:"NAME_REGEX")
279-
in
280-
let number_filter =
281-
let doc =
282-
"A comma-separated list of test case numbers (and ranges of numbers) \
283-
to run, e.g: '4,6-10,19'"
284-
in
285-
Arg.(
286-
value
287-
& pos 1 (some int_range_list) None
288-
& info [] ~doc ~docv:"TESTCASES")
79+
let style_renderer =
80+
match color_flag with
81+
| Some `Auto -> None
82+
| Some (`Ansi_tty | `None) as a -> a
83+
| None -> (
84+
try
85+
(* Default to [always] when running inside Dune *)
86+
let (_ : string) = Sys.getenv "INSIDE_DUNE" in
87+
Some `Ansi_tty
88+
with Not_found -> None)
28989
in
290-
Term.(
291-
pure (fun n t -> `Test_filter (Some (n, t))) $ name_regex $ number_filter)
90+
P.setup_std_outputs ?style_renderer ()
29291

293-
let default_cmd ?and_exit runtime_flags args library_name tests =
92+
let default_cmd ~and_exit config args library_name tests =
29493
let exec_name = Filename.basename Sys.argv.(0) in
29594
let doc = "Run all the tests." in
296-
let flags = flags_with_defaults runtime_flags in
297-
( Term.(
298-
pure (run_test ?and_exit)
299-
$ flags
300-
$ pure (`Test_filter None)
301-
$ set_color
302-
$ args
303-
$ pure library_name
304-
$ pure tests),
305-
Term.info exec_name ~doc )
95+
let term =
96+
let+ () = set_color
97+
and+ cli_config = Config.User.term ~and_exit
98+
and+ args = args in
99+
let config = Config.User.(cli_config || config) in
100+
run_with_args' config library_name args tests
101+
in
102+
(term, Term.info exec_name ~doc)
306103

307-
let test_cmd ?and_exit runtime_flags ~filter args library_name tests =
104+
let test_cmd config args library_name tests =
308105
let doc = "Run a subset of the tests." in
309-
let flags = flags_with_defaults runtime_flags in
310-
let filter =
311-
Term.(
312-
pure (fun a -> match a with `Test_filter None -> filter | _ -> a)
313-
$ test_filter)
106+
let term =
107+
let+ () = set_color
108+
and+ cli_config = Config.User.term ~and_exit:true
109+
and+ args = args in
110+
let config = Config.User.(cli_config || config) in
111+
run_with_args' config library_name args tests
314112
in
315-
( Term.(
316-
pure (run_test ?and_exit)
317-
$ flags
318-
$ filter
319-
$ set_color
320-
$ args
321-
$ pure library_name
322-
$ pure tests),
323-
Term.info "test" ~doc )
113+
(term, Term.info "test" ~doc)
324114

325115
let list_cmd tests =
326116
let doc = "List all available tests." in
327-
( Term.(pure (fun () -> list_tests) $ set_color $ pure tests),
117+
( (let+ () = set_color in
118+
list_tests tests),
328119
Term.info "list" ~doc )
329120

330-
let run_with_args ?(and_exit = true) ?verbose ?compact ?tail_errors
331-
?quick_only ?show_errors ?json ?filter ?log_dir ?bail ?argv name
332-
(args : 'a Term.t) (tl : 'a test list) =
121+
let run_with_args' (type a) ~argv config name (args : a Term.t)
122+
(tl : a test list) =
123+
let and_exit = Config.User.and_exit config in
333124
let ( >>= ) = M.bind in
334-
let runtime_flags =
335-
{
336-
verbose;
337-
compact;
338-
tail_errors;
339-
show_errors;
340-
quick_only;
341-
json;
342-
log_dir;
343-
bail;
344-
}
345-
in
346-
let choices =
347-
[
348-
list_cmd tl;
349-
test_cmd ~and_exit runtime_flags ~filter:(`Test_filter filter) args name
350-
tl;
351-
]
352-
in
125+
let choices = [ list_cmd tl; test_cmd config args name tl ] in
353126
let exit_or_return result =
354127
if and_exit then exit (Term.exit_status_of_result result) else M.return ()
355128
in
356129
let result =
357130
Term.eval_choice ?argv
358131
~catch:and_exit (* Only log exceptions not raised to the user code *)
359-
(default_cmd ~and_exit runtime_flags args name tl)
132+
(default_cmd ~and_exit config args name tl)
360133
choices
361134
in
362135
match result with
@@ -365,8 +138,12 @@ struct
365138
| `Error (`Parse | `Term) as result ->
366139
exit (Term.exit_status_of_result result)
367140

368-
let run ?and_exit ?verbose ?compact ?tail_errors ?quick_only ?show_errors
369-
?json ?filter ?log_dir ?bail ?argv name tl =
370-
run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only
371-
?show_errors ?json ?filter ?log_dir ?bail ?argv name (Term.pure ()) tl
141+
let run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only
142+
?show_errors ?json ?filter ?log_dir ?bail ?argv =
143+
Config.User.kcreate (run_with_args' ~argv) ?and_exit ?verbose ?compact
144+
?tail_errors ?quick_only ?show_errors ?json ?filter ?log_dir ?bail
145+
146+
let run =
147+
Config.User.kcreate (fun config ?argv name tl ->
148+
run_with_args' config ~argv name (Term.pure ()) tl)
372149
end

0 commit comments

Comments
 (0)