From 6d8bd626435b43f2c93924bbf40fbfec3cde9f46 Mon Sep 17 00:00:00 2001 From: Craig Ferguson Date: Mon, 18 Jan 2021 12:54:55 +0100 Subject: [PATCH] Initial implementation of nestable test API --- alcotest.opam | 2 +- dune-project | 2 +- src/alcotest-async/alcotest_async.ml | 13 + src/alcotest-async/alcotest_async_intf.ml | 18 + src/alcotest-async/dune | 1 + src/alcotest-engine/alcotest_engine.ml | 12 + src/alcotest-engine/alcotest_engine.mli | 14 + src/alcotest-engine/cli.ml | 236 +++++--- src/alcotest-engine/cli_intf.ml | 7 +- src/alcotest-engine/config.ml | 196 +++--- src/alcotest-engine/config_intf.ml | 13 +- src/alcotest-engine/core.ml | 568 +++++++++--------- src/alcotest-engine/core_intf.ml | 120 +++- src/alcotest-engine/filter.ml | 17 + src/alcotest-engine/filter.mli | 15 + src/alcotest-engine/log_trap.ml | 62 +- src/alcotest-engine/log_trap_intf.ml | 20 +- src/alcotest-engine/model.ml | 310 ++++++---- src/alcotest-engine/model.mli | 97 +++ src/alcotest-engine/monad.ml | 2 + src/alcotest-engine/monad_intf.ml | 8 +- src/alcotest-engine/platform.ml | 6 +- src/alcotest-engine/pp.ml | 255 ++++---- src/alcotest-engine/pp_intf.ml | 61 +- src/alcotest-engine/safe_string.ml | 2 +- src/alcotest-engine/safe_string.mli | 6 +- src/alcotest-engine/tag.ml | 111 ++++ src/alcotest-engine/tag.mli | 52 ++ src/alcotest-engine/test.mli | 16 +- src/alcotest-lwt/alcotest_lwt.ml | 12 + src/alcotest-lwt/alcotest_lwt_intf.ml | 14 + src/alcotest-lwt/dune | 3 +- src/alcotest-mirage/alcotest_mirage.ml | 76 ++- src/alcotest-mirage/alcotest_mirage.mli | 17 + src/alcotest-mirage/dune | 9 +- .../alcotest_stdlib_ext.ml | 67 ++- .../alcotest_stdlib_ext.mli | 39 +- src/alcotest/alcotest.ml | 37 +- src/alcotest/alcotest.mli | 129 +++- src/alcotest/dune | 2 +- test/e2e/alcotest-lwt/dune | 2 +- .../failing/async_failure.expected | 8 +- .../alcotest-lwt/failing/fail_with.expected | 8 +- test/e2e/alcotest/failing/bail-js.expected | 10 +- test/e2e/alcotest/failing/bail.expected | 10 +- .../alcotest/failing/check_basic-js.expected | 56 +- .../e2e/alcotest/failing/check_basic.expected | 56 +- .../failing/check_located-js.expected | 24 +- .../alcotest/failing/check_located.expected | 24 +- .../alcotest/failing/check_long-js.expected | 12 +- test/e2e/alcotest/failing/check_long.expected | 12 +- test/e2e/alcotest/failing/compact-js.expected | 4 +- test/e2e/alcotest/failing/compact.expected | 4 +- .../failing/empty_suite_name-js.expected | 2 +- .../failing/empty_suite_name.expected | 2 +- .../failing/exception_in_test-js.expected | 10 +- .../failing/exception_in_test.expected | 10 +- .../failing/filter_all_tests-js.expected | 3 - .../failing/filter_all_tests.expected | 3 - .../failing/invalid_arg_in_test-js.expected | 10 +- .../failing/invalid_arg_in_test.expected | 10 +- .../failing/long_test_case_name-js.expected | 12 +- .../failing/long_test_case_name.expected | 12 +- .../failing/tail_errors_limit-js.expected | 6 +- .../failing/tail_errors_limit.expected | 6 +- .../failing/tail_errors_unlimited-js.expected | 6 +- .../failing/tail_errors_unlimited.expected | 6 +- .../inside-dune/color-overridden.expected | 6 +- .../passing/and_exit_false-js.expected | 3 +- .../alcotest/passing/and_exit_false.expected | 3 +- .../passing/and_exit_true-js.expected | 2 +- .../alcotest/passing/and_exit_true.expected | 2 +- .../passing/assert_and_verbose-js.expected | 8 +- .../passing/assert_and_verbose.expected | 8 +- .../passing/assert_not_printed-js.expected | 6 +- .../passing/assert_not_printed.expected | 6 +- test/e2e/alcotest/passing/basic-js.expected | 8 +- test/e2e/alcotest/passing/basic.expected | 8 +- .../alcotest/passing/check_basic-js.expected | 48 +- .../e2e/alcotest/passing/check_basic.expected | 48 +- .../alcotest/passing/cli_verbose-js.expected | 2 +- .../e2e/alcotest/passing/cli_verbose.expected | 2 +- .../passing/empty_test_name-js.expected | 2 +- .../alcotest/passing/empty_test_name.expected | 2 +- .../alcotest/passing/filter_name-js.expected | 6 +- .../e2e/alcotest/passing/filter_name.expected | 6 +- .../passing/filter_name_regex-js.expected | 10 +- .../passing/filter_name_regex.expected | 10 +- .../alcotest/passing/filter_range-js.expected | 12 +- .../alcotest/passing/filter_range.expected | 12 +- test/e2e/alcotest/passing/isatty-js.expected | 8 +- test/e2e/alcotest/passing/isatty.expected | 8 +- .../alcotest/passing/list_tests-js.expected | 11 +- test/e2e/alcotest/passing/list_tests.expected | 11 +- .../alcotest/passing/quick_only-js.expected | 8 +- test/e2e/alcotest/passing/quick_only.expected | 8 +- .../passing/quick_only_regex-js.expected | 8 +- .../passing/quick_only_regex.expected | 8 +- .../passing/separator_testname-js.expected | 2 +- .../passing/separator_testname.expected | 2 +- .../passing/unicode_testname-js.expected | 4 +- .../passing/unicode_testname.expected | 4 +- .../passing/verbose_newlines-js.expected | 8 +- .../passing/verbose_newlines.expected | 8 +- 104 files changed, 2138 insertions(+), 1155 deletions(-) create mode 100644 src/alcotest-engine/filter.ml create mode 100644 src/alcotest-engine/filter.mli create mode 100644 src/alcotest-engine/model.mli create mode 100644 src/alcotest-engine/tag.ml create mode 100644 src/alcotest-engine/tag.mli diff --git a/alcotest.opam b/alcotest.opam index 27c67819..ac2aaf77 100644 --- a/alcotest.opam +++ b/alcotest.opam @@ -20,7 +20,7 @@ doc: "https://mirage.github.io/alcotest" bug-reports: "https://github.com/mirage/alcotest/issues" depends: [ "dune" {>= "2.8"} - "ocaml" {>= "4.05.0"} + "ocaml" {>= "4.06.0"} "fmt" {>= "0.8.7"} "astring" "cmdliner" {>= "1.0.0" & < "1.1.0"} diff --git a/dune-project b/dune-project index cce7c93b..4ba1c7e4 100644 --- a/dune-project +++ b/dune-project @@ -24,7 +24,7 @@ inspect), with a simple (yet expressive) query language to select the tests to run. ") (depends - (ocaml (>= 4.05.0)) + (ocaml (>= 4.06.0)) (fmt (>= 0.8.7)) astring (cmdliner (and (>= 1.0.0) (< 1.1.0))) diff --git a/src/alcotest-async/alcotest_async.ml b/src/alcotest-async/alcotest_async.ml index 00b8e3cf..b16f527d 100644 --- a/src/alcotest-async/alcotest_async.ml +++ b/src/alcotest-async/alcotest_async.ml @@ -30,3 +30,16 @@ module V1 = struct end include V1 + +module Unstable = struct + module Tester = + Alcotest_engine.Unstable.Cli.Make (Alcotest.Unix_platform) (Promise) + + include Tester + + let test_sync ?pos ?tags ~name fn = + test ?pos ?tags ~name (fun x -> Deferred.return (fn x)) + + let test ?(timeout = sec 2.) ?pos ?tags ~name fn = + test ?pos ?tags ~name (run_test timeout name fn) +end diff --git a/src/alcotest-async/alcotest_async_intf.ml b/src/alcotest-async/alcotest_async_intf.ml index 24d7382c..290af4b4 100644 --- a/src/alcotest-async/alcotest_async_intf.ml +++ b/src/alcotest-async/alcotest_async_intf.ml @@ -21,4 +21,22 @@ module type Alcotest_async = sig module V1 : V1 (** An alias of the above API that provides a stability guarantees over major version changes. *) + + module Unstable : sig + open Alcotest_engine.Unstable + + include + Core.S + with type 'a m := 'a Async_kernel.Deferred.t + and type 'a test_args := 'a + and type config := Config.User.t + and type source_code_position := Source_code_position.pos + and type tag_set := Tag.Set.t + + val test : + ?timeout:Core_kernel.Time.Span.t -> + (('a -> unit Async_kernel.Deferred.t) -> 'a test) Core.identified + + val test_sync : (('a -> unit) -> 'a test) Core.identified + end end diff --git a/src/alcotest-async/dune b/src/alcotest-async/dune index f69d6eff..0e5f6343 100644 --- a/src/alcotest-async/dune +++ b/src/alcotest-async/dune @@ -3,6 +3,7 @@ (public_name alcotest-async) (libraries alcotest.engine + alcotest.stdlib_ext alcotest async_kernel async_unix diff --git a/src/alcotest-engine/alcotest_engine.ml b/src/alcotest-engine/alcotest_engine.ml index 014c84a9..6621f131 100644 --- a/src/alcotest-engine/alcotest_engine.ml +++ b/src/alcotest-engine/alcotest_engine.ml @@ -1,9 +1,21 @@ +open! Import + module V1 = struct module Test = Test module Core = Core.V1 module Cli = Cli.V1 end +module Unstable = struct + module Cli = Cli.Unstable + module Config = Config + module Core = Core.Unstable + module Filter = Filter + module Source_code_position = Source_code_position + module Tag = Tag + module Test = Test +end + module Monad = Monad module Platform = Platform diff --git a/src/alcotest-engine/alcotest_engine.mli b/src/alcotest-engine/alcotest_engine.mli index 7aea4a6f..cdb96959 100644 --- a/src/alcotest-engine/alcotest_engine.mli +++ b/src/alcotest-engine/alcotest_engine.mli @@ -20,6 +20,8 @@ to defined tests. The platform-specific runners for these tests are in [alcotest], [alcotest-lwt], [alcotest-async] and [alcotest-mirage]. *) +open! Import + module V1 : sig (** Version 1 of the user-facing Alcotest API. *) @@ -43,6 +45,18 @@ module V1 : sig (** Wraps {!Core} to provide a command-line interface. *) end +module Unstable : sig + module Test = Test + (** Unstable version of the user-facing Alcotest API. *) + + module Core = Core.Unstable + module Cli = Cli.Unstable + module Tag = Tag + module Filter = Filter + module Config = Config + module Source_code_position = Source_code_position +end + module Monad = Monad (** Monad signatures for use with {!V1.Core} and {!V1.Cli}. *) diff --git a/src/alcotest-engine/cli.ml b/src/alcotest-engine/cli.ml index d4b7966b..4252abee 100644 --- a/src/alcotest-engine/cli.ml +++ b/src/alcotest-engine/cli.ml @@ -18,116 +18,162 @@ include Cli_intf open! Import open Cmdliner +open Cmdliner_syntax -module Make (P : Platform.MAKER) (M : Monad.S) : - V1_types.S with type return = unit M.t = struct - (** *) - - (** The priority order for determining options should be as follows: - - + 1. if a CLI flag/option is _explicitly_ set, use that; - + 2. if the corresponding environment variable is _explicitly_ set, use - that; - + 3. if the flag/option is set by [run ?argv] - + 4. if the flag/option is passed to [run] directly, use that; - + 5. otherwise, use the default behaviour set by {!Alcotest.Core}. *) +let set_color (module Platform : Platform.S) = + let env = Arg.env_var "ALCOTEST_COLOR" in + let+ color_flag = + let enum = [ ("auto", `Auto); ("always", `Ansi_tty); ("never", `None) ] in + let color = Arg.enum enum in + let enum_alts = Arg.doc_alts_enum enum in + let doc = + Fmt.str + "Colorize the output. $(docv) must be %s. Defaults to %s when running \ + inside Dune, otherwise defaults to %s." + enum_alts (Arg.doc_quote "always") (Arg.doc_quote "auto") + in + Arg.( + value & opt (some color) None & info [ "color" ] ~env ~doc ~docv:"WHEN") + in + let style_renderer = + match color_flag with + | Some `Auto -> None + | Some (`Ansi_tty | `None) as a -> a + | None -> ( + try + (* Default to [always] when running inside Dune *) + let (_ : string) = Sys.getenv "INSIDE_DUNE" in + Some `Ansi_tty + with Not_found -> None) + in + Platform.setup_std_outputs ?style_renderer () - module C = Core.V1.Make (P) (M) - include C +module Make (P : Platform.MAKER) (M : Monad.S) = struct + module C = Core.Unstable.Make (P) (M) module P = P (M) + include C open Cmdliner_syntax - let set_color = - let env = Arg.env_var "ALCOTEST_COLOR" in - let+ color_flag = - let enum = [ ("auto", `Auto); ("always", `Ansi_tty); ("never", `None) ] in - let color = Arg.enum enum in - let enum_alts = Arg.doc_alts_enum enum in - let doc = - Fmt.str - "Colorize the output. $(docv) must be %s. Defaults to %s when \ - running inside Dune, otherwise defaults to %s." - enum_alts (Arg.doc_quote "always") (Arg.doc_quote "auto") + let run ?pos ?(config = Config.User.create ()) ?name ?__FILE__:file suite = + let test_command = + let term = + let+ () = set_color (module P) + and+ cli_config = + Config.User.term ~and_exit:false ~record_backtrace:true + in + let config = Config.User.(cli_config || config) in + run ?pos ~config ?name ?__FILE__:file suite in - Arg.( - value & opt (some color) None & info [ "color" ] ~env ~doc ~docv:"WHEN") - in - let style_renderer = - match color_flag with - | Some `Auto -> None - | Some (`Ansi_tty | `None) as a -> a - | None -> ( - try - (* Default to [always] when running inside Dune *) - let (_ : string) = Sys.getenv "INSIDE_DUNE" in - Some `Ansi_tty - with Not_found -> None) - in - P.setup_std_outputs ?style_renderer () - - let default_cmd config args library_name tests = - let and_exit = Config.User.and_exit config - and record_backtrace = Config.User.record_backtrace config in - let exec_name = Filename.basename Sys.argv.(0) in - let doc = "Run all the tests." in - let term = - let+ () = set_color - and+ cli_config = Config.User.term ~and_exit ~record_backtrace - and+ args = args in - let config = Config.User.(cli_config || config) in - run_with_args' config library_name args tests + (term, Term.info "test") in - (term, Term.info exec_name ~doc) - - let test_cmd config args library_name tests = - let doc = "Run a subset of the tests." in - let term = - let+ () = set_color - and+ cli_config = Config.User.term ~and_exit:true ~record_backtrace:true - and+ args = args in - let config = Config.User.(cli_config || config) in - run_with_args' config library_name args tests - in - (term, Term.info "test" ~doc) - - let list_cmd tests = - let doc = "List all available tests." in - ( (let+ () = set_color in - list_tests tests), - Term.info "list" ~doc ) - - let run_with_args' (type a) ~argv config name (args : a Term.t) - (tl : a test list) = - let ( >>= ) = M.bind in - let choices = [ list_cmd tl; test_cmd config args name tl ] in - let and_exit = Config.User.and_exit config in - let exit_or_return result = - if and_exit then exit (Term.exit_status_of_result result) else M.return () - in - let result = - Term.eval_choice ?argv - ~catch:and_exit (* Only log exceptions not raised to the user code *) - (default_cmd config args name tl) - choices + let list_command = + let term = + let+ () = set_color (module P) in + list_tests ~name suite + in + (term, Term.info "list") in - match result with - | `Ok unit_m -> unit_m >>= fun () -> exit_or_return (`Ok ()) + let exit_or_return result = exit (Term.exit_status_of_result result) in + match Term.eval_choice test_command [ test_command; list_command ] with + | `Ok f -> M.bind f (fun () -> exit_or_return (`Ok ())) | (`Help | `Version | `Error `Exn) as result -> exit_or_return result | `Error (`Parse | `Term) as result -> exit (Term.exit_status_of_result result) +end - let run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only - ?show_errors ?json ?filter ?log_dir ?bail ?record_backtrace ?argv = - Config.User.kcreate (run_with_args' ~argv) ?and_exit ?verbose ?compact - ?tail_errors ?quick_only ?show_errors ?json ?filter ?log_dir ?bail - ?record_backtrace +module Make_v1 : V1_types.MAKER = +functor + (P : Platform.MAKER) + (M : Monad.S) + -> + struct + (** *) - let run = - Config.User.kcreate (fun config ?argv name tl -> - run_with_args' config ~argv name (Term.pure ()) tl) -end + (** The priority order for determining options should be as follows: + + + 1. if a CLI flag/option is _explicitly_ set, use that; + + 2. if the corresponding environment variable is _explicitly_ set, use + that; + + 3. if the flag/option is set by [run ?argv] + + 4. if the flag/option is passed to [run] directly, use that; + + 5. otherwise, use the default behaviour set by {!Alcotest.Core}. *) + + module C = Core.V1.Make (P) (M) + include C + module P = P (M) + open Cmdliner_syntax + + let default_cmd config args library_name tests = + let and_exit = Config.User.and_exit config + and record_backtrace = Config.User.record_backtrace config in + let exec_name = Filename.basename Sys.argv.(0) in + let doc = "Run all the tests." in + let term = + let+ () = set_color (module P) + and+ cli_config = Config.User.term ~and_exit ~record_backtrace + and+ args = args in + let config = Config.User.(cli_config || config) in + run_with_args' config library_name args tests + in + (term, Term.info exec_name ~doc) + + let test_cmd config args library_name tests = + let doc = "Run a subset of the tests." in + let term = + let+ () = set_color (module P) + and+ cli_config = Config.User.term ~and_exit:true ~record_backtrace:true + and+ args = args in + let config = Config.User.(cli_config || config) in + run_with_args' config library_name args tests + in + (term, Term.info "test" ~doc) + + let list_cmd suite_name tests = + let doc = "List all available tests." in + ( (let+ () = set_color (module P) in + list_tests suite_name tests), + Term.info "list" ~doc ) + + let run_with_args' (type a) ~argv config name (args : a Term.t) + (tl : a test list) = + let ( >>= ) = M.bind in + let choices = [ list_cmd name tl; test_cmd config args name tl ] in + let and_exit = Config.User.and_exit config in + let exit_or_return result = + if and_exit then exit (Term.exit_status_of_result result) + else M.return () + in + let result = + Term.eval_choice ?argv + ~catch:and_exit (* Only log exceptions not raised to the user code *) + (default_cmd config args name tl) + choices + in + match result with + | `Ok unit_m -> unit_m >>= fun () -> exit_or_return (`Ok ()) + | (`Help | `Version | `Error `Exn) as result -> exit_or_return result + | `Error (`Parse | `Term) as result -> + exit (Term.exit_status_of_result result) + + let run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only + ?show_errors ?json ?filter:_ ?log_dir ?bail ?record_backtrace ?argv = + Config.User.kcreate (run_with_args' ~argv) ?and_exit ?verbose ?compact + ?tail_errors ?quick_only ?show_errors ?json ?filter:None ?log_dir ?bail + ?record_backtrace + + let run ?and_exit ?verbose ?compact ?tail_errors ?quick_only ?show_errors + ?json ?filter:_ ?log_dir ?bail ?record_backtrace ?argv name tl = + Config.User.kcreate + (fun c -> run_with_args' ~argv c name (Term.pure ()) tl) + ?and_exit ?verbose ?compact ?tail_errors ?quick_only ?show_errors ?json + ?filter:None ?log_dir ?bail ?record_backtrace + end module V1 = struct include V1_types + module Make = Make_v1 +end + +module Unstable = struct module Make = Make end diff --git a/src/alcotest-engine/cli_intf.ml b/src/alcotest-engine/cli_intf.ml index be9a04dc..e09702b1 100644 --- a/src/alcotest-engine/cli_intf.ml +++ b/src/alcotest-engine/cli_intf.ml @@ -63,7 +63,10 @@ module type Cli = sig module type S = V1_types.S module type MAKER = V1_types.MAKER - module Make (P : Platform.MAKER) (M : Monad.S) : - V1_types.S with type return = unit M.t + module Make : MAKER + end + + module Unstable : sig + module Make : Core.Unstable.MAKER end end diff --git a/src/alcotest-engine/config.ml b/src/alcotest-engine/config.ml index af10ebd1..f000c3a7 100644 --- a/src/alcotest-engine/config.ml +++ b/src/alcotest-engine/config.ml @@ -135,78 +135,122 @@ module Key = struct end module Filter = struct - type t = filter - - let regex : Re.re Arg.conv = - let parse s = - try Ok Re.(compile @@ Pcre.re s) with - | Re.Perl.Parse_error -> - Error (`Msg "Perl-compatible regexp parse error") - | Re.Perl.Not_supported -> Error (`Msg "unsupported regexp feature") - in - let print = Re.pp_re in - Arg.conv (parse, print) - - let int_range_list : int list Arg.conv = - let exception Invalid_format in - let parse s = - let rec range lower upper acc = - if lower > upper then acc else range (succ lower) upper (lower :: acc) + module V1 = struct + type t = filter_v1 + + let regex : Re.re Arg.conv = + let parse s = + try Ok Re.(compile @@ Pcre.re s) with + | Re.Perl.Parse_error -> + Error (`Msg "Perl-compatible regexp parse error") + | Re.Perl.Not_supported -> Error (`Msg "unsupported regexp feature") + in + let print = Re.pp_re in + Arg.conv (parse, print) + + let int_range_list : int list Arg.conv = + let exception Invalid_format in + let parse s = + let rec range lower upper acc = + if lower > upper then acc + else range (succ lower) upper (lower :: acc) + in + let process_range acc s = + String.cuts ~sep:".." s + |> List.concat_map (String.cuts ~sep:"-") + |> List.map String.to_int + |> function + | [ Some i ] -> i :: acc + | [ Some lower; Some upper ] when lower <= upper -> + range lower upper acc + | _ -> raise Invalid_format + in + let ranges = String.cuts ~sep:"," s in + match List.fold_left process_range [] ranges with + | list -> Ok list + | exception Invalid_format -> + Error + (`Msg + "must be a comma-separated list of integers / integer ranges") in - let process_range acc s = - String.cuts ~sep:".." s - |> List.concat_map (String.cuts ~sep:"-") - |> List.map String.to_int - |> function - | [ Some i ] -> i :: acc - | [ Some lower; Some upper ] when lower <= upper -> - range lower upper acc - | _ -> raise Invalid_format + let print ppf set = Fmt.(braces @@ list ~sep:comma int) ppf set in + Arg.conv (parse, print) + + let term = + let+ name_regex = + let doc = "A regular expression matching the names of tests to run" in + Arg.( + value & pos 0 (some regex) None & info [] ~doc ~docv:"NAME_REGEX") + and+ index_cases = + let doc = + "A comma-separated list of test case numbers (and ranges of \ + numbers) to run, e.g: '4,6-10,19'. When specifying ranges, both \ + '-' and '..' are accepted as valid separators." + in + Arg.( + value + & pos 1 (some int_range_list) None + & info [] ~doc ~docv:"TESTCASES") + in + match (name_regex, index_cases) with + | None, None -> None + | _, _ -> + let name_filter = + match name_regex with + | None -> fun _ -> true + | Some r -> fun n -> Re.execp r n + in + let index_filter = + match index_cases with + | None -> fun _ -> true + | Some ints -> + let set = Int.Set.of_list ints in + fun i -> Int.Set.mem i set + in + Some + (fun ~name ~index -> + if name_filter name && index_filter index then `Run else `Skip) + end + + module V2 = struct + type t = filter_v2 + + let of_v1 ~quick_only position_filter _ tags = + let speed_level = + match Tag.Set.find Tag.Speed_level.tag tags with + | Some `Slow -> if quick_only then `Skip else `Run + | Some `Quick | None -> `Run in - let ranges = String.cuts ~sep:"," s in - match List.fold_left process_range [] ranges with - | list -> Ok list - | exception Invalid_format -> - Error - (`Msg - "must be a comma-separated list of integers / integer ranges") + let position = + match Tag.Set.find Tag.Position.tag tags with + | None -> `Run + | Some (name, index) -> position_filter ~name ~index + in + match (speed_level, position) with + | `Run, `Run -> `Run + | `Skip, _ | _, `Skip -> `Skip + end + + type t = [ `V1 of V1.t | `V2 of V2.t ] + + let default = + (* TODO: duplicated *) + let only_if _ s = + match Tag.Set.find Tag.Predicate.tag s with + | Some p -> p () + | None -> `Run in - let print ppf set = Fmt.(braces @@ list ~sep:comma int) ppf set in - Arg.conv (parse, print) - let term = - let+ name_regex = - let doc = "A regular expression matching the names of tests to run" in - Arg.(value & pos 0 (some regex) None & info [] ~doc ~docv:"NAME_REGEX") - and+ index_cases = - let doc = - "A comma-separated list of test case numbers (and ranges of numbers) \ - to run, e.g: '4,6-10,19'. When specifying ranges, both '-' and '..' \ - are accepted as valid separators." - in - Arg.( - value - & pos 1 (some int_range_list) None - & info [] ~doc ~docv:"TESTCASES") + let quick_only_config c s = + match Tag.Set.find Tag.Speed_level.tag s with + | Some `Slow when c#quick_only -> `Skip + | _ -> `Run in - match (name_regex, index_cases) with - | None, None -> None - | _, _ -> - let name_filter = - match name_regex with - | None -> fun _ -> true - | Some r -> fun n -> Re.execp r n - in - let index_filter = - match index_cases with - | None -> fun _ -> true - | Some ints -> - let set = Int.Set.of_list ints in - fun i -> Int.Set.mem i set - in - Some - (fun ~name ~index -> - if name_filter name && index_filter index then `Run else `Skip) + + let ( ++ ) f g a b = + match (f a b, g a b) with `Run, `Run -> `Run | _, _ -> `Skip + in + only_if ++ quick_only_config end end @@ -252,7 +296,7 @@ module User = struct and+ show_errors = Show_errors.term and+ quick_only = Quick_only.term and+ json = Json.term - and+ filter = Filter.term + and+ filter = Filter.V1.term and+ log_dir = Log_dir.term and+ bail = Bail.term in { @@ -263,7 +307,7 @@ module User = struct show_errors; quick_only; json; - filter; + filter = Option.map (fun x -> `V1 x) filter; log_dir; bail; record_backtrace = Some record_backtrace; @@ -290,6 +334,7 @@ module User = struct } let create : (unit -> t) with_options = kcreate (fun t () -> t) + let quick_only t = Option.value ~default:Quick_only.default t.quick_only let and_exit t = Option.value ~default:And_exit.default t.and_exit let record_backtrace t = @@ -302,8 +347,8 @@ let apply_defaults ~default_log_dir : User.t -> t = verbose; compact; tail_errors; - quick_only; show_errors; + quick_only; json; filter; log_dir; @@ -311,16 +356,23 @@ let apply_defaults ~default_log_dir : User.t -> t = record_backtrace; } -> let open Key in + let quick_only = Option.value ~default:Quick_only.default quick_only in + let filter = + match filter with + | None -> Filter.default + | Some (`V2 f) -> f + | Some (`V1 f) -> Filter.V2.of_v1 ~quick_only f + in object method and_exit = Option.value ~default:And_exit.default and_exit method verbose = Option.value ~default:Verbose.default verbose method compact = Option.value ~default:Compact.default compact method tail_errors = Option.value ~default:Tail_errors.default tail_errors - method quick_only = Option.value ~default:Quick_only.default quick_only + method quick_only = quick_only method show_errors = Option.value ~default:Show_errors.default show_errors method json = Option.value ~default:Json.default json method filter = filter - method log_dir = Option.value ~default:default_log_dir log_dir + method log_dir = Option.value ~default:(Lazy.force default_log_dir) log_dir method bail = Option.value ~default:Bail.default bail method record_backtrace = diff --git a/src/alcotest-engine/config_intf.ml b/src/alcotest-engine/config_intf.ml index 12a68df1..28fdad46 100644 --- a/src/alcotest-engine/config_intf.ml +++ b/src/alcotest-engine/config_intf.ml @@ -1,8 +1,12 @@ module Types = struct type bound = [ `Unlimited | `Limit of int ] - type filter = name:string -> index:int -> [ `Run | `Skip ] + type filter_result = [ `Run | `Skip ] - type t = + type filter_v1 = name:string -> index:int -> filter_result + and filter_v2 = t -> Tag.Set.t -> filter_result + and filter = [ `V1 of filter_v1 | `V2 of filter_v2 ] + + and t = < and_exit : bool ; verbose : bool ; compact : bool @@ -10,7 +14,7 @@ module Types = struct ; quick_only : bool ; show_errors : bool ; json : bool - ; filter : filter option + ; filter : filter_v2 ; log_dir : string ; bail : bool ; record_backtrace : bool > @@ -54,9 +58,10 @@ module type Config = sig (** {2 Accessors} *) + val quick_only : t -> bool val and_exit : t -> bool val record_backtrace : t -> bool end - val apply_defaults : default_log_dir:string -> User.t -> t + val apply_defaults : default_log_dir:string Lazy.t -> User.t -> t end diff --git a/src/alcotest-engine/core.ml b/src/alcotest-engine/core.ml index 611d1e05..bf3fd209 100644 --- a/src/alcotest-engine/core.ml +++ b/src/alcotest-engine/core.ml @@ -1,5 +1,6 @@ (* * Copyright (c) 2013-2016 Thomas Gazagnaire + * Copyright (c) 2020-2021 Craig Ferguson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,11 +16,10 @@ *) include Core_intf +include Types open! Import open Model -exception Check_error of unit Fmt.t - let () = let print_error = (* We instantiate the error print buffer lazily, so as to be sensitive to @@ -40,42 +40,45 @@ let () = module Make (P : Platform.MAKER) (M : Monad.S) = struct module P = P (M) - - module Pp = struct - include Pp - include Pp.Make (P) - end - module M = Monad.Extend (M) - module Suite = Suite (M) - module Log_trap = Log_trap.Make (M) (P) - include M.Syntax - (* Types *) - type return = unit M.t - type 'a run = 'a -> unit M.t - type speed_level = [ `Quick | `Slow ] + module Suite = struct + include Suite (M) - exception Test_error + let of_tests_exn ~name ~file ~loc tests = + match of_tests ~name ~file ~loc tests with + | Ok s -> s + | Error (`Duplicate_path path) -> + Pp.user_error "Duplicate test path: `%s'" path + | Error `Empty_file -> + Pp.user_error + "Suite file cannot be \"\". Please pass a non-empty string to \ + `run`." + | Error `Empty_name -> + Pp.user_error + "Suite name cannot be \"\". Please pass a non-empty string to \ + `run`." + | Error `No_identifier -> + Pp.user_error "Suite name and file cannot cannot both be `None`" + end - type 'a test_case = string * speed_level * 'a run + module Log_trap = Log_trap.Make (M) (P) - let test_case n s f = (n, s, f) + module Pp = struct + include Pp + include Pp.Make (P) + end - type 'a test = string * 'a test_case list + open M.Syntax - (* global state *) type 'a t = { - (* library values. *) suite : 'a Suite.t; - (* runtime state. *) - mutable errors : unit Fmt.t list; - (* runtime options. *) - max_label : int; - (** Longest test label in the suite, in UTF-8 characters. *) + loc : Source_code_position.pos option; config : Config.t; run_id : string; log_trap : Log_trap.t; + progress_reporter : Pp.Progress_reporter.t option; + mutable errors : unit Fmt.t list; } let gen_run_id = @@ -88,63 +91,61 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct in fun () -> String.v ~len:8 random_hex - let empty ~config ~trap_logs ~suite_name:unescaped_name = - let errors = [] in - let suite = - match Suite.v ~name:unescaped_name with - | Ok s -> s - | Error `Empty_name -> - Pp.user_error - "Suite name cannot cannot be empty. Please pass a non-empty string \ - to `run`." - in - let max_label = 0 in + let v ~suite ~loc ~config = let run_id = gen_run_id () in let log_trap = - match trap_logs with - | false -> Log_trap.inactive - | true -> + match config#verbose with + | true -> Log_trap.inactive + | false -> Log_trap.active ~root:config#log_dir ~uuid:run_id - ~suite_name:(Suite.name suite) + ~suite_name:(Suite.identifier suite) + in + let progress_reporter = + match config#json with + | true -> None + | false -> + Some + (Pp.Progress_reporter.create ~ppf:Fmt.stdout + ~isatty:(P.stdout_isatty ()) ~compact:config#compact + ~selector_on_first_failure: + (not (config#verbose || config#show_errors))) in - { suite; errors; max_label; config; run_id; log_trap } + { suite; errors = []; loc; config; run_id; log_trap; progress_reporter } + + type 'a test = 'a Suite.test + + let bt () = match Printexc.get_backtrace () with "" -> "" | s -> "\n" ^ s + + let exn path name pp : Run_result.t = + Exn (path, name, Fmt.(pp ++ const lines (bt ()))) + + let test ?pos ?(tags = Tag.Set.empty) ~name fn = + Suite.test ~name ~tags ~loc:pos fn - let compare_speed_level s1 s2 = - match (s1, s2) with - | `Quick, `Quick | `Slow, `Slow -> 0 - | `Quick, _ -> 1 - | _, `Quick -> -1 + let group ?pos ?(tags = Tag.Set.empty) ~name ts = + Suite.group ~name ~tags ~loc:pos ts let pp_suite_results t = let log_dir = Log_trap.pp_current_run_dir t.log_trap in Pp.suite_results ~log_dir t.config - let pp_event ~isatty ~prior_error ~tests_so_far t = - let cfg = t.config in - let selector_on_failure = - (not prior_error) && not (cfg#verbose || cfg#show_errors) - in - if not cfg#json then - Pp.event ~isatty ~compact:cfg#compact ~max_label:t.max_label - ~doc_of_test_name:(Suite.doc_of_test_name t.suite) - ~selector_on_failure ~tests_so_far - else Fmt.nop - - let pp_info t = - Pp.info ~max_label:t.max_label - ~doc_of_test_name:(Suite.doc_of_test_name t.suite) - - let color c ppf fmt = Fmt.(styled c string) ppf fmt - let red_s fmt = color `Red fmt - let red ppf fmt = Fmt.kstr (fun str -> red_s ppf str) fmt - - let pp_error t ppf e = - let path, error_fmt = - match e with `Error (p, f) -> (p, f) | `Exn (p, _, f) -> (p, f) + let report_event t event = + match t.progress_reporter with + | None -> () + | Some pr -> Pp.Progress_reporter.event pr event + + let red ppf fmt = Fmt.kstr (fun str -> Fmt.(styled `Red string) ppf str) fmt + + let pp_error t ppf (e : Run_result.t) = + let index, error_fmt = + match e with + | Error (p, f) -> (p, f) + | Exn (p, _, f) -> (p, f) + | _ -> assert false in let pp_logs ppf () = let pp_logs = - Log_trap.recover_logs ~tail:t.config#tail_errors t.log_trap path + Log_trap.recover_logs ~tail:t.config#tail_errors t.log_trap index in match (t.config#verbose, pp_logs) with | true, _ | _, None -> Fmt.pf ppf "%a@," error_fmt () @@ -152,48 +153,28 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct let pp_log_dir = Pp.map_theta ~f:(fun s -> Pp.quoted (Fmt.styled `Cyan s)) - (Log_trap.pp_log_location t.log_trap path) + (Log_trap.pp_log_location t.log_trap index) in Fmt.pf ppf "%tLogs saved to %t.@," pp_logs pp_log_dir in Fmt.( - Pp.with_surrounding_box - (const - (Pp.event_line ~margins:3 ~max_label:t.max_label - ~doc_of_test_name:(Suite.doc_of_test_name t.suite)) - (`Result (path, e))) + Pp.with_surrounding_box (const Pp.event_line { index; type_ = Result e }) ++ pp_logs ++ Pp.horizontal_rule ++ cut) ppf () - let has_run : Run_result.t -> bool = function - | `Ok | `Error _ | `Exn _ -> true - | `Skip | `Todo _ -> false - - let bt () = match Printexc.get_backtrace () with "" -> "" | s -> "\n" ^ s - let exn path name pp = `Exn (path, name, Fmt.(pp ++ const lines (bt ()))) - - let protect_test path (f : 'a run) : 'a -> Run_result.t M.t = - fun args -> - M.catch - (fun () -> f args >|= fun () -> `Ok) - ((function - | Check_error err -> - let err = Fmt.(err ++ const string (bt ())) in - `Error (path, err) - | Failure s -> exn path "failure" Fmt.(const string s) - | Invalid_argument s -> exn path "invalid" Fmt.(const string s) - | e -> exn path "exception" Fmt.(const exn e)) - >> M.return) - - type running_state = { tests_so_far : int; first_error : int option } + type running_state = { + tests_so_far : int; + first_error : int option; + failures : int; + } (** State that is kept during the test executions. *) - let with_captured_logs t name fn args = + let with_captured_logs t idx fn args = if t.config#verbose then fn args else - Log_trap.with_captured_logs t.log_trap name + Log_trap.with_captured_logs t.log_trap idx (fun () -> (* When capturing the logs of a test, also add the result of the test at the end. *) @@ -202,217 +183,240 @@ module Make (P : Platform.MAKER) (M : Monad.S) = struct result) () - let perform_test t args { tests_so_far; first_error } - (test : _ Suite.test_case) = - let open Suite in - let print_event = - pp_event t - ~prior_error:(Option.is_some first_error) - ~tests_so_far ~isatty:(P.stdout_isatty ()) Fmt.stdout - in + let protect_test : 'a. Index.t -> ('a -> unit M.t) -> 'a -> Run_result.t M.t = + fun index f args -> + M.catch + (fun () -> f args >|= fun () -> Run_result.Ok) + ((function + | Check_error err -> + let err = Fmt.(err ++ const string (bt ())) in + Run_result.Error (index, err) + | Failure s -> exn index "failure" Fmt.(const string s) + | Invalid_argument s -> exn index "invalid" Fmt.(const string s) + | e -> exn index "exception" Fmt.(const exn e)) + >> M.return) + + let perform_test t args index test acc = + let test = protect_test index test in let* () = M.return () in - print_event (`Start test.name); - let+ result, errored = - match test.fn with - | `Skip -> M.return (`Skip, false) - | `Run fn -> - Fmt.(flush stdout) () (* Show event before any test stderr *); - let+ result = with_captured_logs t test.name fn args in - (* Store errors *) - let errored : bool = - let error, errored = - match result with - | (`Error _ | `Exn (_, _, _)) as e -> - ([ Fmt.const (pp_error t) e ], true) - | _ -> ([], false) - in - t.errors <- error @ t.errors; - errored - in - (* Show any remaining test output before the event *) - Fmt.(flush stdout ()); - Fmt.(flush stderr ()); - (result, errored) + report_event t { index; type_ = Start }; + Fmt.(flush stdout) () (* Show event before any test stderr *); + let+ (result : Run_result.t) = with_captured_logs t index test args in + (* Store errors *) + let errored : bool = + if Run_result.is_failure result then ( + t.errors <- Fmt.const (pp_error t) result :: t.errors; + true) + else false in - print_event (`Result (test.name, result)); - let error = if errored then Some tests_so_far else None in - let state = + (* Show any remaining test output before the event *) + Fmt.(flush stdout ()); + Fmt.(flush stderr ()); + report_event t { index; type_ = Result result }; + let error = if errored then Some acc.tests_so_far else None in + let acc = { - tests_so_far = tests_so_far + 1; - first_error = Option.(first_error || error); + tests_so_far = acc.tests_so_far + 1; + first_error = Option.(acc.first_error || error); + failures = (acc.failures + if errored then 1 else 0); } in - (state, result) + (acc, result) + + exception Test_error - let perform_tests t tests args = + let pp_suite_desc ppf suite = + match Suite.(pp_name suite, pp_file suite) with + | Some name, Some file -> Fmt.pf ppf "`%t', in file `%t'" name file + | Some name, None -> Fmt.pf ppf "`%t'" name + | None, Some file -> Fmt.pf ppf "file `%t'" file + | None, None -> assert false + + let perform_tests : type a. a t -> a -> int M.t = + fun t args -> let currently_bailing acc = Option.is_some acc.first_error && t.config#bail in - let+ state, test_results = - M.List.fold_map_s - (fun acc test -> - if currently_bailing acc then - M.return ({ acc with tests_so_far = succ acc.tests_so_far }, `Skip) - else perform_test t args acc test) - { tests_so_far = 0; first_error = None } - tests + let test_it = perform_test t args in + let* () = M.return () in + Fmt.pr "Testing %a.@,@[%t@]" pp_suite_desc t.suite + (Pp.map_theta + ~f:(Fmt.styled `Faint) + (fun ppf -> + Fmt.pf ppf "This run has ID %a.@,@," (Pp.quoted Fmt.string) t.run_id)); + + let start_time = P.time () in + let+ results = + Suite.foldi_until t.suite + ~filter:(Filter.apply t.config#filter t.config) + ~init:{ tests_so_far = 0; first_error = None; failures = 0 } + ~finish:(fun x -> x) + ~group:(fun _ctx acc _ -> + if currently_bailing acc then M.return (Stop acc) + else M.return (Continue acc)) + ~test:(fun index (acc : running_state) test -> + if currently_bailing acc then M.return (Stop acc) + else + match test with + | `Run f -> + let+ x, _ = test_it index f acc in + Continue x + | `Skip -> + report_event t { index; type_ = Result Skip }; + M.return (Continue acc)) in let () = - if currently_bailing state then - match state.tests_so_far - Option.get_exn state.first_error - 1 with + if currently_bailing results then + match results.tests_so_far - Option.get_exn results.first_error - 1 with | n when n > 0 -> Fmt.pr "@\n %a@\n" Fmt.(styled `Faint string) - (Fmt.str "... with %d subsequent test%a skipped." n Pp.pp_plural n) + (Fmt.str "... with %d subsequent test%a skipped." n Pp.plural n) | 0 -> () | _ -> assert false in - test_results - - let skip_label test_case = Suite.{ test_case with fn = `Skip } - - let filter_test_case p test_case = - match p with - | None -> true - | Some p -> ( - let name, index = - let tn = test_case.Suite.name in - Test_name.(Safe_string.to_unescaped_string (name tn), index tn) - in - match p ~name ~index with `Run -> true | `Skip -> false) - - let filter_test_cases ~subst path test_cases = - let filter_test_case = filter_test_case path in - test_cases - |> List.filter_map (fun tc -> - if filter_test_case tc then Some tc - else if subst then Some (skip_label tc) - else None) - - let select_speed speed_level (test_case : 'a Suite.test_case as 'tc) : 'tc = - if compare_speed_level test_case.speed_level speed_level >= 0 then test_case - else Suite.{ test_case with fn = `Skip } - - let result t test args = - let initial_backtrace_status = Printexc.backtrace_status () in - if t.config#record_backtrace then Printexc.record_backtrace true; - let start_time = P.time () in - let speed_level = if t.config#quick_only then `Quick else `Slow in - let test = List.map (select_speed speed_level) test in - let+ results = perform_tests t test args in - let time = P.time () -. start_time in - let success = List.length (List.filter has_run results) in - let failures = List.length (List.filter Run_result.is_failure results) in - if t.config#record_backtrace then - Printexc.record_backtrace initial_backtrace_status; - Pp.{ time; success; failures; errors = List.rev t.errors } - - let list_registered_tests t () = - Suite.tests t.suite - |> List.map (fun t -> t.Suite.name) - |> List.sort Test_name.compare - |> Fmt.(list ~sep:(const string "\n") (pp_info t) stdout) - - let register (type a) (t : a t) (name, (ts : a test_case list)) : a t = - let max_label = max t.max_label (String.length_utf8 name) in - let test_details = - List.mapi - (fun index (doc, speed, test) -> - let path = Test_name.v ~name ~index in - let doc = - if doc = "" || doc.[String.length doc - 1] = '.' then doc - else doc ^ "." - in - let test a = protect_test path test a in - (path, doc, speed, `Run test)) - ts - in - let suite = - List.fold_left - (fun acc td -> - match Suite.add acc td with - | Ok acc -> acc - | Error (`Duplicate_test_path path) -> - Fmt.kstr Pp.user_error "Duplicate test path: `%s'" path) - t.suite test_details - in - { t with suite; max_label } - - let register_all t cases = List.fold_left register t cases - - let run_tests t () args = - let filter = t.config#filter in - let suite = Suite.tests t.suite in - let is_empty = filter_test_cases ~subst:false filter suite = [] in - let+ result = - if is_empty && Option.is_some filter then ( - flush_all (); - Fmt.(pf stderr) - "%a\n" red - "Invalid request (no tests to run, filter skipped everything)!"; - exit 1) - else - let tests = filter_test_cases ~subst:true filter suite in - result t tests args - in - (pp_suite_results t) Fmt.stdout result; - result.failures - - let default_log_dir () = - let fname_concat l = List.fold_left Filename.concat "" l in - fname_concat [ P.getcwd (); "_build"; "_tests" ] - - type 'a with_options = 'a Config.with_options - - let list_tests (type a) (tl : a test list) = - (* TODO: refactor [register_all] to not require dummy state *) - let config = - Config.apply_defaults ~default_log_dir:"" - (Config.User.create ()) - in - let t = - register_all - (empty ~config ~trap_logs:false ~suite_name:"") - tl - in - list_registered_tests t (); + let time = P.time () -. start_time + and success = results.tests_so_far + and failures = results.failures + and errors = List.rev t.errors in + (pp_suite_results t) Fmt.stdout Pp.{ time; success; failures; errors }; + failures + + let list_tests ?pos ?config:_ ~name tests = + let suite = Suite.of_tests_exn ~name ~file:None ~loc:pos tests in + Fmt.pr "@[%a@]@." Suite.pp suite; M.return () - let run_with_args' (config : Config.User.t) name (type a) (args : a) - (tl : a test list) = - let config = - Config.apply_defaults ~default_log_dir:(default_log_dir ()) config - in - let t = empty ~config ~trap_logs:(not config#verbose) ~suite_name:name in - let t = register_all t tl in - let+ test_failures = - (* Only print inside the concurrency monad *) - let* () = M.return () in - let open Fmt in - pr "Testing %a.@," (Pp.quoted Fmt.(styled `Bold Suite.pp_name)) t.suite; - pr "@[%a@]" - (styled `Faint (fun ppf () -> - pf ppf "This run has ID %a.@,@," (Pp.quoted string) t.run_id)) - (); - run_tests t () args + let default_log_dir = + let ( / ) = Filename.concat in + lazy (P.getcwd () / "_build" / "_tests") + + let run_with_args ?pos ?(config = Config.User.create ()) ?name ?__FILE__:file + test_ctx tests = + let config = Config.apply_defaults ~default_log_dir config in + let suite = Suite.of_tests_exn ~name ~file ~loc:pos tests in + let* at_least_one_test = + Suite.foldi_until suite + ~filter:(Filter.apply config#filter config) + ~init:() + ~finish:(fun () -> false) + ~test: + (fun _ () -> function + | `Run _ -> M.return (Stop true) + | `Skip -> M.return (Continue ())) in - match (test_failures, t.config#and_exit) with + if not at_least_one_test then ( + Fmt.(pf stderr) + "%a\n" red + "Invalid request (no tests to run, filter skipped everything)!"; + exit 1); + let initial_backtrace_status = Printexc.backtrace_status () in + if config#record_backtrace then Printexc.record_backtrace true; + let t = v ~suite ~loc:pos ~config in + let+ test_failures = perform_tests t test_ctx in + if config#record_backtrace then + Printexc.record_backtrace initial_backtrace_status; + match (test_failures, config#and_exit) with | 0, true -> exit 0 | 0, false -> () | _, true -> exit 1 | _, false -> raise Test_error - let run' config name (tl : unit test list) = run_with_args' config name () tl - - let run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only - ?show_errors ?json ?filter ?log_dir ?bail ?record_backtrace = - Config.User.kcreate run_with_args' ?and_exit ?verbose ?compact ?tail_errors - ?quick_only ?show_errors ?json ?filter ?log_dir ?bail ?record_backtrace - - let run = Config.User.kcreate run' + let run ?pos ?config ?name ?__FILE__:file suite = + run_with_args ?pos ?config ?name ?__FILE__:file () suite end +module Make_v1 : V1_types.MAKER = +functor + (P : Platform.MAKER) + (M : Monad.S) + -> + struct + module X = Make (P) (M) + module P = P (M) + module M = Monad.Extend (M) + open M.Syntax + + (* Types *) + type return = unit M.t + type 'a run = 'a -> unit M.t + type speed_level = [ `Quick | `Slow ] + + exception Test_error + + type 'a test_case = string * speed_level * 'a run + + let test_case n s f = (n, s, f) + + type 'a test = string * 'a test_case list + + type 'a with_options = + ?and_exit:bool -> + ?verbose:bool -> + ?compact:bool -> + ?tail_errors:[ `Unlimited | `Limit of int ] -> + ?quick_only:bool -> + ?show_errors:bool -> + ?json:bool -> + ?filter:(name:string -> index:int -> [ `Run | `Skip ]) -> + ?log_dir:string -> + ?bail:bool -> + ?record_backtrace:bool -> + 'a + + let migrate_suite ts = + ListLabels.map ts ~f:(fun (group_name, children) -> + let children = + ListLabels.mapi children ~f:(fun i (name, speed_level, fn) -> + let speed = + match speed_level with + | `Quick -> Tag.Speed_level.quick + | `Slow -> Tag.Speed_level.slow + in + let position = Tag.(v Position.tag) (group_name, i) in + let tags = Tag.Set.(empty |> add speed |> add position) in + X.test ~name ~tags fn) + in + X.group ~name:group_name children) + + let run_main config name args tl = + let config = Config.User.(create () || config) in + let suite = migrate_suite tl in + X.run_with_args ~name ~config args suite >|= fun _ -> exit 0 + + let list_tests name (ts : _ test list) = + let suite = migrate_suite ts in + X.list_tests ~name:(Some name) suite + + let run' config name (tl : unit test list) = run_main config name () tl + + let run_with_args ?and_exit ?verbose ?compact ?tail_errors ?quick_only + ?show_errors ?json ?filter ?log_dir ?bail ?record_backtrace = + Config.User.kcreate run_main ?and_exit ?verbose ?compact ?tail_errors + ?quick_only ?show_errors ?json + ?filter:(Option.map (fun f -> `V1 f) filter) + ?log_dir ?bail ?record_backtrace + + let run : (string -> unit test list -> return) with_options = + fun ?and_exit ?verbose ?compact ?tail_errors ?quick_only ?show_errors ?json + ?filter ?log_dir ?bail ?record_backtrace -> + Config.User.kcreate run' ?and_exit ?verbose ?compact ?tail_errors + ?quick_only ?show_errors ?json + ?filter:(Option.map (fun f -> `V1 f) filter) + ?log_dir ?bail ?record_backtrace + + let run' = run' + let run_with_args' a b c d = run_main a b c d + end + module V1 = struct include V1_types + module Make = Make_v1 +end + +module Unstable = struct + type nonrec 'a identified = 'a identified + + include Unstable_types module Make = Make end diff --git a/src/alcotest-engine/core_intf.ml b/src/alcotest-engine/core_intf.ml index 39152b2c..3e4ef8f7 100644 --- a/src/alcotest-engine/core_intf.ml +++ b/src/alcotest-engine/core_intf.ml @@ -89,7 +89,7 @@ module V1_types = struct module type MAKER = functor (P : Platform.MAKER) (M : Monad.S) -> sig include S with type return = unit M.t - val list_tests : 'a test list -> return + val list_tests : string -> 'a test list -> return (** Print all of the test cases in a human-readable form *) val run' : Config.User.t -> string -> unit test list -> return @@ -100,9 +100,118 @@ module V1_types = struct end end -module type Core = sig +module Types = struct exception Check_error of unit Fmt.t + (* TODO(4.08): replace with local type substitution *) + type 'a identified = + ?pos:Source_code_position.pos -> ?tags:Tag.Set.t -> name:string -> 'a + (** A test suite is a tree of named test cases, with named internal nodes. + This type defines the metadata associated with each node in the tree: *) +end + +module Unstable_types = struct + module type Test_constructors = sig + (* These types are intended to be destructively-substituted by various + backends. *) + + type 'a m + type 'a test_args + type tag_set + type source_code_position + + type 'a test + (** The type of unit tests. *) + + val test : + ?pos:source_code_position -> + ?tags:tag_set -> + name:string -> + ('a -> unit m) test_args -> + 'a test + (** [test ~name f] is a named test that runs the function [f]. If [f] makes + a failing assertion (e.g. via [Alcotest.check]) or raises an exception, + then the test case will fail. Otherwise, the test case passes. *) + + val group : + ?pos:source_code_position -> + ?tags:tag_set -> + name:string -> + 'a test list -> + 'a test + (** [group ~name tests] is a group of tests with the given name.*) + end + + module type Test_runners = sig + type 'a m + type 'a test + type config + type source_code_position + + val run : + ?pos:source_code_position -> + ?config:config -> + ?name:string -> + ?__FILE__:string -> + unit test list -> + unit m + + val run_with_args : + ?pos:source_code_position -> + ?config:config -> + ?name:string -> + ?__FILE__:string -> + 'a -> + 'a test list -> + unit m + + (** {2 Log capturing} + + Whenever a test is executed, its standard output / error streams are + redirected to a log file on disk. If the test fails, this log file is + printed to the *) + + (** {2 Command-line interface} + + The test suite runners can be configured at run-time via an + auto-generated command line interface, in addition to the [~config] + argument. + + Run the binary with the [--help] option to see the full list of + supported command-line options (e.g. using + [dune exec ./path/to/text.exe -- --help]). *) + end + + (** Extensions to {!S} for use by backends. *) + module type S = sig + include Test_constructors + + include + Test_runners + with type 'a m := 'a m + and type 'a test := 'a test + and type source_code_position := source_code_position + + val list_tests : + ?pos:source_code_position -> + ?config:config -> + name:string option -> + _ test list -> + unit m + end + + module type MAKER = functor (P : Platform.MAKER) (M : Monad.S) -> + S + with type 'a m := 'a M.t + and type 'a test_args := 'a + and type config := Config.User.t + and type source_code_position := Source_code_position.pos + and type tag_set := Tag.Set.t +end + +module type Core = sig + include module type of Types + module V1 : sig module type S = V1_types.S module type MAKER = V1_types.MAKER @@ -113,4 +222,11 @@ module type Core = sig [run_with_args] functions must be scheduled in a global event loop. Intended for use by the {!Alcotest_lwt} and {!Alcotest_async} backends. *) end + + module Unstable : sig + type nonrec 'a identified = 'a identified + + include module type of Unstable_types + module Make : MAKER + end end diff --git a/src/alcotest-engine/filter.ml b/src/alcotest-engine/filter.ml new file mode 100644 index 00000000..392aed23 --- /dev/null +++ b/src/alcotest-engine/filter.ml @@ -0,0 +1,17 @@ +type t = Config.filter_v2 + +let v f _ = f + +let ( ++ ) f g a b = + match (f a b, g a b) with `Run, `Run -> `Run | _, _ -> `Skip + +let only_if _ s = + match Tag.Set.find Tag.Predicate.tag s with Some p -> p () | None -> `Run + +let quick_only_config c s = + match Tag.Set.find Tag.Speed_level.tag s with + | Some `Slow when c#quick_only -> `Skip + | _ -> `Run + +let default = only_if ++ quick_only_config +let apply f = f diff --git a/src/alcotest-engine/filter.mli b/src/alcotest-engine/filter.mli new file mode 100644 index 00000000..7582a988 --- /dev/null +++ b/src/alcotest-engine/filter.mli @@ -0,0 +1,15 @@ +type t = Config.filter_v2 + +val v : (Tag.Set.t -> [ `Run | `Skip ]) -> t + +val ( ++ ) : t -> t -> t +(** [f ++ g] is the filter that runs only tests that are run by both [f] {i and} + [g]. *) + +val default : t + +(** Internal: *) + +val apply : t -> Config.t -> Tag.Set.t -> [ `Run | `Skip ] +val quick_only_config : t +val only_if : t diff --git a/src/alcotest-engine/log_trap.ml b/src/alcotest-engine/log_trap.ml index 4254acd5..c01459d2 100644 --- a/src/alcotest-engine/log_trap.ml +++ b/src/alcotest-engine/log_trap.ml @@ -1,10 +1,10 @@ open Astring -open Model -open! Import include Log_trap_intf +open! Import +open Model module Make - (Promise : Monad.EXTENDED) + (Promise : Monad.Extended) (Platform : Platform.S with type 'a promise := 'a Promise.t) = struct open Promise.Syntax @@ -12,6 +12,25 @@ struct type state = { root : string; uuid : string; suite_name : string } type t = Inactive | Active of state + let inactive = Inactive + + let active ~root ~uuid ~suite_name = + Platform.prepare_log_trap ~root ~uuid ~name:suite_name; + Active { root; uuid; suite_name } + + let log_dir { suite_name; uuid; root } = + (* We don't create symlinks on Windows. *) + let via_symlink = not Sys.win32 in + Filename.concat root (if via_symlink then suite_name else uuid) + + let parent_fpath state index = + ListLabels.fold_left (Index.parent_path index) ~init:(log_dir state) + ~f:(fun acc x -> Filename.concat acc (Safe_string.to_string x)) + + let output_fpath state index = + Filename.concat (parent_fpath state index) + (Safe_string.to_string (Index.leaf_name index) ^ ".output") + (** Take a string path and collapse a leading [$HOME] path segment to [~]. *) let maybe_collapse_home path = match Platform.home_directory () with @@ -26,12 +45,6 @@ struct in "~" ^ tail) - let inactive = Inactive - - let active ~root ~uuid ~suite_name = - Platform.prepare_log_trap ~root ~uuid ~name:suite_name; - Active { root; uuid; suite_name } - let pp_path = Fmt.using maybe_collapse_home Fmt.string let iter_lines fpath ~f = @@ -50,53 +63,48 @@ struct | `Limit limit -> let rev_lines = ref [] in iter_lines fpath ~f:(fun l -> rev_lines := l :: !rev_lines); - let selected_lines = List.rev_head limit !rev_lines in + let selected_lines = List.rev_take limit !rev_lines in let omitted_count = List.length !rev_lines - List.length selected_lines in let display_lines = if omitted_count = 0 then selected_lines else - Fmt.str "... (omitting %i line%a)" omitted_count Pp.pp_plural + Fmt.str "... (omitting %i line%a)" omitted_count Pp.plural omitted_count :: selected_lines in ListLabels.iter display_lines ~f:(Fmt.pf ppf "%s@\n") - let log_dir { suite_name; uuid; root } = - (* We don't create symlinks on Windows. *) - let via_symlink = not Sys.win32 in - Filename.concat root (if via_symlink then suite_name else uuid) - - let output_fpath t tname = Filename.concat (log_dir t) (Test_name.file tname) - - let active_or_exn = function + let get_redirect_or_exn = function | Active t -> t | Inactive -> failwith "internal error: no log location" let pp_current_run_dir t ppf = - let t = active_or_exn t in + let t = get_redirect_or_exn t in pp_path ppf (log_dir t) - let pp_log_location t tname ppf = - let t = active_or_exn t in - let path = output_fpath t tname in + let pp_log_location t idx ppf = + let t = get_redirect_or_exn t in + let path = output_fpath t idx in pp_path ppf path - let recover_logs t ~tail tname = + let recover_logs t ~tail idx = match t with | Inactive -> None | Active t -> ( - let fpath = output_fpath t tname in + let fpath = output_fpath t idx in + (* Fmt.epr "Recovering logs from %s\n%!" filename; *) match Platform.file_exists fpath with | false -> None | true -> Some (fun ppf -> pp_tail tail fpath ppf)) - let with_captured_logs t tname f x = + let with_captured_logs t idx f x = match t with | Inactive -> f x | Active t -> - let fd = Platform.open_write_only (output_fpath t tname) in + Platform.mkdir_p (parent_fpath t idx); + let fd = Platform.open_write_only (output_fpath t idx) in let* () = Promise.return () in let+ a = Platform.with_redirect fd (fun () -> f x) in Platform.close fd; diff --git a/src/alcotest-engine/log_trap_intf.ml b/src/alcotest-engine/log_trap_intf.ml index 05e46b4d..cac928d1 100644 --- a/src/alcotest-engine/log_trap_intf.ml +++ b/src/alcotest-engine/log_trap_intf.ml @@ -3,17 +3,19 @@ open Model (** Running tests have their output hidden by default to avoid cluttering the Alcotest display with irrelevant output. However, we (usually) want to keep - the logs on disk so that we can re-display them if a test fails. Logs are - stored with the following structure: + the logs on disk so that we can re-display them if a test fails. + + Logs are stored with the following structure: {[ ├── E0965BF9/... ├── 6DDB68D5/ ;; ID for each test run │ │ - │ ├── alpha.000.output ;; ... containing files for individual tests - │ ├── alpha.001.output ;; with format ..output. - │ └── beta.000.output + │ ├── first_group/ ;; Directories for test groups + │ │ ├── test_foo.output ;; ... containing files for individual tests + │ │ └── test_bar.output + │ └── second_group/ │ ├── latest/ ;; Symlink to most recent UUID └── / ;; Symlink to most recent UUID @@ -26,13 +28,13 @@ module type S = sig val active : root:string -> uuid:string -> suite_name:string -> t val with_captured_logs : - t -> Test_name.t -> ('a -> 'b promise) -> 'a -> 'b promise + t -> Index.t -> ('a -> 'b promise) -> 'a -> 'b promise (** Capture all logs for a given test run. *) val recover_logs : t -> tail:[ `Unlimited | `Limit of int ] -> - Test_name.t -> + Index.t -> (Format.formatter -> unit) option (** Print the logs for a given test to the given formatter, if they exist. [tail] determines whether to show all lines in the captured log or just a @@ -42,7 +44,7 @@ module type S = sig (** Print the folder containing all captured traces for the current test run. Raises an exception if traces are not being recorded. *) - val pp_log_location : t -> Test_name.t -> Format.formatter -> unit + val pp_log_location : t -> Index.t -> Format.formatter -> unit (** Print the file containing the trace of a particular test. Raises an exception if traces are not being recorded. *) end @@ -51,7 +53,7 @@ module type Log_trap = sig module type S = S module Make - (Promise : Monad.EXTENDED) + (Promise : Monad.Extended) (Platform : Platform.S with type 'a promise := 'a Promise.t) : S with type 'a promise := 'a Promise.t end diff --git a/src/alcotest-engine/model.ml b/src/alcotest-engine/model.ml index de17cc2a..5e933c42 100644 --- a/src/alcotest-engine/model.ml +++ b/src/alcotest-engine/model.ml @@ -1,140 +1,214 @@ open! Import -type speed_level = [ `Quick | `Slow ] - -module Test_name : sig - type t - - val v : name:string -> index:int -> t - val name : t -> Safe_string.t - 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 : Safe_string.t; file : string; index : int } - - let index { index; _ } = index - - let v ~name ~index = - let name = Safe_string.v name in - let file = - let name = - match Safe_string.to_string name with "" -> "" | n -> n ^ "." - in - Fmt.str "%s%03d.output" name index +module Index = struct + type t = + | Root + | Child of { + parent_path : Safe_string.t list; + name : Safe_string.t; + last : bool; + } + + let root = Root + + let rcons t ~last ~name = + let parent_path = + match t with + | Root -> [] + | Child { parent_path; name; _ } -> parent_path @ [ name ] in - { name; file; index } + Child { parent_path; name; last } + + let last_in_parent = function Root -> false | Child { last; _ } -> last - let pp = Fmt.using (fun { name; _ } -> name) Safe_string.pp - let name { name; _ } = name - let file { file; _ } = file - let length = name >> Safe_string.length + let parent_path = function + | Root -> failwith "Root node has no parent" + | Child { parent_path; _ } -> parent_path - let compare t t' = - match Safe_string.compare t.name t'.name with - | 0 -> (compare : int -> int -> int) t.index t'.index - | n -> n + let leaf_name = function + | Root -> failwith "Root node has no label" + | Child { name; _ } -> name 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 + | Ok + | Exn of Index.t * string * unit Fmt.t + | Error of Index.t * unit Fmt.t + | Skip + | Todo of string -module Suite (M : Monad.S) : sig - type 'a t - type 'a test_fn = [ `Skip | `Run of 'a -> Run_result.t M.t ] + let is_failure = function + | Ok | Skip -> false + | Error _ | Exn _ | Todo _ -> true - type 'a test_case = { - name : Test_name.t; - speed_level : speed_level; - fn : 'a test_fn; - } + let has_run = function Ok | Error _ | Exn _ -> true | Skip | Todo _ -> false +end - val v : name:string -> (_ t, [> `Empty_name ]) result - (** Construct a new suite, given a non-empty [name]. Test cases must be added - with {!add}. *) +module Suite (M : Monad.S) = struct + module String_set = Set.Make (String) + module M = Monad.Extend (M) + open M.Syntax - val name : _ t -> string - (** An escaped form of the suite name. *) + type filter_result = [ `Run | `Skip ] + type filter = Tag.Set.t -> filter_result - val pp_name : _ t Fmt.t - (** Pretty-print the unescaped suite name. *) + type 'a test = { + label : Safe_string.t; + loc : Source_code_position.pos option; + tags : Tag.Set.t; + node : 'a test_node; + } - val add : - 'a t -> - Test_name.t * string * speed_level * 'a test_fn -> - ('a t, [ `Duplicate_test_path of string ]) result + and 'a test_node = Test of ('a -> unit M.t) | Group of 'a test list - 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) + let test ~name ~loc ~tags fn = + let label = Safe_string.v name in + { label; loc; tags; node = Test fn } - type 'a test_fn = [ `Skip | `Run of 'a -> Run_result.t M.t ] - - type 'a test_case = { - name : Test_name.t; - speed_level : speed_level; - fn : 'a test_fn; - } + let group ~name ~loc ~tags children = + let label = Safe_string.v name in + { label; loc; tags; node = Group children } type 'a t = { - name : Safe_string.t; - tests : 'a test_case list; - (* caches computed from the library values. *) - filepaths : String_set.t; - doc : (Test_name.t, string) Hashtbl.t; + identifier : Safe_string.t; + name : Safe_string.t option; + file : string option; + loc : Source_code_position.pos option; + tests : 'a test list; } - let v ~name = - match String.length name with - | 0 -> Error `Empty_name - | _ -> - let name = Safe_string.v name in - let tests = [] in - let filepaths = String_set.empty in - let doc = Hashtbl.create 0 in - Ok { name; tests; filepaths; doc } - - let name { name; _ } = Safe_string.to_string name - let pp_name ppf { name; _ } = Safe_string.pp ppf name - - 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 -> "" + let validate_tests = + let rec aux ctx ts = + let open Result.Syntax in + let* () = + let duplicate = + ts + |> List.map (fun { label; _ } -> label) + |> List.find_duplicate ~compare:Safe_string.compare + in + match duplicate with + | None -> Ok () + | Some dup -> + let path = + List.rev_map Safe_string.to_string (dup :: ctx) + |> String.concat ~sep:" › " + in + Error (`Duplicate_path path) + in + List.fold_result ts ~init:() ~f:(fun () -> function + | { node = Test _; _ } -> Ok () + | { node = Group children; label; _ } -> aux (label :: ctx) children) + in + fun ts -> aux [] ts + + let of_tests ~name ~file ~loc tests = + let open Result.Syntax in + let* () = validate_tests tests in + match (name, file) with + | Some x, _ when x = "" -> Error `Empty_name + | _, Some x when x = "" -> Error `Empty_file + | None, None -> Error `No_identifier + | Some identifier, _ | _, Some identifier -> + let identifier = Safe_string.v identifier in + let name = Option.map Safe_string.v name in + Ok { identifier; name; file; loc; tests } + + let rec list_fold_until ~f ~init:acc ~finish = function + | [] -> M.return (finish acc) + | [ x ] -> ( + f ~last:true acc x >|= function Stop c -> c | Continue c -> finish c) + | x :: (_ :: _ as xs) -> ( + f ~last:false acc x >>= function + | Stop c -> M.return c + | Continue acc -> list_fold_until ~f ~init:acc ~finish xs) + + let foldi_until ~filter ?group ?test ~init:acc ~finish t = + let fold_default _ acc _ = M.return (Continue acc) in + let group = Option.value group ~default:fold_default + and test = Option.value test ~default:fold_default in + let rec aux ~last ~index acc { label; tags; node; _ } = + let index = Index.rcons index ~last ~name:label in + let filter_result = filter tags in + match node with + | Test fn -> + let arg = + match filter_result with `Run -> `Run fn | `Skip -> `Skip + in + test index acc arg + | Group children -> ( + M.bind (group index acc filter_result) @@ function + | Stop _ as x -> M.return x + | Continue acc -> ( + match filter_result with + | `Skip -> M.return (Continue acc) + | `Run -> + list_fold_until children ~init:acc + ~f:(fun ~last acc child -> + aux ~last ~index acc child >|= function + | Continue _ as c -> c + | Stop _ as s -> Stop s) + ~finish:(fun x -> Continue x))) + in + list_fold_until t.tests ~init:acc ~finish ~f:(aux ~index:Index.root) + + let fold ~filter ~group ~test ~init t = + foldi_until t ~filter ~init + ~finish:(fun x -> x) + ~group:(fun _ a x -> M.return (Continue (group a x))) + ~test:(fun _ a x -> M.return (Continue (test a x))) + + let identifier { identifier; _ } = Safe_string.to_string identifier + let pp_name { name; _ } = Option.map (Fun.flip Safe_string.pp) name + let pp_file { file; _ } = Option.map (Fun.flip Fmt.string) file + + let pp_tags ppf ts = + let open Fmt in + string ppf "< "; + list ~sep:(const string "; ") Tag.pp ppf (Tag.Set.to_list ts); + Fmt.string ppf " >" + + let rec pp_nodes ~pre ~last_dir ppf files = + let open Fmt in + (* Only print newline at the end if our ancestors have not already done so + (i.e. we are not the descendant of a last directory *) + let pp_last_dir ppf last_dir = if not last_dir then pf ppf "@,%s" pre in + let pp_children_last ppf = + pf ppf "%s└─ %a" pre (pp_node ~last_dir:true ~pre:(pre ^ " ")) + and pp_children_not_last ppf = + pf ppf "%s├─ %a" pre (pp_node ~last_dir:false ~pre:(pre ^ "│ ")) + in + match files with + | [] -> () + | [ last ] -> pf ppf "@,%a%a" pp_children_last last pp_last_dir last_dir + | _ :: _ :: _ -> + let last, not_last = + match List.rev files with + | [] -> assert false + | last :: not_last_rev -> (last, List.rev not_last_rev) + in + pf ppf "@,%a@,%a%a" + (list ~sep:cut pp_children_not_last) + not_last pp_children_last last pp_last_dir last_dir + + and pp_node ~pre ~last_dir ppf = + let open Fmt in + let pp_group_name = styled `Bold (styled `Blue Safe_string.pp) in + function + | { node = Test _; label; tags; _ } -> + Fmt.pf ppf "%a %a" Safe_string.pp label + Fmt.(styled `Faint pp_tags) + tags + | { node = Group children; label; _ } -> + pp_group_name ppf label; + pp_nodes ~pre ~last_dir ppf children + + let pp ppf t = + pp_node ~pre:"" ~last_dir:false ppf + { + node = Group t.tests; + label = t.identifier; + loc = None; + tags = Tag.Set.empty; + } end diff --git a/src/alcotest-engine/model.mli b/src/alcotest-engine/model.mli new file mode 100644 index 00000000..67a61c7c --- /dev/null +++ b/src/alcotest-engine/model.mli @@ -0,0 +1,97 @@ +open! Import + +module Index : sig + type t + (** A value that uniquely identifies a particular test (or group of tests) in + a suite (expressed as a path of parents names followed by a name). *) + + val rcons : t -> last:bool -> name:Safe_string.t -> t + (** [rcons ~last ~name group] is the path to the child named [name] in + [group]. [last] specifies if the child [name] is the last sibling in + [group] (when ordered lexicographically). *) + + val last_in_parent : t -> bool + val parent_path : t -> Safe_string.t list + val leaf_name : t -> Safe_string.t +end + +module Run_result : sig + type t = + | Ok + | Exn of Index.t * string * unit Fmt.t + | Error of Index.t * unit Fmt.t + | Skip + | Todo of string + + val is_failure : t -> bool + (** [is_failure] holds for test results that are error states. *) + + val has_run : t -> bool +end + +module Suite (M : Monad.S) : sig + type 'a test + type filter_result = [ `Run | `Skip ] + type filter = Tag.Set.t -> filter_result + + val test : + name:string -> + loc:Source_code_position.pos option -> + tags:Tag.Set.t -> + ('a -> unit M.t) -> + 'a test + + val group : + name:string -> + loc:Source_code_position.pos option -> + tags:Tag.Set.t -> + 'a test list -> + 'a test + + type 'a t + + val foldi_until : + filter:filter -> + ?group: + (Index.t -> 'acc -> filter_result -> ('acc, 'final) continue_or_stop M.t) -> + ?test: + (Index.t -> + 'acc -> + [ `Run of 'a -> unit M.t | `Skip ] -> + ('acc, 'final) continue_or_stop M.t) -> + init:'acc -> + finish:('acc -> 'final) -> + 'a t -> + 'final M.t + (** Depth-first traversal over the suite, skipping nodes according to the + [filter] predicate defined over node {!Tag}s. *) + + val fold : + filter:filter -> + group:('acc -> filter_result -> 'acc) -> + test:('acc -> [ `Run of 'a -> unit M.t | `Skip ] -> 'acc) -> + init:'acc -> + 'a t -> + 'acc M.t + + val of_tests : + name:string option -> + file:string option -> + loc:Source_code_position.pos option -> + 'a test list -> + ( 'a t, + [ `Duplicate_path of string | `No_identifier | `Empty_name | `Empty_file ] + ) + result + + val identifier : _ t -> string + (** An escaped form of the suite name (or the file name, if [name = None]). *) + + val pp_name : _ t -> (Format.formatter -> unit) option + (** Pretty-print the unescaped suite name, if supplied. *) + + val pp_file : _ t -> (Format.formatter -> unit) option + (** Pretty-print the source file of the suite, if supplied. *) + + val pp : _ t Fmt.t +end diff --git a/src/alcotest-engine/monad.ml b/src/alcotest-engine/monad.ml index 4932ba88..5ea6ecd9 100644 --- a/src/alcotest-engine/monad.ml +++ b/src/alcotest-engine/monad.ml @@ -27,6 +27,8 @@ end module Extend (M : S) = struct include M + let map f x = M.bind x (fun y -> M.return (f y)) + module Syntax = struct let ( >>= ) = M.bind let ( >|= ) x f = x >>= fun y -> M.return (f y) diff --git a/src/alcotest-engine/monad_intf.ml b/src/alcotest-engine/monad_intf.ml index 433f7767..fa38adab 100644 --- a/src/alcotest-engine/monad_intf.ml +++ b/src/alcotest-engine/monad_intf.ml @@ -22,9 +22,11 @@ module type S = sig val catch : (unit -> 'a t) -> (exn -> 'a t) -> 'a t end -module type EXTENDED = sig +module type Extended = sig include S + val map : ('a -> 'b) -> 'a t -> 'b t + module Syntax : sig val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t @@ -40,8 +42,8 @@ end module type Monad = sig module type S = S - module type EXTENDED = EXTENDED + module type Extended = Extended module Identity : S with type 'a t = 'a - module Extend (M : S) : EXTENDED with type 'a t = 'a M.t + module Extend (M : S) : Extended with type 'a t = 'a M.t end diff --git a/src/alcotest-engine/platform.ml b/src/alcotest-engine/platform.ml index 70c28496..4de21ab8 100644 --- a/src/alcotest-engine/platform.ml +++ b/src/alcotest-engine/platform.ml @@ -1,6 +1,9 @@ open! Import module type S = sig + val name : string + (** The name of the platform, for use in user messages. *) + val time : unit -> float (** [time ()] returns the current timestamp, used to measure the duration of a testrun. *) @@ -39,6 +42,7 @@ module type S = sig val file_exists : string -> bool val open_write_only : string -> file_descriptor + val mkdir_p : string -> unit val close : file_descriptor -> unit val with_redirect : file_descriptor -> (unit -> 'a promise) -> 'a promise @@ -49,4 +53,4 @@ module type S = sig (** [home_directory ()] is the current user's HOME directory, if it exists. *) end -module type MAKER = functor (M : Monad.S) -> S with type 'a promise := 'a M.t +module type MAKER = functor (M : Monad.S) -> S with type 'a promise = 'a M.t diff --git a/src/alcotest-engine/pp.ml b/src/alcotest-engine/pp.ml index 372e9470..fde90d76 100644 --- a/src/alcotest-engine/pp.ml +++ b/src/alcotest-engine/pp.ml @@ -21,50 +21,73 @@ open! Import open Model let map_theta t ~f ppf = f (fun ppf () -> t ppf) ppf () -let pp_plural ppf x = Fmt.pf ppf (if x < 2 then "" else "s") - -let colour_of_tag = function - | `Ok -> `Green - | `Fail -> `Red - | `Skip | `Todo | `Assert -> `Yellow - -let string_of_tag = function - | `Ok -> "OK" - | `Fail -> "FAIL" - | `Skip -> "SKIP" - | `Todo -> "TODO" - | `Assert -> "ASSERT" - -let pp_tag ~wrapped ppf typ = - let colour = colour_of_tag typ in - let tag = string_of_tag typ in - let tag = if wrapped then "[" ^ tag ^ "]" else tag in - Fmt.(styled colour string) ppf tag - -let tag = pp_tag ~wrapped:false - -module Make (P : sig - val stdout_columns : unit -> int option -end) = -struct +let quoted f = Fmt.(const char '`' ++ f ++ const char '\'') +let plural ppf x = Fmt.pf ppf (if x < 2 then "" else "s") + +module Tag : sig + val pp : wrapped:bool -> tag Fmt.t + val colour : tag -> Fmt.style + val of_run_result : Run_result.t -> tag +end = struct + let colour = function + | `Ok -> `Green + | `Fail -> `Red + | `Skip | `Todo | `Assert -> `Yellow + + let string_of_tag : tag -> string = function + | `Ok -> "PASS" + | `Fail -> "FAIL" + | `Skip -> "SKIP" + | `Todo -> " —— " + | `Assert -> "ASSERT" + + let of_run_result : Run_result.t -> tag = function + | Ok -> `Ok + | Exn _ | Error _ -> `Fail + | Skip -> `Skip + | Todo _ -> `Todo + + let pp ~wrapped ppf typ = + let colour = colour typ in + let tag = string_of_tag typ in + let tag = if wrapped then "[" ^ tag ^ "]" else tag in + Fmt.(styled colour string) ppf tag +end + +let left_padding ~with_selector = + let open Fmt in + (if with_selector then const (styled `Bold (styled `Red char)) '>' + else const char ' ') + ++ const char ' ' + +let user_error fmt = + Fmt.kstr + (fun s -> + Fmt.epr "%a: %s@." Fmt.(styled `Red string) "ERROR" s; + exit 1) + fmt + +let tag = Tag.pp ~wrapped:false + +module Make (P : Make_arg) = struct include Types let terminal_width = lazy (match P.stdout_columns () with Some w -> w | None -> 80) - let rresult_error ppf = function - | `Error (_, e) -> Fmt.pf ppf "%a@," e () - | `Exn (_, n, e) -> Fmt.pf ppf "[%s] @[%a@]" n e () - | `Ok | `Todo _ | `Skip -> () + let rresult_error : Run_result.t Fmt.t = + fun ppf -> function + | Error (_, e) -> Fmt.pf ppf "%a@," e () + | Exn (_, n, e) -> Fmt.pf ppf "[%s] @[%a@]" n e () + | Ok | Todo _ | Skip -> () (* Colours *) let color c ppf fmt = Fmt.(styled c string) ppf fmt let red_s fmt = color `Red fmt let red ppf fmt = Fmt.kstr (fun str -> red_s ppf str) fmt let green_s fmt = color `Green fmt - let yellow_s fmt = color `Yellow fmt let left_gutter = 2 - let left_tag = 14 + let left_tag = 10 let left_total = left_gutter + left_tag let left nb pp ppf a = @@ -75,101 +98,103 @@ struct pp ppf a; Fmt.string ppf (String.v ~len:nb (fun _ -> ' '))) - let pp_test_name ~max_label ppf tname = - let name_len = Test_name.length tname in - let index = Test_name.index tname in - let padding = - match max_label + 8 - name_len with - | n when n <= 0 -> "" - | n -> String.v ~len:n (fun _ -> ' ') + let info ?(available_width = Lazy.force terminal_width) ppf index = + let name = Index.leaf_name index in + let sep = Fmt.(const (styled `Faint string) " › ") in + let sep_length = 3 in + let rec aux available_width = function + | [] -> + if available_width < Safe_string.length name then ( + Safe_string.pp ppf + (Safe_string.prefix (available_width - sep_length) name); + Fmt.string ppf "...") + else Safe_string.pp ppf name + | x :: xs -> + let len = Safe_string.length x in + if available_width < len then ( + Safe_string.pp ppf (Safe_string.prefix (available_width - 3) x); + Fmt.string ppf "...") + else ( + Fmt.styled `Faint Safe_string.pp ppf x; + sep ppf (); + aux (available_width - len - sep_length) xs) in - Fmt.pf ppf "%a%s%3d" Fmt.(styled `Cyan Test_name.pp) tname padding index - - let info ?(available_width = Lazy.force terminal_width) ~max_label - ~doc_of_test_name ppf tname = - let pp_test_name ppf = Fmt.pf ppf "%a " (pp_test_name ~max_label) tname in - let test_doc = - let test_doc = doc_of_test_name tname in - let available_width = - pp_test_name Format.str_formatter; - let used_width = String.length_utf8 (Format.flush_str_formatter ()) in - available_width - used_width - in - if String.length_utf8 test_doc <= available_width then test_doc - else String.prefix_utf8 (available_width - 3) test_doc ^ "..." - in - Fmt.pf ppf "%t%s" pp_test_name test_doc - - let tag_of_result = function - | `Ok -> `Ok - | `Exn _ | `Error _ -> `Fail - | `Skip -> `Skip - | `Todo _ -> `Todo + aux available_width (Index.parent_path index) let pp_result ppf result = - let tag = tag_of_result result in - left left_tag (pp_tag ~wrapped:true) ppf tag + let tag = Tag.of_run_result result in + left left_tag (Tag.pp ~wrapped:true) ppf tag - let pp_result_compact ppf result = - let colour = result |> tag_of_result |> colour_of_tag in + let pp_result_compact : Run_result.t Fmt.t = + fun ppf result -> + let colour = result |> Tag.of_run_result |> Tag.colour in let char = match result with - | `Ok -> '.' - | `Exn _ | `Error _ -> 'F' - | `Skip -> 'S' - | `Todo _ -> 'T' + | Ok -> '.' + | Exn _ | Error _ -> 'F' + | Skip -> 'S' + | Todo _ -> 'T' in Fmt.(styled colour char) ppf char - let left_padding ~with_selector = - let open Fmt in - (if with_selector then const (styled `Bold (styled `Red char)) '>' - else const char ' ') - ++ const char ' ' - - let pp_result_full ~max_label ~doc_of_test_name ~selector_on_failure ppf - (path, result) = + let pp_result_full ~selector_on_failure ppf (path, result) = let with_selector = selector_on_failure && Run_result.is_failure result in + let available_width = Lazy.force terminal_width - left_total in (left_padding ~with_selector) ppf (); pp_result ppf result; - let available_width = Lazy.force terminal_width - left_total in - (info ~available_width ~max_label ~doc_of_test_name) ppf path; - () + info ~available_width ppf path - let event_line ~margins ~max_label ~doc_of_test_name ppf = function - | `Result (p, r) -> + let event_line ppf = function + | { index; type_ = Result r } -> pp_result ppf r; - (info - ~available_width:(Lazy.force terminal_width - margins - left_total) - ~max_label ~doc_of_test_name) - ppf p + info ppf index | _ -> assert false - let event ~isatty ~compact ~max_label ~doc_of_test_name ~selector_on_failure - ~tests_so_far ppf event = - match (compact, isatty, event) with - | true, _, `Start _ | _, false, `Start _ -> () - | false, true, `Start tname -> - Fmt.( - left_padding ~with_selector:false - ++ const (left left_tag yellow_s) "..." - ++ const - (info - ~available_width:(Lazy.force terminal_width - left_total) - ~max_label ~doc_of_test_name) - tname) - ppf () - | true, _, `Result (_, r) -> - pp_result_compact ppf r; - (* Wrap compact output to terminal width manually *) - if (tests_so_far + 1) mod Lazy.force terminal_width = 0 then - Format.pp_force_newline ppf (); - () - | false, _, `Result (tname, r) -> - if isatty then Fmt.pf ppf "\r"; - Fmt.pf ppf "%a@," - (pp_result_full ~max_label ~doc_of_test_name ~selector_on_failure) - (tname, r) + module Progress_reporter = struct + type t = { + ppf : Format.formatter; + isatty : bool; + compact : bool; + selector_on_first_failure : bool; + mutable prior_failure : bool; + mutable tests_so_far : int; + } + + let create ~ppf ~isatty ~compact ~selector_on_first_failure = + { + ppf; + isatty; + compact; + selector_on_first_failure; + prior_failure = false; + tests_so_far = 0; + } + + let event t event = + match (t.compact, t.isatty, event.type_) with + | true, _, Start | _, false, Start -> () + | false, true, Start -> + left_padding ~with_selector:false t.ppf (); + (left left_tag (Tag.pp ~wrapped:true)) t.ppf `Todo; + info + ~available_width:(Lazy.force terminal_width - left_total) + t.ppf event.index + | true, _, Result r -> + t.tests_so_far <- succ t.tests_so_far; + pp_result_compact t.ppf r; + (* Wrap compact output to terminal width manually *) + if t.tests_so_far mod Lazy.force terminal_width = 0 then + Format.pp_force_newline t.ppf (); + () + | false, _, Result r -> + if t.isatty then Fmt.char t.ppf '\r'; + pp_result_full + ~selector_on_failure: + (t.selector_on_first_failure && not t.prior_failure) + t.ppf (event.index, r); + if Run_result.is_failure r then t.prior_failure <- true; + Format.pp_force_newline t.ppf () + end let pp_suite_errors ~show_all = function | [] -> Fmt.nop @@ -211,10 +236,10 @@ struct let pp_summary ppf r = let pp_failures ppf = function | 0 -> green_s ppf "Test Successful" - | n -> red ppf "%d failure%a!" n pp_plural n + | n -> red ppf "%d failure%a!" n plural n in Fmt.pf ppf "%a in %.3fs. %d test%a run.@," pp_failures r.failures r.time - r.success pp_plural r.success + r.success plural r.success let suite_results ~log_dir cfg ppf r = let print_summary = (not cfg#compact) || r.failures > 0 in @@ -238,8 +263,4 @@ struct if not cfg#verbose then pp_full_logs ppf log_dir; pp_summary ppf r); Format.pp_close_box ppf () - - let user_error msg = - Fmt.epr "%a: %s\n" Fmt.(styled `Red string) "ERROR" msg; - exit 1 end diff --git a/src/alcotest-engine/pp_intf.ml b/src/alcotest-engine/pp_intf.ml index 0496721c..542bc7e7 100644 --- a/src/alcotest-engine/pp_intf.ml +++ b/src/alcotest-engine/pp_intf.ml @@ -15,13 +15,15 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) +open! Import open Model type theta = Format.formatter -> unit (** Type corresponding to a [%t] placeholder. *) module Types = struct - type event = [ `Result of Test_name.t * Run_result.t | `Start of Test_name.t ] + type event_type = Start | Result of Run_result.t + type event = { index : Index.t; type_ : event_type } type result = { success : int; @@ -29,6 +31,8 @@ module Types = struct time : float; errors : unit Fmt.t list; } + + type tag = [ `Ok | `Fail | `Skip | `Todo | `Assert ] end module type Make_arg = sig @@ -38,37 +42,28 @@ end module type S = sig include module type of Types - val info : - ?available_width:int -> - max_label:int -> - doc_of_test_name:(Test_name.t -> string) -> - Test_name.t Fmt.t - + val info : ?available_width:int -> Index.t Fmt.t val rresult_error : Run_result.t Fmt.t + val event_line : event Fmt.t + + module Progress_reporter : sig + type t + + val create : + ppf:Format.formatter -> + isatty:bool -> + compact:bool -> + selector_on_first_failure:bool -> + t - val event_line : - margins:int -> - max_label:int -> - doc_of_test_name:(Test_name.t -> string) -> - [ `Result of Test_name.t * [< Run_result.t ] | `Start of Test_name.t ] Fmt.t - - val event : - isatty:bool -> - compact:bool -> - max_label:int -> - doc_of_test_name:(Test_name.t -> string) -> - selector_on_failure:bool -> - tests_so_far:int -> - event Fmt.t + val event : t -> event -> unit + end val suite_results : log_dir:theta -> < verbose : bool ; show_errors : bool ; json : bool ; compact : bool ; .. > -> result Fmt.t - val quoted : 'a Fmt.t -> 'a Fmt.t - (** Wraps a formatter with `GNU-style quotation marks'. *) - val with_surrounding_box : 'a Fmt.t -> 'a Fmt.t (** Wraps a formatter with a Unicode box with width given by {!X.stdout_columns}. Uses box-drawing characters from code page 437. *) @@ -76,16 +71,21 @@ module type S = sig val horizontal_rule : _ Fmt.t (** Horizontal rule of length {!X.stdout_columns}. Uses box-drawing characters from code page 437. *) - - val user_error : string -> _ - (** Raise a user error, then fail. *) end module type Pp = sig - val tag : [ `Ok | `Fail | `Skip | `Todo | `Assert ] Fmt.t + include module type of Types + + val tag : tag Fmt.t + val map_theta : theta -> f:(unit Fmt.t -> unit Fmt.t) -> theta + (** Transform a stateless pretty-printer (for use with the [%t] conversion + spec) at the level of [unit Fmt.t] values. *) - val pp_plural : int Fmt.t + val quoted : 'a Fmt.t -> 'a Fmt.t + (** Wraps a formatter with GNU-style `quotation marks'. *) + + val plural : int Fmt.t (** This is for adding an 's' to words that should be pluralized, e.g. {[ @@ -93,5 +93,8 @@ module type Pp = sig Fmt.pr "Found %i item%a." n pp_plural n ]} *) + val user_error : ('a, Format.formatter, unit, _) format4 -> 'a + (** Raise a user error, then fail. *) + module Make (X : Make_arg) : S end diff --git a/src/alcotest-engine/safe_string.ml b/src/alcotest-engine/safe_string.ml index 6003554c..97d169e9 100644 --- a/src/alcotest-engine/safe_string.ml +++ b/src/alcotest-engine/safe_string.ml @@ -25,8 +25,8 @@ type t = { raw : string; escaped : string } let v raw = { raw; escaped = escape raw } let to_string { escaped; _ } = escaped -let to_unescaped_string { raw; _ } = raw let pp = Fmt.using (fun { raw; _ } -> raw) Fmt.string let length { raw; _ } = String.length_utf8 raw +let prefix n { raw; _ } = v (String.prefix_utf8 n raw) let equal t t' = String.equal t.escaped t'.escaped let compare t t' = String.compare t.escaped t'.escaped diff --git a/src/alcotest-engine/safe_string.mli b/src/alcotest-engine/safe_string.mli index d4693644..0db0dbec 100644 --- a/src/alcotest-engine/safe_string.mli +++ b/src/alcotest-engine/safe_string.mli @@ -7,14 +7,14 @@ val v : string -> t val to_string : t -> string (** Get the escaped form of the given {!Safe_string}. *) -val to_unescaped_string : t -> string -(** Get the raw, unescaped string initially passed to [v]. *) - val pp : t Fmt.t (** Pretty-print the unescaped string. *) val length : t -> int (** The approximate number of terminal columns consumed by [pp_name]. *) +val prefix : int -> t -> t +(** Truncate a string to a given number of terminal columns. *) + val equal : t -> t -> bool val compare : t -> t -> int diff --git a/src/alcotest-engine/tag.ml b/src/alcotest-engine/tag.ml new file mode 100644 index 00000000..c2e28d71 --- /dev/null +++ b/src/alcotest-engine/tag.ml @@ -0,0 +1,111 @@ +open! Import + +(** A universal type, into which any other type can be injected and (partially) + recovered. *) +module Univ : sig + type t + + val create : unit -> ('a -> t) * (t -> 'a option) +end = struct + type t = .. + + let create : type a. unit -> (a -> t) * (t -> a option) = + fun () -> + let module M = struct + type t += Case of a option + end in + ((fun x -> M.Case (Some x)), function M.Case x -> x | _ -> None) +end + +module Key = struct + type 'a t = { + type_id : 'a Type_id.t; + id : int; + inj : 'a -> Univ.t; + prj : Univ.t -> 'a option; + name : string; + pp_data : Format.formatter -> 'a -> unit; + } + + let gen_id = + let counter = ref (-1) in + fun () -> + incr counter; + !counter + + let create ~name ~pp_data = + let inj, prj = Univ.create () in + { type_id = Type_id.create (); id = gen_id (); inj; prj; name; pp_data } + + let pp ppf spec = Format.fprintf ppf "(spec %s)" spec.name + + type 'a key = 'a t + + module Boxed = struct + type t = V : 'a key -> t [@@ocaml.unboxed] + + let compare (V x) (V y) = Int.compare x.id y.id + end +end + +type t = V : 'a Key.t * 'a -> t + +let v k v = V (k, v) +let pp ppf (V ({ name; pp_data; _ }, v)) = Fmt.pf ppf "%s = %a" name pp_data v +let const ~name v = V (Key.create ~name ~pp_data:Fmt.(const string name), v) + +module Set = struct + (** A tag set is a map from (boxed) definitions to tags. *) + + module Map = Map.Make (Key.Boxed) + + type nonrec t = t Map.t + + let empty = Map.empty + let add (V (k, _) as v) s = Map.add (V k) v s + + let find : type a. a Key.t -> t -> a option = + fun k t -> + try + let (V (k', v)) = Map.find (V k) t in + match Type_id.equal k.type_id k'.type_id with + | Some Type_id.Refl -> Some v + | None -> assert false + with Not_found -> None + + let to_list = Map.bindings >> List.map snd + + let fold_until = + let rec aux ~f ~finish acc = function + | [] -> finish acc + | (_k, v) :: xs -> ( + match f acc v with + | Continue acc -> aux ~f ~finish acc xs + | Stop final -> final) + in + fun t ~init ~f ~finish -> aux ~f ~finish init (Map.bindings t) +end + +module Speed_level = struct + type t = [ `Quick | `Slow ] + + let tag = + Key.create ~name:"speed_level" + ~pp_data: + (Fmt.of_to_string (function `Quick -> "Quick" | `Slow -> "Slow")) + + let quick = V (tag, `Quick) + let slow = V (tag, `Slow) +end + +module Predicate = struct + type t = unit -> [ `Run | `Skip ] + + let tag = + Key.create ~name:"Predicate" ~pp_data:(fun ppf _ -> + Fmt.pf ppf "Predicate <...>") +end + +module Position = struct + let tag = Key.create ~name:"index" ~pp_data:Fmt.(using snd int) +end diff --git a/src/alcotest-engine/tag.mli b/src/alcotest-engine/tag.mli new file mode 100644 index 00000000..ca7b49a3 --- /dev/null +++ b/src/alcotest-engine/tag.mli @@ -0,0 +1,52 @@ +open! Import + +module Key : sig + type 'a t + + val create : name:string -> pp_data:(Format.formatter -> 'a -> unit) -> 'a t + val pp : _ t Fmt.t +end + +type t + +val v : 'a Key.t -> 'a -> t +val pp : t Fmt.t +val const : name:string -> 'a -> t + +module Set : sig + type tag + type t + + val empty : t + val add : tag -> t -> t + val find : 'a Key.t -> t -> 'a option + val to_list : t -> tag list + + val fold_until : + t -> + init:'a -> + f:('a -> tag -> ('a, 'b) continue_or_stop) -> + finish:('a -> 'b) -> + 'b +end +with type tag := t + +module Speed_level : sig + type tag + type t = [ `Quick | `Slow ] + + val tag : t Key.t + val quick : tag + val slow : tag +end +with type tag := t + +module Position : sig + val tag : (string * int) Key.t +end + +module Predicate : sig + type t = unit -> [ `Run | `Skip ] + + val tag : t Key.t +end diff --git a/src/alcotest-engine/test.mli b/src/alcotest-engine/test.mli index 4b08f736..8c2d5738 100644 --- a/src/alcotest-engine/test.mli +++ b/src/alcotest-engine/test.mli @@ -14,10 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) -(** {1 Testable values} - - The following combinators represent types that can be used with the {!check} - functions below. *) +open! Import (** [TESTABLE] provides an abstract description for testable values. *) module type TESTABLE = sig @@ -112,17 +109,6 @@ val neg : 'a testable -> 'a testable Functions for asserting various properties within unit-tests. A failing assertion will cause the testcase to fail immediately. *) -module Source_code_position : sig - type here = Lexing.position - (** Location information passed via a [~here] argument, intended for use with - a PPX such as {{:https://github.com/janestreet/ppx_here} [ppx_here]}. *) - - type pos = string * int * int * int - (** Location information passed via a [~pos] argument, intended for use with - the [__POS__] macro provided by the standard library. See the - documentation of [__POS__] for more information. *) -end - type 'a extra_info = ?here:Source_code_position.here -> ?pos:Source_code_position.pos -> 'a (** The assertion functions optionally take information about the {i location} diff --git a/src/alcotest-lwt/alcotest_lwt.ml b/src/alcotest-lwt/alcotest_lwt.ml index 886d5c70..9175b663 100644 --- a/src/alcotest-lwt/alcotest_lwt.ml +++ b/src/alcotest-lwt/alcotest_lwt.ml @@ -31,4 +31,16 @@ module V1 = struct let test_case n s f = test_case n s (run_test f) end +module Unstable = struct + module Tester = + Alcotest_engine.Unstable.Cli.Make (Alcotest.Unix_platform) (Lwt) + + include Tester + + let test_sync ?pos ?tags ~name f = + test ?pos ?tags ~name (fun x -> Lwt.return (f x)) + + let test ?pos ?tags ~name f = test ?pos ?tags ~name (run_test f) +end + include V1 diff --git a/src/alcotest-lwt/alcotest_lwt_intf.ml b/src/alcotest-lwt/alcotest_lwt_intf.ml index 0743d8ef..e9867253 100644 --- a/src/alcotest-lwt/alcotest_lwt_intf.ml +++ b/src/alcotest-lwt/alcotest_lwt_intf.ml @@ -19,4 +19,18 @@ module type Alcotest_lwt = sig module V1 : V1 (** An alias of the above API that provides a stability guarantees over major version changes. *) + + module Unstable : sig + open Alcotest_engine.Unstable + + include + Core.S + with type 'a m := 'a Lwt.t + and type 'a test_args := Lwt_switch.t -> 'a + and type config := Config.User.t + and type source_code_position := Source_code_position.pos + and type tag_set := Tag.Set.t + + val test_sync : (('a -> unit) -> 'a test) Core.identified + end end diff --git a/src/alcotest-lwt/dune b/src/alcotest-lwt/dune index a917a1e3..223956ff 100644 --- a/src/alcotest-lwt/dune +++ b/src/alcotest-lwt/dune @@ -5,7 +5,8 @@ logs lwt fmt - alcotest.engine alcotest + alcotest.engine + alcotest.stdlib_ext (re_export lwt.unix) ;; Unused dependency added for convenience to consumers )) diff --git a/src/alcotest-mirage/alcotest_mirage.ml b/src/alcotest-mirage/alcotest_mirage.ml index 3d14f797..63337ca1 100644 --- a/src/alcotest-mirage/alcotest_mirage.ml +++ b/src/alcotest-mirage/alcotest_mirage.ml @@ -1,38 +1,52 @@ -module Make (C : Mirage_clock.MCLOCK) = struct - module Platform (M : Alcotest_engine.Monad.S) = struct - let time () = Duration.to_f @@ C.elapsed_ns () - let getcwd () = "" - let stdout_isatty () = true - let stdout_columns () = None - let setup_std_outputs ?style_renderer:_ ?utf_8:_ () = () - - (* Pre-4.07 doesn't support empty variant types. *) - type file_descriptor = { empty : 'a. 'a } - - let log_trap_supported = false - let prepare_log_trap ~root:_ = assert false - let file_exists _ = assert false - let open_write_only _ = assert false - let close = function (fd : file_descriptor) -> fd.empty - let with_redirect = function (fd : file_descriptor) -> fd.empty - - let home_directory () = - Error (`Msg "Home directory not available for the MirageOS platform") - end +module Platform (C : Mirage_clock.MCLOCK) (M : Alcotest_engine.Monad.S) = struct + let name = "Mirage" + let time () = Duration.to_f @@ C.elapsed_ns () + let getcwd () = "" + let stdout_isatty () = true + let stdout_columns () = None + let setup_std_outputs ?style_renderer:_ ?utf_8:_ () = () + + (* Pre-4.07 doesn't support empty variant types. *) + type 'a promise = 'a M.t + type file_descriptor = { empty : 'a. 'a } + + let log_trap_supported = false + let prepare_log_trap ~root:_ = assert false + let file_exists _ = assert false + let open_write_only _ = assert false + let close = function (fd : file_descriptor) -> fd.empty + let with_redirect = function (fd : file_descriptor) -> fd.empty + let mkdir_p _ = assert false + + let home_directory () = + Error (`Msg "Home directory not available for the MirageOS platform") +end + +let run_test fn args = + let async_ex, async_waker = Lwt.wait () in + let handle_exn ex = + Logs.debug (fun f -> f "Uncaught async exception: %a" Fmt.exn ex); + if Lwt.state async_ex = Lwt.Sleep then Lwt.wakeup_exn async_waker ex + in + Lwt.async_exception_hook := handle_exn; + Lwt_switch.with_switch (fun sw -> Lwt.pick [ fn sw args; async_ex ]) - module Tester = Alcotest_engine.V1.Cli.Make (Platform) (Lwt) +module Make (C : Mirage_clock.MCLOCK) = struct + module Tester = Alcotest_engine.V1.Cli.Make (Platform (C)) (Lwt) include Tester let test_case_sync n s f = test_case n s (fun x -> Lwt.return (f x)) + let test_case n s f = test_case n s (run_test f) +end - let run_test fn args = - let async_ex, async_waker = Lwt.wait () in - let handle_exn ex = - Logs.debug (fun f -> f "Uncaught async exception: %a" Fmt.exn ex); - if Lwt.state async_ex = Lwt.Sleep then Lwt.wakeup_exn async_waker ex - in - Lwt.async_exception_hook := handle_exn; - Lwt_switch.with_switch (fun sw -> Lwt.pick [ fn sw args; async_ex ]) +module Unstable = struct + module Make (C : Mirage_clock.MCLOCK) = struct + module Tester = Alcotest_engine.Unstable.Cli.Make (Platform (C)) (Lwt) + include Tester - let test_case n s f = test_case n s (run_test f) + let test_sync ?pos ?tags ~name fn = + test ~name ?pos ?tags (fun x -> Lwt.return (fn x)) + + let test ?pos ?tags ~name fn = test ~name ?pos ?tags (run_test fn) + end end diff --git a/src/alcotest-mirage/alcotest_mirage.mli b/src/alcotest-mirage/alcotest_mirage.mli index f81fdd27..e0162b97 100644 --- a/src/alcotest-mirage/alcotest_mirage.mli +++ b/src/alcotest-mirage/alcotest_mirage.mli @@ -30,3 +30,20 @@ module Make (C : Mirage_clock.MCLOCK) : sig val test_case_sync : string -> speed_level -> ('a -> unit) -> 'a test_case end + +module Unstable : sig + open Alcotest_engine.Unstable + + module Make (C : Mirage_clock.MCLOCK) : sig + include + Core.S + with type 'a m := 'a Lwt.t + and type 'a test_args := Lwt_switch.t -> 'a + and type config := Config.User.t + and type source_code_position := Source_code_position.pos + and type tag_set := Tag.Set.t + + val test_sync : + (('a -> unit) -> 'a test) Alcotest_engine.Unstable.Core.identified + end +end diff --git a/src/alcotest-mirage/dune b/src/alcotest-mirage/dune index 06904351..7b11ab84 100644 --- a/src/alcotest-mirage/dune +++ b/src/alcotest-mirage/dune @@ -1,4 +1,11 @@ (library (public_name alcotest-mirage) (name alcotest_mirage) - (libraries alcotest.engine duration mirage-clock logs fmt lwt)) + (libraries + alcotest.engine + alcotest.stdlib_ext + duration + mirage-clock + logs + fmt + lwt)) diff --git a/src/alcotest-stdlib-ext/alcotest_stdlib_ext.ml b/src/alcotest-stdlib-ext/alcotest_stdlib_ext.ml index 6232b349..1bd6dba9 100644 --- a/src/alcotest-stdlib-ext/alcotest_stdlib_ext.ml +++ b/src/alcotest-stdlib-ext/alcotest_stdlib_ext.ml @@ -1,15 +1,21 @@ let ( >> ) f g x = x |> f |> g +type ('a, 'b) continue_or_stop = Continue of 'a | Stop of 'b + module Fun = struct let id x = x + let flip f b a = f a b end module Int = struct - module Set = Set.Make (struct + module T = struct type t = int let compare = (compare : int -> int -> int) - end) + end + + include T + module Set = Set.Make (T) end module String = struct @@ -36,7 +42,7 @@ module List = struct type 'a t = 'a list - let rev_head n l = + let rev_take n l = let rec aux acc n l = match l with | x :: xs -> @@ -74,13 +80,35 @@ module List = struct | Error e, Ok _ -> Error [ e ]) l (Ok []) + let fold_result t ~init ~f = + let rec aux acc = function + | [] -> Ok acc + | x :: xs -> ( match f acc x with Ok x -> aux x xs | Error _ as e -> e) + in + aux init t + let init n f = let rec aux acc i = if i >= n then rev acc else aux (f i :: acc) (i + 1) in aux [] 0 + + let find_duplicate ~compare l = + let sorted = sort compare l in + let rec aux l = + match l with + | [] | [ _ ] -> None + | hd1 :: (hd2 :: _ as tl) -> + if compare hd1 hd2 = 0 then Some hd1 else aux tl + in + aux sorted end module Result = struct let map f = function Ok x -> Ok (f x) | Error e -> Error e + + module Syntax = struct + let ( let+ ) x f = map f x + let ( let* ) x f = match x with Error _ as e -> e | Ok x -> f x + end end module Option = struct @@ -92,6 +120,7 @@ module Option = struct | None -> invalid_arg "Option.get_exn: None" let value ~default = function None -> default | Some x -> x + let value_lazy ~default = function None -> Lazy.force default | Some x -> x let ( || ) a b = match (a, b) with @@ -99,6 +128,38 @@ module Option = struct | (Some _ as x), _ | None, (Some _ as x) -> x end +module Type_id = struct + type (_, _) equal = Refl : ('a, 'a) equal + type _ equality = .. + + module type Inst = sig + type t + type _ equality += Eq : t equality + end + + type 'a t = (module Inst with type t = 'a) + + let create : type a. unit -> a t = + fun () -> + let module Inst = struct + type t = a + type _ equality += Eq : t equality + end in + (module Inst) + + let equal : type a b. a t -> b t -> (a, b) equal option = + fun (module A) (module B) -> match A.Eq with B.Eq -> Some Refl | _ -> None + + let cast : type a b. a t -> b t -> a -> b option = + fun awit bwit a -> + match equal awit bwit with Some Refl -> Some a | None -> None +end + +module Source_code_position = struct + type here = Lexing.position + type pos = string * int * int * int +end + module Cmdliner_syntax = struct open Cmdliner diff --git a/src/alcotest-stdlib-ext/alcotest_stdlib_ext.mli b/src/alcotest-stdlib-ext/alcotest_stdlib_ext.mli index 64084fa8..b3f16e82 100644 --- a/src/alcotest-stdlib-ext/alcotest_stdlib_ext.mli +++ b/src/alcotest-stdlib-ext/alcotest_stdlib_ext.mli @@ -3,12 +3,19 @@ val ( >> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c +type ('a, 'b) continue_or_stop = Continue of 'a | Stop of 'b + module Fun : sig val id : 'a -> 'a + val flip : ('a -> 'b -> 'c) -> 'b -> 'a -> 'c end module Int : sig - module Set : Set.S with type elt = int + type t = int + + val compare : t -> t -> int + + module Set : Set.S with type elt = t end module String : sig @@ -27,17 +34,30 @@ module List : sig type 'a t = 'a list - val rev_head : int -> 'a list -> 'a list + val rev_take : int -> 'a list -> 'a list (** Reverse a list, taking at most the first n elements of the original list. *) val concat_map : ('a -> 'b list) -> 'a list -> 'b list val filter_map : ('a -> 'b option) -> 'a list -> 'b list val lift_result : ('a, 'b) result t -> ('a t, 'b t) result + + val fold_result : + 'a t -> + init:'acc -> + f:('acc -> 'a -> ('acc, 'e) result) -> + ('acc, 'e) result + + val find_duplicate : compare:('a -> 'a -> int) -> 'a t -> 'a option val init : int -> (int -> 'a) -> 'a list end module Result : sig val map : ('a -> 'b) -> ('a, 'e) result -> ('b, 'e) result + + module Syntax : sig + val ( let+ ) : ('a, 'e) result -> ('a -> 'b) -> ('b, 'e) result + val ( let* ) : ('a, 'e) result -> ('a -> ('b, 'e) result) -> ('b, 'e) result + end end module Option : sig @@ -45,9 +65,24 @@ module Option : sig val is_some : _ option -> bool val get_exn : 'a option -> 'a val value : default:'a -> 'a option -> 'a + val value_lazy : default:'a Lazy.t -> 'a option -> 'a val ( || ) : 'a option -> 'a option -> 'a option end +module Type_id : sig + type 'a t + type (_, _) equal = Refl : ('a, 'a) equal + + val create : unit -> 'a t + val equal : 'a t -> 'b t -> ('a, 'b) equal option + val cast : 'a t -> 'b t -> 'a -> 'b option +end + +module Source_code_position : sig + type here = Lexing.position + type pos = string * int * int * int +end + module Cmdliner_syntax : sig open Cmdliner diff --git a/src/alcotest/alcotest.ml b/src/alcotest/alcotest.ml index e1653bf3..9ff64d78 100644 --- a/src/alcotest/alcotest.ml +++ b/src/alcotest/alcotest.ml @@ -1,6 +1,12 @@ +open! Alcotest_stdlib_ext + module Unix_platform (M : Alcotest_engine.Monad.S) = struct module M = Alcotest_engine.Monad.Extend (M) + type 'a promise = 'a M.t + + let name = "Unix" + module Unix = struct open Astring include Unix @@ -30,6 +36,7 @@ module Unix_platform (M : Alcotest_engine.Monad.S) = struct open M.Syntax let time = Unix.gettimeofday + let mkdir_p path = Unix.mkdir_p path 0o770 let getcwd = Sys.getcwd let unlink_if_exists file = @@ -122,12 +129,38 @@ module Unix_platform (M : Alcotest_engine.Monad.S) = struct end module V1 = struct - include Alcotest_engine.V1.Test - module T = Alcotest_engine.V1.Cli.Make (Unix_platform) (Alcotest_engine.Monad.Identity) include T + include Alcotest_engine.V1.Test +end + +module Unstable = struct + open Alcotest_engine.Unstable + + module Source_code_position = struct + type t = string * int * int * int + end + + module Tag = Tag + + module Config = struct + include Config.User + + let v ?and_exit ?verbose ?compact ?tail_errors ?quick_only ?show_errors + ?json ?filter ?bail ?log_dir () = + create + ?filter:(Option.map (fun f -> `V2 f) filter) + ?and_exit ?verbose ?compact ?tail_errors ?quick_only ?show_errors ?json + ?bail ?log_dir () + + let merge = ( || ) + end + + module T = Cli.Make (Unix_platform) (Alcotest_engine.Monad.Identity) + include T + include Alcotest_engine.Unstable.Test end include V1 diff --git a/src/alcotest/alcotest.mli b/src/alcotest/alcotest.mli index a0ebda60..03765f07 100644 --- a/src/alcotest/alcotest.mli +++ b/src/alcotest/alcotest.mli @@ -17,17 +17,19 @@ (** A lightweight and colourful test framework. [Alcotest] provides a simple interface to perform unit tests. It exposes a - simple {{!TESTABLE} TESTABLE} module type, a {{!check} check function} to - assert test predicates and a {{!run} run} function to perform a list of - [unit -> unit] test callbacks. - - From these descriptions, [Alcotest] builds a quiet and colorful output where - only faulty runs are fully displayed at the end of the run (with the full - logs ready to inspect). + simple {!TESTABLE} module type, a {{!check} check function} to assert test + predicates and a {{!run} run} function to perform a list of [unit -> unit] + test callbacks. {e Release %%VERSION%%} *) -include Alcotest_engine.V1.Cli.S with type return = unit +include Alcotest_engine.V1.Cli.S with type return := unit +(** @inline *) + +(** {1 Testable values} + + The following combinators represent types that can be used with the {!check} + functions below. *) include module type of Alcotest_engine.V1.Test (** @inline *) @@ -43,6 +45,117 @@ module V1 : sig (** @inline *) end +module Unstable : sig + open Alcotest_engine.Unstable + + (** The latest, cutting-edge API of Alcotest. + + {b Note:} this API is still under active development, and provides no + stability guarantee. + + - {{!defining} defining} a test suite; + - {{!implementing} asserting} test properties; + - {{!running} running} the suite. + + From these descriptions, [Alcotest] builds a quiet and colorful output + where only faulty runs are fully displayed at the end of the run (with the + full logs ready to inspect). + + {[ + let test_foo () = + (* ... make test assertions ... *) + + let test_bar () = (* ... *) + + let () = + Alcotest.run ~name:__FILE__ [ + Alcotest.test ~name:"foo" test_foo; + Alcotest.test ~name:"bar" test_bar + ] + ]} *) + + (** {1:defining Defining tests} *) + + type 'a test + (** An {i ['a] test} is a program that, when executed with arguments of type + ['a], may either pass or fail with some exception. *) + + (** A {i tag} is an attribute that is attached to a test. *) + module Tag : sig + include module type of Tag with type t = Tag.t + (** @inline *) + end + + module Source_code_position : sig + type t = string * int * int * int + (** Location information passed via a [~pos] argument, intended for use with + the [__POS__] macro provided by the standard library. See the + documentation of [__POS__] for more information. *) + end + + (** @inline *) + include + Core.Test_constructors + with type 'a test := 'a test + and type 'a m := 'a + and type 'a test_args := 'a + and type source_code_position := Source_code_position.t + and type tag_set := Tag.Set.t + + (** {1:implementing Implementing tests} *) + + include module type of Alcotest_engine.Unstable.Test + (** @inline *) + + (** {1:running Running tests} *) + + module Config : sig + type t + + val v : + ?and_exit:bool -> + ?verbose:bool -> + ?compact:bool -> + ?tail_errors:[ `Unlimited | `Limit of int ] -> + ?quick_only:bool -> + ?show_errors:bool -> + ?json:bool -> + ?filter:Filter.t -> + ?bail:bool -> + ?log_dir:string -> + unit -> + t + + (** The various options taken by the tests runners {!run} and + {!run_with_args}: + + - [and_exit] (default [true]). Once the tests have completed, exit with + return code [0] if all tests passed, otherwise [1]. + - [verbose] (default [false]). Display the test std.out and std.err + (rather than redirecting to a log file). + - [compact] (default [false]). Compact the output of the tests. + - [tail_errors] (default unlimited). Show only the last N lines of + output of failed tests. + - [quick_only] (default [false]). Don't run tests with the + {{!Core.speed_level} [`Slow] speed level}. + - [show_errors] (default [false]). Display the test errors. + - [json] (default [false]). Print test results in a JSON-compatible + format. + - [log_dir] (default ["$PWD/_build/_tests/"]). The directory in which to + log the output of the tests (if [verbose] is not set). *) + + val merge : t -> t -> t + end + + (** @inline *) + include + Core.Test_runners + with type 'a test := 'a test + and type 'a m := 'a + and type source_code_position := Source_code_position.t + and type config := Config.t +end + (** {1 Unix-specific engine constructors} The [Alcotest_engine] package provides the most general form of the Alcotest diff --git a/src/alcotest/dune b/src/alcotest/dune index 32005d6c..be4c93ed 100644 --- a/src/alcotest/dune +++ b/src/alcotest/dune @@ -1,6 +1,6 @@ (library (public_name alcotest) - (libraries alcotest.engine astring fmt fmt.tty) + (libraries alcotest.engine alcotest.stdlib_ext astring fmt fmt.tty) (foreign_stubs (language c) (names alcotest_stubs)) diff --git a/test/e2e/alcotest-lwt/dune b/test/e2e/alcotest-lwt/dune index 7e853d4f..db42377c 100644 --- a/test/e2e/alcotest-lwt/dune +++ b/test/e2e/alcotest-lwt/dune @@ -1,4 +1,4 @@ (env (_ (env-vars - (ALCOTEST_COLOR auto)))) + (ALCOTEST_COLOR never)))) diff --git a/test/e2e/alcotest-lwt/failing/async_failure.expected b/test/e2e/alcotest-lwt/failing/async_failure.expected index 08da910e..d54c2a9f 100644 --- a/test/e2e/alcotest-lwt/failing/async_failure.expected +++ b/test/e2e/alcotest-lwt/failing/async_failure.expected @@ -1,17 +1,17 @@ Testing `test/e2e/alcotest-lwt/failing/async_failure.ml'. This run has ID `'. -> [FAIL] all 0 one. - [OK] all 1 two. +> [FAIL] all › one + [PASS] all › two ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] all 0 one. │ +│ [FAIL] all › one │ └──────────────────────────────────────────────────────────────────────────────┘ freeing all resources [failure] All is broken -Logs saved to `/_build/_tests//all.000.output'. +Logs saved to `/_build/_tests//all/one.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest-lwt/failing/fail_with.expected b/test/e2e/alcotest-lwt/failing/fail_with.expected index eb83569d..ca01b915 100644 --- a/test/e2e/alcotest-lwt/failing/fail_with.expected +++ b/test/e2e/alcotest-lwt/failing/fail_with.expected @@ -1,14 +1,14 @@ Testing `test/e2e/alcotest-lwt/failing/fail_with.ml'. This run has ID `'. -> [FAIL] all 0 one. - [OK] all 1 two. +> [FAIL] all › one + [PASS] all › two ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] all 0 one. │ +│ [FAIL] all › one │ └──────────────────────────────────────────────────────────────────────────────┘ [failure] should fail -Logs saved to `/_build/_tests//all.000.output'. +Logs saved to `/_build/_tests//all/one.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/bail-js.expected b/test/e2e/alcotest/failing/bail-js.expected index 9257b011..85464331 100644 --- a/test/e2e/alcotest/failing/bail-js.expected +++ b/test/e2e/alcotest/failing/bail-js.expected @@ -1,16 +1,14 @@ Testing `test/e2e/alcotest/failing/bail.ml'. This run has ID `'. - [OK] passing 0 a. -> [FAIL] failing 0 b. - - ... with 1 subsequent test skipped. + [PASS] passing › a +> [FAIL] failing › b ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] failing 0 b. │ +│ [FAIL] failing › b │ └──────────────────────────────────────────────────────────────────────────────┘ [failure] Expected failure -Logs saved to `/_build/_tests//failing.000.output'. +Logs saved to `/_build/_tests//failing/b.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/bail.expected b/test/e2e/alcotest/failing/bail.expected index 91cfe561..8ec11017 100644 --- a/test/e2e/alcotest/failing/bail.expected +++ b/test/e2e/alcotest/failing/bail.expected @@ -1,18 +1,16 @@ Testing `test/e2e/alcotest/failing/bail.ml'. This run has ID `'. - [OK] passing 0 a. -> [FAIL] failing 0 b. - - ... with 1 subsequent test skipped. + [PASS] passing › a +> [FAIL] failing › b ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] failing 0 b. │ +│ [FAIL] failing › b │ └──────────────────────────────────────────────────────────────────────────────┘ [failure] Expected failure -Logs saved to `/_build/_tests//failing.000.output'. +Logs saved to `/_build/_tests//failing/b.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/check_basic-js.expected b/test/e2e/alcotest/failing/check_basic-js.expected index e69f26ed..3b56cb73 100644 --- a/test/e2e/alcotest/failing/check_basic-js.expected +++ b/test/e2e/alcotest/failing/check_basic-js.expected @@ -2,36 +2,36 @@ Testing `test/e2e/alcotest/failing/check_basic.ml'. This run has ID `'. ASSERT bool - [FAIL] different basic 0 bool. + [FAIL] different basic › bool ASSERT int - [FAIL] different basic 1 int. + [FAIL] different basic › int ASSERT int32 - [FAIL] different basic 2 int32. + [FAIL] different basic › int32 ASSERT int64 - [FAIL] different basic 3 int64. + [FAIL] different basic › int64 ASSERT float - [FAIL] different basic 4 float. + [FAIL] different basic › float ASSERT char - [FAIL] different basic 5 char. + [FAIL] different basic › char ASSERT string - [FAIL] different basic 6 string. + [FAIL] different basic › string ASSERT bytes - [FAIL] different basic 7 bytes. + [FAIL] different basic › bytes ASSERT list - [FAIL] different composite 0 list. + [FAIL] different composite › list ASSERT array - [FAIL] different composite 1 array. + [FAIL] different composite › array ASSERT option some - [FAIL] different composite 2 option some. + [FAIL] different composite › option some ASSERT result - [FAIL] different composite 3 result. + [FAIL] different composite › result ASSERT pair - [FAIL] different composite 4 pair. + [FAIL] different composite › pair ASSERT triple - [FAIL] different composite 5 triple. + [FAIL] different composite › triple ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 0 bool. │ +│ [FAIL] different basic › bool │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL bool @@ -42,7 +42,7 @@ FAIL bool ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 1 int. │ +│ [FAIL] different basic › int │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL int @@ -53,7 +53,7 @@ FAIL int ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 2 int32. │ +│ [FAIL] different basic › int32 │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL int32 @@ -64,7 +64,7 @@ FAIL int32 ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 3 int64. │ +│ [FAIL] different basic › int64 │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL int64 @@ -75,7 +75,7 @@ FAIL int64 ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 4 float. │ +│ [FAIL] different basic › float │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL float @@ -86,7 +86,7 @@ FAIL float ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 5 char. │ +│ [FAIL] different basic › char │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL char @@ -97,7 +97,7 @@ FAIL char ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 6 string. │ +│ [FAIL] different basic › string │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL string @@ -108,7 +108,7 @@ FAIL string ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 7 bytes. │ +│ [FAIL] different basic › bytes │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL bytes @@ -119,7 +119,7 @@ FAIL bytes ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 0 list. │ +│ [FAIL] different composite › list │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL list @@ -130,7 +130,7 @@ FAIL list ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 1 array. │ +│ [FAIL] different composite › array │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL array @@ -141,7 +141,7 @@ FAIL array ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 2 option some. │ +│ [FAIL] different composite › option some │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL option some @@ -152,7 +152,7 @@ FAIL option some ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 3 result. │ +│ [FAIL] different composite › result │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL result @@ -163,7 +163,7 @@ FAIL result ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 4 pair. │ +│ [FAIL] different composite › pair │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL pair @@ -174,7 +174,7 @@ FAIL pair ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 5 triple. │ +│ [FAIL] different composite › triple │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL triple diff --git a/test/e2e/alcotest/failing/check_basic.expected b/test/e2e/alcotest/failing/check_basic.expected index 830a094d..37e119c8 100644 --- a/test/e2e/alcotest/failing/check_basic.expected +++ b/test/e2e/alcotest/failing/check_basic.expected @@ -2,36 +2,36 @@ Testing `test/e2e/alcotest/failing/check_basic.ml'. This run has ID `'. ASSERT bool - [FAIL] different basic 0 bool. + [FAIL] different basic › bool ASSERT int - [FAIL] different basic 1 int. + [FAIL] different basic › int ASSERT int32 - [FAIL] different basic 2 int32. + [FAIL] different basic › int32 ASSERT int64 - [FAIL] different basic 3 int64. + [FAIL] different basic › int64 ASSERT float - [FAIL] different basic 4 float. + [FAIL] different basic › float ASSERT char - [FAIL] different basic 5 char. + [FAIL] different basic › char ASSERT string - [FAIL] different basic 6 string. + [FAIL] different basic › string ASSERT bytes - [FAIL] different basic 7 bytes. + [FAIL] different basic › bytes ASSERT list - [FAIL] different composite 0 list. + [FAIL] different composite › list ASSERT array - [FAIL] different composite 1 array. + [FAIL] different composite › array ASSERT option some - [FAIL] different composite 2 option some. + [FAIL] different composite › option some ASSERT result - [FAIL] different composite 3 result. + [FAIL] different composite › result ASSERT pair - [FAIL] different composite 4 pair. + [FAIL] different composite › pair ASSERT triple - [FAIL] different composite 5 triple. + [FAIL] different composite › triple ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 0 bool. │ +│ [FAIL] different basic › bool │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL bool @@ -44,7 +44,7 @@ FAIL bool ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 1 int. │ +│ [FAIL] different basic › int │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL int @@ -57,7 +57,7 @@ FAIL int ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 2 int32. │ +│ [FAIL] different basic › int32 │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL int32 @@ -70,7 +70,7 @@ FAIL int32 ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 3 int64. │ +│ [FAIL] different basic › int64 │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL int64 @@ -83,7 +83,7 @@ FAIL int64 ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 4 float. │ +│ [FAIL] different basic › float │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL float @@ -96,7 +96,7 @@ FAIL float ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 5 char. │ +│ [FAIL] different basic › char │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL char @@ -109,7 +109,7 @@ FAIL char ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 6 string. │ +│ [FAIL] different basic › string │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL string @@ -122,7 +122,7 @@ FAIL string ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different basic 7 bytes. │ +│ [FAIL] different basic › bytes │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL bytes @@ -135,7 +135,7 @@ FAIL bytes ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 0 list. │ +│ [FAIL] different composite › list │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL list @@ -148,7 +148,7 @@ FAIL list ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 1 array. │ +│ [FAIL] different composite › array │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL array @@ -161,7 +161,7 @@ FAIL array ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 2 option some. │ +│ [FAIL] different composite › option some │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL option some @@ -174,7 +174,7 @@ FAIL option some ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 3 result. │ +│ [FAIL] different composite › result │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL result @@ -187,7 +187,7 @@ FAIL result ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 4 pair. │ +│ [FAIL] different composite › pair │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL pair @@ -200,7 +200,7 @@ FAIL pair ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] different composite 5 triple. │ +│ [FAIL] different composite › triple │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL triple diff --git a/test/e2e/alcotest/failing/check_located-js.expected b/test/e2e/alcotest/failing/check_located-js.expected index d0e4400d..c3a7f64d 100644 --- a/test/e2e/alcotest/failing/check_located-js.expected +++ b/test/e2e/alcotest/failing/check_located-js.expected @@ -2,20 +2,20 @@ Testing `test/e2e/alcotest/failing/check_located.ml'. This run has ID `'. ASSERT Expected failure - [FAIL] check 0 here. + [FAIL] check › here ASSERT Expected failure - [FAIL] check 1 pos. + [FAIL] check › pos ASSERT Expected failure - [FAIL] check_raises 0 here. + [FAIL] check_raises › here ASSERT Expected failure - [FAIL] check_raises 1 pos. + [FAIL] check_raises › pos ASSERT Expected failure - [FAIL] fail 0 here. + [FAIL] fail › here ASSERT Expected failure - [FAIL] fail 1 pos. + [FAIL] fail › pos ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check 0 here. │ +│ [FAIL] check › here │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 2, character 30: FAIL Expected failure @@ -27,7 +27,7 @@ FAIL Expected failure ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check 1 pos. │ +│ [FAIL] check › pos │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 4, character 10: FAIL Expected failure @@ -39,7 +39,7 @@ FAIL Expected failure ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check_raises 0 here. │ +│ [FAIL] check_raises › here │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 2, character 30: FAIL Expected failure: expecting Failure(""), got nothing. @@ -47,7 +47,7 @@ FAIL Expected failure: expecting Failure(""), got nothing. ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check_raises 1 pos. │ +│ [FAIL] check_raises › pos │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 4, character 10: FAIL Expected failure: expecting Failure(""), got nothing. @@ -55,7 +55,7 @@ FAIL Expected failure: expecting Failure(""), got nothing. ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] fail 0 here. │ +│ [FAIL] fail › here │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 2, character 30: FAIL Expected failure @@ -63,7 +63,7 @@ FAIL Expected failure ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] fail 1 pos. │ +│ [FAIL] fail › pos │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 4, character 10: FAIL Expected failure diff --git a/test/e2e/alcotest/failing/check_located.expected b/test/e2e/alcotest/failing/check_located.expected index 57a96fd0..f5eabc6e 100644 --- a/test/e2e/alcotest/failing/check_located.expected +++ b/test/e2e/alcotest/failing/check_located.expected @@ -2,20 +2,20 @@ Testing `test/e2e/alcotest/failing/check_located.ml'. This run has ID `'. ASSERT Expected failure - [FAIL] check 0 here. + [FAIL] check › here ASSERT Expected failure - [FAIL] check 1 pos. + [FAIL] check › pos ASSERT Expected failure - [FAIL] check_raises 0 here. + [FAIL] check_raises › here ASSERT Expected failure - [FAIL] check_raises 1 pos. + [FAIL] check_raises › pos ASSERT Expected failure - [FAIL] fail 0 here. + [FAIL] fail › here ASSERT Expected failure - [FAIL] fail 1 pos. + [FAIL] fail › pos ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check 0 here. │ +│ [FAIL] check › here │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 2, character 30: FAIL Expected failure @@ -29,7 +29,7 @@ FAIL Expected failure ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check 1 pos. │ +│ [FAIL] check › pos │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 4, character 10: FAIL Expected failure @@ -43,7 +43,7 @@ FAIL Expected failure ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check_raises 0 here. │ +│ [FAIL] check_raises › here │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 2, character 30: FAIL Expected failure: expecting Failure(""), got nothing. @@ -53,7 +53,7 @@ FAIL Expected failure: expecting Failure(""), got nothing. ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] check_raises 1 pos. │ +│ [FAIL] check_raises › pos │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 4, character 10: FAIL Expected failure: expecting Failure(""), got nothing. @@ -63,7 +63,7 @@ FAIL Expected failure: expecting Failure(""), got nothing. ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] fail 0 here. │ +│ [FAIL] fail › here │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 2, character 30: FAIL Expected failure @@ -73,7 +73,7 @@ FAIL Expected failure ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] fail 1 pos. │ +│ [FAIL] fail › pos │ └──────────────────────────────────────────────────────────────────────────────┘ File "test/e2e/alcotest/failing/check_located.ml", line 4, character 10: FAIL Expected failure diff --git a/test/e2e/alcotest/failing/check_long-js.expected b/test/e2e/alcotest/failing/check_long-js.expected index 705ddca8..a87a772d 100644 --- a/test/e2e/alcotest/failing/check_long-js.expected +++ b/test/e2e/alcotest/failing/check_long-js.expected @@ -2,14 +2,14 @@ Testing `test/e2e/alcotest/failing/check_long.ml'. This run has ID `'. ASSERT list - [FAIL] wrapping 0 list. + [FAIL] wrapping › list ASSERT array - [FAIL] wrapping 1 array. + [FAIL] wrapping › array ASSERT nested options - [FAIL] wrapping 2 nested options. + [FAIL] wrapping › nested options ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] wrapping 0 list. │ +│ [FAIL] wrapping › list │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL list @@ -25,7 +25,7 @@ FAIL list ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] wrapping 1 array. │ +│ [FAIL] wrapping › array │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL array @@ -51,7 +51,7 @@ FAIL array ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] wrapping 2 nested options. │ +│ [FAIL] wrapping › nested options │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL nested options diff --git a/test/e2e/alcotest/failing/check_long.expected b/test/e2e/alcotest/failing/check_long.expected index 503365a5..a97f4450 100644 --- a/test/e2e/alcotest/failing/check_long.expected +++ b/test/e2e/alcotest/failing/check_long.expected @@ -2,14 +2,14 @@ Testing `test/e2e/alcotest/failing/check_long.ml'. This run has ID `'. ASSERT list - [FAIL] wrapping 0 list. + [FAIL] wrapping › list ASSERT array - [FAIL] wrapping 1 array. + [FAIL] wrapping › array ASSERT nested options - [FAIL] wrapping 2 nested options. + [FAIL] wrapping › nested options ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] wrapping 0 list. │ +│ [FAIL] wrapping › list │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL list @@ -27,7 +27,7 @@ FAIL list ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] wrapping 1 array. │ +│ [FAIL] wrapping › array │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL array @@ -55,7 +55,7 @@ FAIL array ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] wrapping 2 nested options. │ +│ [FAIL] wrapping › nested options │ └──────────────────────────────────────────────────────────────────────────────┘ FAIL nested options diff --git a/test/e2e/alcotest/failing/compact-js.expected b/test/e2e/alcotest/failing/compact-js.expected index 4cfeefdf..65dc5937 100644 --- a/test/e2e/alcotest/failing/compact-js.expected +++ b/test/e2e/alcotest/failing/compact-js.expected @@ -4,10 +4,10 @@ This run has ID `'. ..F... ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] alpha 2 3. │ +│ [FAIL] alpha › 3 │ └──────────────────────────────────────────────────────────────────────────────┘ [failure] Error -Logs saved to `/_build/_tests//alpha.002.output'. +Logs saved to `/_build/_tests//alpha/3.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/compact.expected b/test/e2e/alcotest/failing/compact.expected index a6020d0d..9ec972da 100644 --- a/test/e2e/alcotest/failing/compact.expected +++ b/test/e2e/alcotest/failing/compact.expected @@ -4,12 +4,12 @@ This run has ID `'. ..F... ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] alpha 2 3. │ +│ [FAIL] alpha › 3 │ └──────────────────────────────────────────────────────────────────────────────┘ [failure] Error -Logs saved to `/_build/_tests//alpha.002.output'. +Logs saved to `/_build/_tests//alpha/3.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/empty_suite_name-js.expected b/test/e2e/alcotest/failing/empty_suite_name-js.expected index 99d335bf..dde60c32 100644 --- a/test/e2e/alcotest/failing/empty_suite_name-js.expected +++ b/test/e2e/alcotest/failing/empty_suite_name-js.expected @@ -1 +1 @@ -ERROR: Suite name cannot cannot be empty. Please pass a non-empty string to `run`. +ERROR: Suite name cannot be "". Please pass a non-empty string to `run`. diff --git a/test/e2e/alcotest/failing/empty_suite_name.expected b/test/e2e/alcotest/failing/empty_suite_name.expected index 99d335bf..dde60c32 100644 --- a/test/e2e/alcotest/failing/empty_suite_name.expected +++ b/test/e2e/alcotest/failing/empty_suite_name.expected @@ -1 +1 @@ -ERROR: Suite name cannot cannot be empty. Please pass a non-empty string to `run`. +ERROR: Suite name cannot be "". Please pass a non-empty string to `run`. diff --git a/test/e2e/alcotest/failing/exception_in_test-js.expected b/test/e2e/alcotest/failing/exception_in_test-js.expected index 00bcabb0..c4934a28 100644 --- a/test/e2e/alcotest/failing/exception_in_test-js.expected +++ b/test/e2e/alcotest/failing/exception_in_test-js.expected @@ -1,15 +1,15 @@ Testing `test/e2e/alcotest/failing/exception_in_test.ml'. This run has ID `'. - [OK] test-a 0 Passing. -> [FAIL] test-a 1 Failing. - [OK] test-b 0 Another pass. + [PASS] test-a › Passing +> [FAIL] test-a › Failing + [PASS] test-b › Another pass ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] test-a 1 Failing. │ +│ [FAIL] test-a › Failing │ └──────────────────────────────────────────────────────────────────────────────┘ [exception] Dune__exe__Exception_in_test.Foo("message") -Logs saved to `/_build/_tests//test-a.001.output'. +Logs saved to `/_build/_tests//test-a/Failing.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/exception_in_test.expected b/test/e2e/alcotest/failing/exception_in_test.expected index fd64b96d..21b68af3 100644 --- a/test/e2e/alcotest/failing/exception_in_test.expected +++ b/test/e2e/alcotest/failing/exception_in_test.expected @@ -1,17 +1,17 @@ Testing `test/e2e/alcotest/failing/exception_in_test.ml'. This run has ID `'. - [OK] test-a 0 Passing. -> [FAIL] test-a 1 Failing. - [OK] test-b 0 Another pass. + [PASS] test-a › Passing +> [FAIL] test-a › Failing + [PASS] test-b › Another pass ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] test-a 1 Failing. │ +│ [FAIL] test-a › Failing │ └──────────────────────────────────────────────────────────────────────────────┘ [exception] Dune__exe__Exception_in_test.Foo("message") -Logs saved to `/_build/_tests//test-a.001.output'. +Logs saved to `/_build/_tests//test-a/Failing.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/filter_all_tests-js.expected b/test/e2e/alcotest/failing/filter_all_tests-js.expected index 6b88ae7b..97767fb7 100644 --- a/test/e2e/alcotest/failing/filter_all_tests-js.expected +++ b/test/e2e/alcotest/failing/filter_all_tests-js.expected @@ -1,4 +1 @@ -Testing `test/e2e/alcotest/failing/filter_all_tests.ml'. Invalid request (no tests to run, filter skipped everything)! -This run has ID `'. - diff --git a/test/e2e/alcotest/failing/filter_all_tests.expected b/test/e2e/alcotest/failing/filter_all_tests.expected index 6b88ae7b..97767fb7 100644 --- a/test/e2e/alcotest/failing/filter_all_tests.expected +++ b/test/e2e/alcotest/failing/filter_all_tests.expected @@ -1,4 +1 @@ -Testing `test/e2e/alcotest/failing/filter_all_tests.ml'. Invalid request (no tests to run, filter skipped everything)! -This run has ID `'. - diff --git a/test/e2e/alcotest/failing/invalid_arg_in_test-js.expected b/test/e2e/alcotest/failing/invalid_arg_in_test-js.expected index 3b9d1e00..82a2de78 100644 --- a/test/e2e/alcotest/failing/invalid_arg_in_test-js.expected +++ b/test/e2e/alcotest/failing/invalid_arg_in_test-js.expected @@ -1,15 +1,15 @@ Testing `test/e2e/alcotest/failing/invalid_arg_in_test.ml'. This run has ID `'. - [OK] test-a 0 Passing. -> [FAIL] test-a 1 Failing. - [OK] test-b 0 Another pass. + [PASS] test-a › Passing +> [FAIL] test-a › Failing + [PASS] test-b › Another pass ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] test-a 1 Failing. │ +│ [FAIL] test-a › Failing │ └──────────────────────────────────────────────────────────────────────────────┘ [invalid] Failing test -Logs saved to `/_build/_tests//test-a.001.output'. +Logs saved to `/_build/_tests//test-a/Failing.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/invalid_arg_in_test.expected b/test/e2e/alcotest/failing/invalid_arg_in_test.expected index 222899ff..828fd928 100644 --- a/test/e2e/alcotest/failing/invalid_arg_in_test.expected +++ b/test/e2e/alcotest/failing/invalid_arg_in_test.expected @@ -1,17 +1,17 @@ Testing `test/e2e/alcotest/failing/invalid_arg_in_test.ml'. This run has ID `'. - [OK] test-a 0 Passing. -> [FAIL] test-a 1 Failing. - [OK] test-b 0 Another pass. + [PASS] test-a › Passing +> [FAIL] test-a › Failing + [PASS] test-b › Another pass ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] test-a 1 Failing. │ +│ [FAIL] test-a › Failing │ └──────────────────────────────────────────────────────────────────────────────┘ [invalid] Failing test -Logs saved to `/_build/_tests//test-a.001.output'. +Logs saved to `/_build/_tests//test-a/Failing.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/long_test_case_name-js.expected b/test/e2e/alcotest/failing/long_test_case_name-js.expected index 8beaf0ec..07a4d7b4 100644 --- a/test/e2e/alcotest/failing/long_test_case_name-js.expected +++ b/test/e2e/alcotest/failing/long_test_case_name-js.expected @@ -1,15 +1,15 @@ Testing `test/e2e/alcotest/failing/long_test_case_name.ml'. This run has ID `'. - [OK] test-a 0 Passing. -> [FAIL] test-a 1 Lorem ipsum dolor sit amet, consectetur a... + [PASS] test-a › Passing +> [FAIL] test-a › Lorem ipsum dolor sit amet, consectetur adipiscing elit.... -┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] test-a 1 Lorem ipsum dolor sit amet, consectetu... │ -└──────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────┐ +│ [FAIL] test-a › Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse... │ +└────────────────────────────────────────────────────────────────────────────────────────────┘ ASSERT Failed FAIL Failed -Logs saved to `/_build/_tests//test-a.001.output'. +Logs saved to `/_build/_tests//test-a/Lorem ipsum dolor sit ametU+002C consectetur adipiscing elit. Suspendisse mollisU+002C orci sed venenatis efficiturU+002C eros est imperdiet purusU+002C sit amet tincidunt massa diam ut elit..output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/long_test_case_name.expected b/test/e2e/alcotest/failing/long_test_case_name.expected index 8a176fbf..0ff652f0 100644 --- a/test/e2e/alcotest/failing/long_test_case_name.expected +++ b/test/e2e/alcotest/failing/long_test_case_name.expected @@ -1,17 +1,17 @@ Testing `test/e2e/alcotest/failing/long_test_case_name.ml'. This run has ID `'. - [OK] test-a 0 Passing. -> [FAIL] test-a 1 Lorem ipsum dolor sit amet, consectetur a... + [PASS] test-a › Passing +> [FAIL] test-a › Lorem ipsum dolor sit amet, consectetur adipiscing elit.... -┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] test-a 1 Lorem ipsum dolor sit amet, consectetu... │ -└──────────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────────────────────────────┐ +│ [FAIL] test-a › Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse... │ +└────────────────────────────────────────────────────────────────────────────────────────────┘ ASSERT Failed FAIL Failed -Logs saved to `/_build/_tests//test-a.001.output'. +Logs saved to `/_build/_tests//test-a/Lorem ipsum dolor sit ametU+002C consectetur adipiscing elit. Suspendisse mollisU+002C orci sed venenatis efficiturU+002C eros est imperdiet purusU+002C sit amet tincidunt massa diam ut elit..output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/tail_errors_limit-js.expected b/test/e2e/alcotest/failing/tail_errors_limit-js.expected index f8ce4f95..8175fa54 100644 --- a/test/e2e/alcotest/failing/tail_errors_limit-js.expected +++ b/test/e2e/alcotest/failing/tail_errors_limit-js.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/failing/tail_errors_limit.ml'. This run has ID `'. -> [FAIL] failing 0 test. +> [FAIL] failing › test ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] failing 0 test. │ +│ [FAIL] failing › test │ └──────────────────────────────────────────────────────────────────────────────┘ ... (omitting 92 lines) output line 93 @@ -17,7 +17,7 @@ output line 99 output line 100 ASSERT Logs should be 10 lines long, including this line (omitting 92). FAIL Logs should be 10 lines long, including this line (omitting 92). -Logs saved to `/_build/_tests//failing.000.output'. +Logs saved to `/_build/_tests//failing/test.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/tail_errors_limit.expected b/test/e2e/alcotest/failing/tail_errors_limit.expected index f8ce4f95..8175fa54 100644 --- a/test/e2e/alcotest/failing/tail_errors_limit.expected +++ b/test/e2e/alcotest/failing/tail_errors_limit.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/failing/tail_errors_limit.ml'. This run has ID `'. -> [FAIL] failing 0 test. +> [FAIL] failing › test ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] failing 0 test. │ +│ [FAIL] failing › test │ └──────────────────────────────────────────────────────────────────────────────┘ ... (omitting 92 lines) output line 93 @@ -17,7 +17,7 @@ output line 99 output line 100 ASSERT Logs should be 10 lines long, including this line (omitting 92). FAIL Logs should be 10 lines long, including this line (omitting 92). -Logs saved to `/_build/_tests//failing.000.output'. +Logs saved to `/_build/_tests//failing/test.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/tail_errors_unlimited-js.expected b/test/e2e/alcotest/failing/tail_errors_unlimited-js.expected index 40267829..3fbe8af7 100644 --- a/test/e2e/alcotest/failing/tail_errors_unlimited-js.expected +++ b/test/e2e/alcotest/failing/tail_errors_unlimited-js.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/failing/tail_errors_unlimited.ml'. This run has ID `'. -> [FAIL] failing 0 test. +> [FAIL] failing › test ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] failing 0 test. │ +│ [FAIL] failing › test │ └──────────────────────────────────────────────────────────────────────────────┘ output line 1 output line 2 @@ -108,7 +108,7 @@ output line 99 output line 100 ASSERT Logs above should be 101 lines long. FAIL Logs above should be 101 lines long. -Logs saved to `/_build/_tests//failing.000.output'. +Logs saved to `/_build/_tests//failing/test.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/failing/tail_errors_unlimited.expected b/test/e2e/alcotest/failing/tail_errors_unlimited.expected index 446f17de..efa240ce 100644 --- a/test/e2e/alcotest/failing/tail_errors_unlimited.expected +++ b/test/e2e/alcotest/failing/tail_errors_unlimited.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/failing/tail_errors_unlimited.ml'. This run has ID `'. -> [FAIL] failing 0 test. +> [FAIL] failing › test ┌──────────────────────────────────────────────────────────────────────────────┐ -│ [FAIL] failing 0 test. │ +│ [FAIL] failing › test │ └──────────────────────────────────────────────────────────────────────────────┘ output line 1 output line 2 @@ -110,7 +110,7 @@ ASSERT Logs above should be 101 lines long. FAIL Logs above should be 101 lines long. -Logs saved to `/_build/_tests//failing.000.output'. +Logs saved to `/_build/_tests//failing/test.output'. ────────────────────────────────────────────────────────────────────────────── Full test results in `/_build/_tests/'. diff --git a/test/e2e/alcotest/inside-dune/color-overridden.expected b/test/e2e/alcotest/inside-dune/color-overridden.expected index 5ef96460..dca6302c 100644 --- a/test/e2e/alcotest/inside-dune/color-overridden.expected +++ b/test/e2e/alcotest/inside-dune/color-overridden.expected @@ -1,9 +1,9 @@ Testing `test/e2e/alcotest/inside-dune/test_color.ml'. This run has ID `'. - [OK] alpha 0 Output may or may not contain ANSII escape... - [OK] alpha 1 according to whether or not [--color] is set. - [OK] alpha 2 (See the corresponding [dune] file.). + [PASS] alpha › Output may or may not contain ANSII escape codes + [PASS] alpha › according to whether or not [--color] is set. + [PASS] alpha › (See the corresponding [dune] file.) Full test results in `/_build/_tests/'. Test Successful in s. 3 tests run. diff --git a/test/e2e/alcotest/passing/and_exit_false-js.expected b/test/e2e/alcotest/passing/and_exit_false-js.expected index dbaba105..cf41b3e2 100644 --- a/test/e2e/alcotest/passing/and_exit_false-js.expected +++ b/test/e2e/alcotest/passing/and_exit_false-js.expected @@ -1,8 +1,7 @@ Testing `test/e2e/alcotest/passing/and_exit_false.ml'. This run has ID `'. - [OK] test-a 0 Test case. + [PASS] test-a › Test case Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. - Program execution continued! diff --git a/test/e2e/alcotest/passing/and_exit_false.expected b/test/e2e/alcotest/passing/and_exit_false.expected index dbaba105..cf41b3e2 100644 --- a/test/e2e/alcotest/passing/and_exit_false.expected +++ b/test/e2e/alcotest/passing/and_exit_false.expected @@ -1,8 +1,7 @@ Testing `test/e2e/alcotest/passing/and_exit_false.ml'. This run has ID `'. - [OK] test-a 0 Test case. + [PASS] test-a › Test case Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. - Program execution continued! diff --git a/test/e2e/alcotest/passing/and_exit_true-js.expected b/test/e2e/alcotest/passing/and_exit_true-js.expected index b5c5d7ed..79765f67 100644 --- a/test/e2e/alcotest/passing/and_exit_true-js.expected +++ b/test/e2e/alcotest/passing/and_exit_true-js.expected @@ -1,7 +1,7 @@ Testing `test/e2e/alcotest/passing/and_exit_true.ml'. This run has ID `'. - [OK] test-a 0 Test case. + [PASS] test-a › Test case Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/and_exit_true.expected b/test/e2e/alcotest/passing/and_exit_true.expected index b5c5d7ed..79765f67 100644 --- a/test/e2e/alcotest/passing/and_exit_true.expected +++ b/test/e2e/alcotest/passing/and_exit_true.expected @@ -1,7 +1,7 @@ Testing `test/e2e/alcotest/passing/and_exit_true.ml'. This run has ID `'. - [OK] test-a 0 Test case. + [PASS] test-a › Test case Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/assert_and_verbose-js.expected b/test/e2e/alcotest/passing/assert_and_verbose-js.expected index a1890d58..cdf8151f 100644 --- a/test/e2e/alcotest/passing/assert_and_verbose-js.expected +++ b/test/e2e/alcotest/passing/assert_and_verbose-js.expected @@ -3,18 +3,18 @@ This run has ID `'. ASSERT alpha-0 check alpha-0 standard out - [OK] alpha 0 check → stdout. + [PASS] alpha › check → stdout alpha-1 standard out ASSERT alpha-1 check - [OK] alpha 1 stdout → check. + [PASS] alpha › stdout → check ASSERT alpha-2 check alpha-2 standard error - [OK] alpha 2 check → stderr. + [PASS] alpha › check → stderr beta-0 standard out ASSERT beta-0 check beta-0 standard error - [OK] beta 0 stdout → check → stderr. + [PASS] beta › stdout → check → stderr Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/assert_and_verbose.expected b/test/e2e/alcotest/passing/assert_and_verbose.expected index a1890d58..cdf8151f 100644 --- a/test/e2e/alcotest/passing/assert_and_verbose.expected +++ b/test/e2e/alcotest/passing/assert_and_verbose.expected @@ -3,18 +3,18 @@ This run has ID `'. ASSERT alpha-0 check alpha-0 standard out - [OK] alpha 0 check → stdout. + [PASS] alpha › check → stdout alpha-1 standard out ASSERT alpha-1 check - [OK] alpha 1 stdout → check. + [PASS] alpha › stdout → check ASSERT alpha-2 check alpha-2 standard error - [OK] alpha 2 check → stderr. + [PASS] alpha › check → stderr beta-0 standard out ASSERT beta-0 check beta-0 standard error - [OK] beta 0 stdout → check → stderr. + [PASS] beta › stdout → check → stderr Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/assert_not_printed-js.expected b/test/e2e/alcotest/passing/assert_not_printed-js.expected index aa5e7104..0e61f4f3 100644 --- a/test/e2e/alcotest/passing/assert_not_printed-js.expected +++ b/test/e2e/alcotest/passing/assert_not_printed-js.expected @@ -1,9 +1,9 @@ Testing `test/e2e/alcotest/passing/assert_not_printed.ml'. This run has ID `'. - [OK] alpha 0 0. - [OK] alpha 1 1. - [OK] beta 0 2. + [PASS] alpha › 0 + [PASS] alpha › 1 + [PASS] beta › 2 Full test results in `/_build/_tests/'. Test Successful in s. 3 tests run. diff --git a/test/e2e/alcotest/passing/assert_not_printed.expected b/test/e2e/alcotest/passing/assert_not_printed.expected index aa5e7104..0e61f4f3 100644 --- a/test/e2e/alcotest/passing/assert_not_printed.expected +++ b/test/e2e/alcotest/passing/assert_not_printed.expected @@ -1,9 +1,9 @@ Testing `test/e2e/alcotest/passing/assert_not_printed.ml'. This run has ID `'. - [OK] alpha 0 0. - [OK] alpha 1 1. - [OK] beta 0 2. + [PASS] alpha › 0 + [PASS] alpha › 1 + [PASS] beta › 2 Full test results in `/_build/_tests/'. Test Successful in s. 3 tests run. diff --git a/test/e2e/alcotest/passing/basic-js.expected b/test/e2e/alcotest/passing/basic-js.expected index f10d1713..d54abd1f 100644 --- a/test/e2e/alcotest/passing/basic-js.expected +++ b/test/e2e/alcotest/passing/basic-js.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/basic.ml'. This run has ID `'. - [OK] test-a 0 First test case. - [OK] test-a 1 Second test case. - [OK] test-b 0 Third test case. - [OK] test-c 0 Fourth test case. + [PASS] test-a › First test case + [PASS] test-a › Second test case + [PASS] test-b › Third test case + [PASS] test-c › Fourth test case Full test results in `/_build/_tests/'. Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/basic.expected b/test/e2e/alcotest/passing/basic.expected index f10d1713..d54abd1f 100644 --- a/test/e2e/alcotest/passing/basic.expected +++ b/test/e2e/alcotest/passing/basic.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/basic.ml'. This run has ID `'. - [OK] test-a 0 First test case. - [OK] test-a 1 Second test case. - [OK] test-b 0 Third test case. - [OK] test-c 0 Fourth test case. + [PASS] test-a › First test case + [PASS] test-a › Second test case + [PASS] test-b › Third test case + [PASS] test-c › Fourth test case Full test results in `/_build/_tests/'. Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/check_basic-js.expected b/test/e2e/alcotest/passing/check_basic-js.expected index 23aa54ca..c14fe62e 100644 --- a/test/e2e/alcotest/passing/check_basic-js.expected +++ b/test/e2e/alcotest/passing/check_basic-js.expected @@ -1,30 +1,30 @@ Testing `test/e2e/alcotest/passing/check_basic.ml'. This run has ID `'. - [OK] reflexive basic 0 unit. - [OK] reflexive basic 1 bool. - [OK] reflexive basic 2 int. - [OK] reflexive basic 3 int32. - [OK] reflexive basic 4 int64. - [OK] reflexive basic 5 float. - [OK] reflexive basic 6 char. - [OK] reflexive basic 7 string. - [OK] reflexive basic 8 bytes. - [OK] reflexive composite 0 empty list. - [OK] reflexive composite 1 non-empty list. - [OK] reflexive composite 2 empty array. - [OK] reflexive composite 3 non-empty array. - [OK] reflexive composite 4 option some. - [OK] reflexive composite 5 option none. - [OK] reflexive composite 6 result ok. - [OK] reflexive composite 7 result error. - [OK] reflexive composite 8 pair. - [OK] reflexive composite 9 triple. - [OK] negation 0 checked exceptions. - [OK] negation 1 negated testables. - [OK] fuzzy equality 0 float thresholds. - [OK] fuzzy equality 1 sorted list. - [OK] labeled check 0 passing. + [PASS] reflexive basic › unit + [PASS] reflexive basic › bool + [PASS] reflexive basic › int + [PASS] reflexive basic › int32 + [PASS] reflexive basic › int64 + [PASS] reflexive basic › float + [PASS] reflexive basic › char + [PASS] reflexive basic › string + [PASS] reflexive basic › bytes + [PASS] reflexive composite › empty list + [PASS] reflexive composite › non-empty list + [PASS] reflexive composite › empty array + [PASS] reflexive composite › non-empty array + [PASS] reflexive composite › option some + [PASS] reflexive composite › option none + [PASS] reflexive composite › result ok + [PASS] reflexive composite › result error + [PASS] reflexive composite › pair + [PASS] reflexive composite › triple + [PASS] negation › checked exceptions + [PASS] negation › negated testables + [PASS] fuzzy equality › float thresholds + [PASS] fuzzy equality › sorted list + [PASS] labeled check › passing Full test results in `/_build/_tests/'. Test Successful in s. 24 tests run. diff --git a/test/e2e/alcotest/passing/check_basic.expected b/test/e2e/alcotest/passing/check_basic.expected index 23aa54ca..c14fe62e 100644 --- a/test/e2e/alcotest/passing/check_basic.expected +++ b/test/e2e/alcotest/passing/check_basic.expected @@ -1,30 +1,30 @@ Testing `test/e2e/alcotest/passing/check_basic.ml'. This run has ID `'. - [OK] reflexive basic 0 unit. - [OK] reflexive basic 1 bool. - [OK] reflexive basic 2 int. - [OK] reflexive basic 3 int32. - [OK] reflexive basic 4 int64. - [OK] reflexive basic 5 float. - [OK] reflexive basic 6 char. - [OK] reflexive basic 7 string. - [OK] reflexive basic 8 bytes. - [OK] reflexive composite 0 empty list. - [OK] reflexive composite 1 non-empty list. - [OK] reflexive composite 2 empty array. - [OK] reflexive composite 3 non-empty array. - [OK] reflexive composite 4 option some. - [OK] reflexive composite 5 option none. - [OK] reflexive composite 6 result ok. - [OK] reflexive composite 7 result error. - [OK] reflexive composite 8 pair. - [OK] reflexive composite 9 triple. - [OK] negation 0 checked exceptions. - [OK] negation 1 negated testables. - [OK] fuzzy equality 0 float thresholds. - [OK] fuzzy equality 1 sorted list. - [OK] labeled check 0 passing. + [PASS] reflexive basic › unit + [PASS] reflexive basic › bool + [PASS] reflexive basic › int + [PASS] reflexive basic › int32 + [PASS] reflexive basic › int64 + [PASS] reflexive basic › float + [PASS] reflexive basic › char + [PASS] reflexive basic › string + [PASS] reflexive basic › bytes + [PASS] reflexive composite › empty list + [PASS] reflexive composite › non-empty list + [PASS] reflexive composite › empty array + [PASS] reflexive composite › non-empty array + [PASS] reflexive composite › option some + [PASS] reflexive composite › option none + [PASS] reflexive composite › result ok + [PASS] reflexive composite › result error + [PASS] reflexive composite › pair + [PASS] reflexive composite › triple + [PASS] negation › checked exceptions + [PASS] negation › negated testables + [PASS] fuzzy equality › float thresholds + [PASS] fuzzy equality › sorted list + [PASS] labeled check › passing Full test results in `/_build/_tests/'. Test Successful in s. 24 tests run. diff --git a/test/e2e/alcotest/passing/cli_verbose-js.expected b/test/e2e/alcotest/passing/cli_verbose-js.expected index a2153997..229b6c0b 100644 --- a/test/e2e/alcotest/passing/cli_verbose-js.expected +++ b/test/e2e/alcotest/passing/cli_verbose-js.expected @@ -2,6 +2,6 @@ Testing `cli_verbose'. This run has ID `'. SHOULD APPEAR IN TEST OUTPUT - [OK] alpha 0 0. + [PASS] alpha › 0 Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/cli_verbose.expected b/test/e2e/alcotest/passing/cli_verbose.expected index a2153997..229b6c0b 100644 --- a/test/e2e/alcotest/passing/cli_verbose.expected +++ b/test/e2e/alcotest/passing/cli_verbose.expected @@ -2,6 +2,6 @@ Testing `cli_verbose'. This run has ID `'. SHOULD APPEAR IN TEST OUTPUT - [OK] alpha 0 0. + [PASS] alpha › 0 Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/empty_test_name-js.expected b/test/e2e/alcotest/passing/empty_test_name-js.expected index dc571a50..b5c2f747 100644 --- a/test/e2e/alcotest/passing/empty_test_name-js.expected +++ b/test/e2e/alcotest/passing/empty_test_name-js.expected @@ -1,7 +1,7 @@ Testing `test/e2e/alcotest/passing/empty_test_name.ml'. This run has ID `'. - [OK] 0 1. + [PASS] › 1 Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/empty_test_name.expected b/test/e2e/alcotest/passing/empty_test_name.expected index dc571a50..b5c2f747 100644 --- a/test/e2e/alcotest/passing/empty_test_name.expected +++ b/test/e2e/alcotest/passing/empty_test_name.expected @@ -1,7 +1,7 @@ Testing `test/e2e/alcotest/passing/empty_test_name.ml'. This run has ID `'. - [OK] 0 1. + [PASS] › 1 Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/filter_name-js.expected b/test/e2e/alcotest/passing/filter_name-js.expected index 41f77018..886ab8a9 100644 --- a/test/e2e/alcotest/passing/filter_name-js.expected +++ b/test/e2e/alcotest/passing/filter_name-js.expected @@ -1,9 +1,9 @@ Testing `test/e2e/alcotest/passing/filter_name.ml'. This run has ID `'. - [OK] test-a 0 First test case. - [OK] test-a 1 Second test case. - [SKIP] test-b 0 Skipped failing test. + [PASS] test-a › First test case + [PASS] test-a › Second test case + [SKIP] test-b › Skipped failing test Full test results in `/_build/_tests/'. Test Successful in s. 2 tests run. diff --git a/test/e2e/alcotest/passing/filter_name.expected b/test/e2e/alcotest/passing/filter_name.expected index 41f77018..886ab8a9 100644 --- a/test/e2e/alcotest/passing/filter_name.expected +++ b/test/e2e/alcotest/passing/filter_name.expected @@ -1,9 +1,9 @@ Testing `test/e2e/alcotest/passing/filter_name.ml'. This run has ID `'. - [OK] test-a 0 First test case. - [OK] test-a 1 Second test case. - [SKIP] test-b 0 Skipped failing test. + [PASS] test-a › First test case + [PASS] test-a › Second test case + [SKIP] test-b › Skipped failing test Full test results in `/_build/_tests/'. Test Successful in s. 2 tests run. diff --git a/test/e2e/alcotest/passing/filter_name_regex-js.expected b/test/e2e/alcotest/passing/filter_name_regex-js.expected index e55dfd86..f170b9b3 100644 --- a/test/e2e/alcotest/passing/filter_name_regex-js.expected +++ b/test/e2e/alcotest/passing/filter_name_regex-js.expected @@ -1,11 +1,11 @@ Testing `test/e2e/alcotest/passing/filter_name_regex.ml'. This run has ID `'. - [OK] basic-run-a 0 Executed. - [OK] basic-run-a 1 Also executed. - [SKIP] basic-run-b 0 Skipped. - [OK] basic-run-c 0 Executed. - [SKIP] complex-run-a 0 Skipped. + [PASS] basic-run-a › Executed + [PASS] basic-run-a › Also executed + [SKIP] basic-run-b › Skipped + [PASS] basic-run-c › Executed + [SKIP] complex-run-a › Skipped Full test results in `/_build/_tests/'. Test Successful in s. 3 tests run. diff --git a/test/e2e/alcotest/passing/filter_name_regex.expected b/test/e2e/alcotest/passing/filter_name_regex.expected index e55dfd86..f170b9b3 100644 --- a/test/e2e/alcotest/passing/filter_name_regex.expected +++ b/test/e2e/alcotest/passing/filter_name_regex.expected @@ -1,11 +1,11 @@ Testing `test/e2e/alcotest/passing/filter_name_regex.ml'. This run has ID `'. - [OK] basic-run-a 0 Executed. - [OK] basic-run-a 1 Also executed. - [SKIP] basic-run-b 0 Skipped. - [OK] basic-run-c 0 Executed. - [SKIP] complex-run-a 0 Skipped. + [PASS] basic-run-a › Executed + [PASS] basic-run-a › Also executed + [SKIP] basic-run-b › Skipped + [PASS] basic-run-c › Executed + [SKIP] complex-run-a › Skipped Full test results in `/_build/_tests/'. Test Successful in s. 3 tests run. diff --git a/test/e2e/alcotest/passing/filter_range-js.expected b/test/e2e/alcotest/passing/filter_range-js.expected index 4a8ef81f..24c2704f 100644 --- a/test/e2e/alcotest/passing/filter_range-js.expected +++ b/test/e2e/alcotest/passing/filter_range-js.expected @@ -1,12 +1,12 @@ Testing `test/e2e/alcotest/passing/filter_range.ml'. This run has ID `'. - [OK] main 0 test-0. - [OK] main 1 test-1. - [OK] main 2 test-2. - [OK] main 3 test-3. - [SKIP] main 4 test-4. - [OK] main 5 test-5. + [PASS] main › test-0 + [PASS] main › test-1 + [PASS] main › test-2 + [PASS] main › test-3 + [SKIP] main › test-4 + [PASS] main › test-5 Full test results in `/_build/_tests/'. Test Successful in s. 5 tests run. diff --git a/test/e2e/alcotest/passing/filter_range.expected b/test/e2e/alcotest/passing/filter_range.expected index 4a8ef81f..24c2704f 100644 --- a/test/e2e/alcotest/passing/filter_range.expected +++ b/test/e2e/alcotest/passing/filter_range.expected @@ -1,12 +1,12 @@ Testing `test/e2e/alcotest/passing/filter_range.ml'. This run has ID `'. - [OK] main 0 test-0. - [OK] main 1 test-1. - [OK] main 2 test-2. - [OK] main 3 test-3. - [SKIP] main 4 test-4. - [OK] main 5 test-5. + [PASS] main › test-0 + [PASS] main › test-1 + [PASS] main › test-2 + [PASS] main › test-3 + [SKIP] main › test-4 + [PASS] main › test-5 Full test results in `/_build/_tests/'. Test Successful in s. 5 tests run. diff --git a/test/e2e/alcotest/passing/isatty-js.expected b/test/e2e/alcotest/passing/isatty-js.expected index 7ed7bc98..5dba48f7 100644 --- a/test/e2e/alcotest/passing/isatty-js.expected +++ b/test/e2e/alcotest/passing/isatty-js.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/isatty.ml'. This run has ID `'. - ... test-a 0 First test case. [OK] test-a 0 First test case. - ... test-a 1 Second test case. [OK] test-a 1 Second test case. - ... test-b 0 Third test case. [OK] test-b 0 Third test case. - ... test-c 0 Fourth test case. [OK] test-c 0 Fourth test case. + [ —— ] test-a › First test case [PASS] test-a › First test case + [ —— ] test-a › Second test case [PASS] test-a › Second test case + [ —— ] test-b › Third test case [PASS] test-b › Third test case + [ —— ] test-c › Fourth test case [PASS] test-c › Fourth test case Full test results in `/_build/_tests/'. Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/isatty.expected b/test/e2e/alcotest/passing/isatty.expected index 7ed7bc98..5dba48f7 100644 --- a/test/e2e/alcotest/passing/isatty.expected +++ b/test/e2e/alcotest/passing/isatty.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/isatty.ml'. This run has ID `'. - ... test-a 0 First test case. [OK] test-a 0 First test case. - ... test-a 1 Second test case. [OK] test-a 1 Second test case. - ... test-b 0 Third test case. [OK] test-b 0 Third test case. - ... test-c 0 Fourth test case. [OK] test-c 0 Fourth test case. + [ —— ] test-a › First test case [PASS] test-a › First test case + [ —— ] test-a › Second test case [PASS] test-a › Second test case + [ —— ] test-b › Third test case [PASS] test-b › Third test case + [ —— ] test-c › Fourth test case [PASS] test-c › Fourth test case Full test results in `/_build/_tests/'. Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/list_tests-js.expected b/test/e2e/alcotest/passing/list_tests-js.expected index de275e29..75cf33d7 100644 --- a/test/e2e/alcotest/passing/list_tests-js.expected +++ b/test/e2e/alcotest/passing/list_tests-js.expected @@ -1,3 +1,8 @@ -test-a 0 alpha. -test-a 1 beta. -test-b 0 lorem ipsum dolor sit amet consectutor adipiscing elit. +test/e2e/alcotest/passing/list_tests.ml +├─ test-a +│ ├─ alpha < speed_level = Quick; index = 0 > +│ └─ beta < speed_level = Quick; index = 1 > +│ +└─ test-b + └─ lorem ipsum dolor sit amet consectutor adipiscing elit < speed_level = Quick; index = 0 > + diff --git a/test/e2e/alcotest/passing/list_tests.expected b/test/e2e/alcotest/passing/list_tests.expected index de275e29..75cf33d7 100644 --- a/test/e2e/alcotest/passing/list_tests.expected +++ b/test/e2e/alcotest/passing/list_tests.expected @@ -1,3 +1,8 @@ -test-a 0 alpha. -test-a 1 beta. -test-b 0 lorem ipsum dolor sit amet consectutor adipiscing elit. +test/e2e/alcotest/passing/list_tests.ml +├─ test-a +│ ├─ alpha < speed_level = Quick; index = 0 > +│ └─ beta < speed_level = Quick; index = 1 > +│ +└─ test-b + └─ lorem ipsum dolor sit amet consectutor adipiscing elit < speed_level = Quick; index = 0 > + diff --git a/test/e2e/alcotest/passing/quick_only-js.expected b/test/e2e/alcotest/passing/quick_only-js.expected index f83811f6..52917e3a 100644 --- a/test/e2e/alcotest/passing/quick_only-js.expected +++ b/test/e2e/alcotest/passing/quick_only-js.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/quick_only.ml'. This run has ID `'. - [OK] test-a 0 Quick. - [SKIP] test-a 1 Slow. - [SKIP] test-b 0 Slow. - [OK] test-b 1 Quick. + [PASS] test-a › Quick + [SKIP] test-a › Slow + [SKIP] test-b › Slow + [PASS] test-b › Quick Full test results in `/_build/_tests/'. Test Successful in s. 2 tests run. diff --git a/test/e2e/alcotest/passing/quick_only.expected b/test/e2e/alcotest/passing/quick_only.expected index f83811f6..52917e3a 100644 --- a/test/e2e/alcotest/passing/quick_only.expected +++ b/test/e2e/alcotest/passing/quick_only.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/quick_only.ml'. This run has ID `'. - [OK] test-a 0 Quick. - [SKIP] test-a 1 Slow. - [SKIP] test-b 0 Slow. - [OK] test-b 1 Quick. + [PASS] test-a › Quick + [SKIP] test-a › Slow + [SKIP] test-b › Slow + [PASS] test-b › Quick Full test results in `/_build/_tests/'. Test Successful in s. 2 tests run. diff --git a/test/e2e/alcotest/passing/quick_only_regex-js.expected b/test/e2e/alcotest/passing/quick_only_regex-js.expected index f8ec3441..c7d64342 100644 --- a/test/e2e/alcotest/passing/quick_only_regex-js.expected +++ b/test/e2e/alcotest/passing/quick_only_regex-js.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/quick_only_regex.ml'. This run has ID `'. - [OK] test-a 0 Quick & passes filter. - [SKIP] test-a 1 Slow & passes filter. - [SKIP] test-b 0 Slow & fails filter. - [SKIP] test-b 1 Quick & fails filter. + [PASS] test-a › Quick & passes filter + [SKIP] test-a › Slow & passes filter + [SKIP] test-b › Slow & fails filter + [SKIP] test-b › Quick & fails filter Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/quick_only_regex.expected b/test/e2e/alcotest/passing/quick_only_regex.expected index f8ec3441..c7d64342 100644 --- a/test/e2e/alcotest/passing/quick_only_regex.expected +++ b/test/e2e/alcotest/passing/quick_only_regex.expected @@ -1,10 +1,10 @@ Testing `test/e2e/alcotest/passing/quick_only_regex.ml'. This run has ID `'. - [OK] test-a 0 Quick & passes filter. - [SKIP] test-a 1 Slow & passes filter. - [SKIP] test-b 0 Slow & fails filter. - [SKIP] test-b 1 Quick & fails filter. + [PASS] test-a › Quick & passes filter + [SKIP] test-a › Slow & passes filter + [SKIP] test-b › Slow & fails filter + [SKIP] test-b › Quick & fails filter Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/separator_testname-js.expected b/test/e2e/alcotest/passing/separator_testname-js.expected index dd0b49cf..3a7ea285 100644 --- a/test/e2e/alcotest/passing/separator_testname-js.expected +++ b/test/e2e/alcotest/passing/separator_testname-js.expected @@ -1,7 +1,7 @@ Testing `test/e2e/alcotest/passing/separator_testname.ml'. This run has ID `'. - [OK] with/separator 0 First test case. + [PASS] with/separator › First test case Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/separator_testname.expected b/test/e2e/alcotest/passing/separator_testname.expected index dd0b49cf..3a7ea285 100644 --- a/test/e2e/alcotest/passing/separator_testname.expected +++ b/test/e2e/alcotest/passing/separator_testname.expected @@ -1,7 +1,7 @@ Testing `test/e2e/alcotest/passing/separator_testname.ml'. This run has ID `'. - [OK] with/separator 0 First test case. + [PASS] with/separator › First test case Full test results in `/_build/_tests/'. Test Successful in s. 1 test run. diff --git a/test/e2e/alcotest/passing/unicode_testname-js.expected b/test/e2e/alcotest/passing/unicode_testname-js.expected index 94336ba3..341656b9 100644 --- a/test/e2e/alcotest/passing/unicode_testname-js.expected +++ b/test/e2e/alcotest/passing/unicode_testname-js.expected @@ -1,8 +1,8 @@ Testing `Suite name containing file separators / and non-ASCII characters 🔥'. This run has ID `'. - [OK] 🔥 0 Non ASCII unicode character. - [OK] 🔥a-b 0 Non ASCII and ASCII characters. + [PASS] 🔥 › Non ASCII unicode character + [PASS] 🔥a-b › Non ASCII and ASCII characters Full test results in `/_build/_tests/'. Test Successful in s. 2 tests run. diff --git a/test/e2e/alcotest/passing/unicode_testname.expected b/test/e2e/alcotest/passing/unicode_testname.expected index 94336ba3..341656b9 100644 --- a/test/e2e/alcotest/passing/unicode_testname.expected +++ b/test/e2e/alcotest/passing/unicode_testname.expected @@ -1,8 +1,8 @@ Testing `Suite name containing file separators / and non-ASCII characters 🔥'. This run has ID `'. - [OK] 🔥 0 Non ASCII unicode character. - [OK] 🔥a-b 0 Non ASCII and ASCII characters. + [PASS] 🔥 › Non ASCII unicode character + [PASS] 🔥a-b › Non ASCII and ASCII characters Full test results in `/_build/_tests/'. Test Successful in s. 2 tests run. diff --git a/test/e2e/alcotest/passing/verbose_newlines-js.expected b/test/e2e/alcotest/passing/verbose_newlines-js.expected index 86ca0f9d..1ee1fa93 100644 --- a/test/e2e/alcotest/passing/verbose_newlines-js.expected +++ b/test/e2e/alcotest/passing/verbose_newlines-js.expected @@ -1,13 +1,13 @@ Testing `test/e2e/alcotest/passing/verbose_newlines.ml'. This run has ID `'. -Print inside alpha [OK] alpha 0 0 newlines. +Print inside alpha [PASS] alpha › 0 newlines Print inside beta - [OK] beta 0 1 newline. + [PASS] beta › 1 newline Print inside gamma -Lorem ipsum dolor sit amet, consectetur adipiscing elit, nullam malesuada dictum tortor in venenatis. [OK] gamma 0 1 newline + long line. +Lorem ipsum dolor sit amet, consectetur adipiscing elit, nullam malesuada dictum tortor in venenatis. [PASS] gamma › 1 newline + long line Print inside delta ASSERT Lorem ipsum dolor sit amet, consectetur adipiscing elit, nullam malesuada dictum tortor in venenatis. - [OK] delta 0 1 newline + long check. + [PASS] delta › 1 newline + long check Test Successful in s. 4 tests run. diff --git a/test/e2e/alcotest/passing/verbose_newlines.expected b/test/e2e/alcotest/passing/verbose_newlines.expected index 86ca0f9d..1ee1fa93 100644 --- a/test/e2e/alcotest/passing/verbose_newlines.expected +++ b/test/e2e/alcotest/passing/verbose_newlines.expected @@ -1,13 +1,13 @@ Testing `test/e2e/alcotest/passing/verbose_newlines.ml'. This run has ID `'. -Print inside alpha [OK] alpha 0 0 newlines. +Print inside alpha [PASS] alpha › 0 newlines Print inside beta - [OK] beta 0 1 newline. + [PASS] beta › 1 newline Print inside gamma -Lorem ipsum dolor sit amet, consectetur adipiscing elit, nullam malesuada dictum tortor in venenatis. [OK] gamma 0 1 newline + long line. +Lorem ipsum dolor sit amet, consectetur adipiscing elit, nullam malesuada dictum tortor in venenatis. [PASS] gamma › 1 newline + long line Print inside delta ASSERT Lorem ipsum dolor sit amet, consectetur adipiscing elit, nullam malesuada dictum tortor in venenatis. - [OK] delta 0 1 newline + long check. + [PASS] delta › 1 newline + long check Test Successful in s. 4 tests run.