2022-01-10 19:57:11 +03:00
|
|
|
open Cmdliner
|
|
|
|
open Utils
|
|
|
|
|
|
|
|
(**{1 Command line interface}*)
|
|
|
|
|
|
|
|
let file_or_folder =
|
|
|
|
Arg.(required & pos 1 (some file) None & info [] ~docv:"FILE(S)" ~doc:"File or folder to process")
|
|
|
|
|
|
|
|
let command =
|
|
|
|
Arg.(
|
|
|
|
required
|
|
|
|
& pos 0 (some string) None
|
|
|
|
& info [] ~docv:"COMMAND" ~doc:"Command selection among: Test")
|
|
|
|
|
|
|
|
let debug = Arg.(value & flag & info [ "debug"; "d" ] ~doc:"Prints debug information")
|
|
|
|
|
2022-01-10 20:35:41 +03:00
|
|
|
let reset_test_outputs = Arg.(value & flag & info [ "r"; "reset" ] ~doc:"Reset tests outputs")
|
|
|
|
|
2022-01-10 19:57:11 +03:00
|
|
|
let catalac =
|
|
|
|
Arg.(
|
|
|
|
value
|
|
|
|
& opt (some string) None
|
|
|
|
& info [ "e"; "exe" ] ~docv:"EXE" ~doc:"Catala compiler executable, defaults to \"catala\"")
|
|
|
|
|
|
|
|
let catala_opts =
|
|
|
|
Arg.(
|
|
|
|
value
|
|
|
|
& opt (some string) None
|
|
|
|
& info [ "c"; "catala-opts" ] ~docv:"LANG" ~doc:"Options to pass to the Catala compiler")
|
|
|
|
|
2022-01-10 20:35:41 +03:00
|
|
|
let clerk_t f =
|
|
|
|
Term.(const f $ file_or_folder $ command $ catalac $ catala_opts $ debug $ reset_test_outputs)
|
2022-01-10 19:57:11 +03:00
|
|
|
|
|
|
|
let version = "0.5.0"
|
|
|
|
|
|
|
|
let info =
|
|
|
|
let doc =
|
|
|
|
"Build system for Catala, a specification language for tax and social benefits computation \
|
|
|
|
rules."
|
|
|
|
in
|
|
|
|
let exits = Term.default_exits @ [ Term.exit_info ~doc:"on error." 1 ] in
|
|
|
|
Term.info "clerk" ~version ~doc ~exits
|
|
|
|
|
|
|
|
(**{1 Testing}*)
|
|
|
|
|
|
|
|
let catala_backend_to_string (backend : Cli.backend_option) : string =
|
|
|
|
match backend with
|
|
|
|
| Cli.Interpret -> "Interpret"
|
|
|
|
| Cli.Makefile -> "Makefile"
|
|
|
|
| Cli.OCaml -> "Ocaml"
|
|
|
|
| Cli.Scopelang -> "Scopelang"
|
|
|
|
| Cli.Dcalc -> "Dcalc"
|
|
|
|
| Cli.Latex -> "Latex"
|
|
|
|
| Cli.Html -> "Html"
|
|
|
|
| Cli.Python -> "Python"
|
|
|
|
|
|
|
|
type expected_output_descr = {
|
|
|
|
base_filename : string;
|
|
|
|
output_dir : string;
|
|
|
|
complete_filename : string;
|
|
|
|
backend : Cli.backend_option;
|
|
|
|
scope : string option;
|
|
|
|
}
|
|
|
|
|
|
|
|
let filename_to_expected_output_descr (output_dir : string) (filename : string) :
|
|
|
|
expected_output_descr option =
|
|
|
|
let complete_filename = filename in
|
|
|
|
let first_extension = Filename.extension filename in
|
|
|
|
let filename = Filename.remove_extension filename in
|
|
|
|
let backend =
|
|
|
|
match String.lowercase_ascii first_extension with
|
|
|
|
| ".interpret" -> Some Cli.Interpret
|
2022-01-11 13:25:41 +03:00
|
|
|
| ".d" -> Some Cli.Makefile
|
|
|
|
| ".ml" -> Some Cli.OCaml
|
2022-01-10 19:57:11 +03:00
|
|
|
| ".scopelang" -> Some Cli.Scopelang
|
|
|
|
| ".dcalc" -> Some Cli.Dcalc
|
2022-01-11 13:25:41 +03:00
|
|
|
| ".tex" -> Some Cli.Latex
|
2022-01-10 19:57:11 +03:00
|
|
|
| ".html" -> Some Cli.Html
|
2022-01-11 13:25:41 +03:00
|
|
|
| ".py" -> Some Cli.Python
|
2022-01-10 19:57:11 +03:00
|
|
|
| _ -> None
|
|
|
|
in
|
|
|
|
match backend with
|
|
|
|
| None -> None
|
|
|
|
| Some backend ->
|
|
|
|
let second_extension = Filename.extension filename in
|
|
|
|
let catala_suffix_regex = Re.Pcre.regexp "\\.catala_(\\w){2}" in
|
|
|
|
let base_filename, scope =
|
|
|
|
if Re.Pcre.pmatch ~rex:catala_suffix_regex second_extension then (filename, None)
|
|
|
|
else
|
|
|
|
let scope_name_regex = Re.Pcre.regexp "\\.(.+)" in
|
|
|
|
let scope_name = (Re.Pcre.extract ~rex:scope_name_regex second_extension).(1) in
|
|
|
|
(Filename.remove_extension filename, Some scope_name)
|
|
|
|
in
|
|
|
|
Some { output_dir; complete_filename; base_filename; backend; scope }
|
|
|
|
|
|
|
|
(** Given a file, looks in the relative [output] directory if there are files with the same base
|
|
|
|
name that contain expected outputs for different *)
|
|
|
|
let search_for_expected_outputs (file : string) : expected_output_descr list =
|
|
|
|
let output_dir = Filename.dirname file ^ Filename.dir_sep ^ "output/" in
|
2022-01-11 12:42:12 +03:00
|
|
|
let output_files = try Sys.readdir output_dir with Sys_error _ -> Array.make 0 "" in
|
2022-01-10 19:57:11 +03:00
|
|
|
List.filter_map
|
|
|
|
(fun output_file ->
|
|
|
|
match filename_to_expected_output_descr output_dir output_file with
|
|
|
|
| None -> None
|
|
|
|
| Some expected_output ->
|
|
|
|
if expected_output.base_filename = Filename.basename file then Some expected_output
|
|
|
|
else None)
|
|
|
|
(Array.to_list output_files)
|
|
|
|
|
2022-01-10 20:35:41 +03:00
|
|
|
let test_file (tested_file : string) (catala_exe : string option) (catala_opts : string option)
|
|
|
|
(reset_test_outputs : bool) : int =
|
2022-01-10 19:57:11 +03:00
|
|
|
let catala_exe = Option.fold ~none:"catala" ~some:Fun.id catala_exe in
|
|
|
|
let catala_opts = Option.fold ~none:"" ~some:Fun.id catala_opts in
|
|
|
|
let expected_outputs = search_for_expected_outputs tested_file in
|
|
|
|
if List.length expected_outputs = 0 then (
|
|
|
|
Cli.error_print (Format.asprintf "No expected outputs were found for test file %s" tested_file);
|
|
|
|
0)
|
|
|
|
else
|
|
|
|
List.fold_left
|
|
|
|
(fun exit expected_output ->
|
|
|
|
let catala_backend = catala_backend_to_string expected_output.backend in
|
|
|
|
let command =
|
|
|
|
String.concat " "
|
|
|
|
(List.filter
|
|
|
|
(fun s -> s <> "")
|
|
|
|
[
|
|
|
|
catala_exe;
|
|
|
|
catala_opts;
|
|
|
|
(match expected_output.scope with None -> "" | Some scope -> "-s " ^ scope);
|
|
|
|
"--unstyled";
|
|
|
|
catala_backend;
|
|
|
|
tested_file;
|
2022-01-10 20:35:41 +03:00
|
|
|
]
|
|
|
|
@
|
2022-01-11 13:25:41 +03:00
|
|
|
match expected_output.backend with
|
|
|
|
| Cli.Interpret ->
|
|
|
|
if reset_test_outputs then
|
|
|
|
[
|
|
|
|
">";
|
|
|
|
Format.asprintf "%s%s" expected_output.output_dir
|
|
|
|
expected_output.complete_filename;
|
|
|
|
"2>&1 ";
|
|
|
|
]
|
|
|
|
else
|
|
|
|
[
|
|
|
|
"2>&1 ";
|
|
|
|
"|";
|
|
|
|
Format.asprintf "colordiff -u -b %s%s -" expected_output.output_dir
|
|
|
|
expected_output.complete_filename;
|
|
|
|
]
|
|
|
|
| Cli.Python | Cli.OCaml | Cli.Dcalc | Cli.Scopelang | Cli.Latex | Cli.Html
|
|
|
|
| Cli.Makefile ->
|
|
|
|
(* for those backends, the output of the Catala compiler will be written in a
|
|
|
|
temporary file which later we're going to diff with the *)
|
|
|
|
if reset_test_outputs then
|
|
|
|
[
|
|
|
|
"-o";
|
|
|
|
Format.asprintf "%s%s" expected_output.output_dir
|
|
|
|
expected_output.complete_filename;
|
|
|
|
]
|
|
|
|
else
|
|
|
|
let temp_file =
|
|
|
|
Filename.temp_file "clerk_"
|
|
|
|
("_" ^ catala_backend_to_string expected_output.backend)
|
|
|
|
in
|
|
|
|
[
|
|
|
|
"-o";
|
|
|
|
temp_file;
|
|
|
|
";";
|
|
|
|
Format.asprintf "colordiff -u -b %s%s %s" expected_output.output_dir
|
|
|
|
expected_output.complete_filename temp_file;
|
|
|
|
])
|
2022-01-10 19:57:11 +03:00
|
|
|
in
|
|
|
|
Cli.debug_print ("Running: " ^ command);
|
|
|
|
let result = Sys.command command in
|
2022-01-10 20:35:41 +03:00
|
|
|
if result <> 0 && not reset_test_outputs then (
|
2022-01-10 19:57:11 +03:00
|
|
|
Cli.error_print
|
|
|
|
(Format.asprintf "Test failed: %s"
|
|
|
|
(Cli.print_with_style [ ANSITerminal.magenta ] "%s%s" expected_output.output_dir
|
|
|
|
expected_output.complete_filename));
|
|
|
|
1)
|
|
|
|
else (
|
|
|
|
Cli.result_print
|
2022-01-10 20:35:41 +03:00
|
|
|
(Format.asprintf "Test %s: %s"
|
|
|
|
(if reset_test_outputs then "reset" else "passed")
|
2022-01-10 19:57:11 +03:00
|
|
|
(Cli.print_with_style [ ANSITerminal.magenta ] "%s%s" expected_output.output_dir
|
|
|
|
expected_output.complete_filename));
|
|
|
|
exit))
|
|
|
|
0 expected_outputs
|
|
|
|
|
|
|
|
(** {1 Driver} *)
|
|
|
|
|
|
|
|
let driver (file_or_folder : string) (command : string) (catala_exe : string option)
|
2022-01-10 20:35:41 +03:00
|
|
|
(catala_opts : string option) (debug : bool) (reset_test_outputs : bool) : int =
|
2022-01-10 19:57:11 +03:00
|
|
|
if debug then Cli.debug_flag := true;
|
|
|
|
match String.lowercase_ascii command with
|
|
|
|
| "test" ->
|
|
|
|
if Sys.is_directory file_or_folder then (
|
|
|
|
Cli.error_print "Testing directories is not supported at this time";
|
|
|
|
0)
|
2022-01-10 20:35:41 +03:00
|
|
|
else test_file file_or_folder catala_exe catala_opts reset_test_outputs
|
2022-01-10 19:57:11 +03:00
|
|
|
| _ ->
|
|
|
|
Cli.error_print (Format.asprintf "The command \"%s\" is unknown to clerk." command);
|
|
|
|
1
|
|
|
|
|
|
|
|
let _ =
|
|
|
|
let return_code = Cmdliner.Term.eval (clerk_t driver, info) in
|
|
|
|
match return_code with
|
|
|
|
| `Ok 0 -> Cmdliner.Term.exit (`Ok 0)
|
|
|
|
| _ -> Cmdliner.Term.exit (`Error `Term)
|