diff --git a/bin/build.ml b/bin/build.ml index 1f1b7d15e6b..46855820586 100644 --- a/bin/build.ml +++ b/bin/build.ml @@ -101,6 +101,7 @@ let poll_handling_rpc_build_requests ~(common : Common.t) ~config = | Runtest test_paths -> Runtest_common.make_request ~contexts:setup.contexts + ~scontexts:setup.scontexts ~to_cwd:root.to_cwd ~test_paths in diff --git a/bin/import.ml b/bin/import.ml index ba78f7e9fe0..152e8ba68dd 100644 --- a/bin/import.ml +++ b/bin/import.ml @@ -39,6 +39,10 @@ include struct module Library = Library module Melange = Melange module Executables = Executables + module Dune_load = Dune_load + module Dir_contents = Dir_contents + module Sub_system_name = Sub_system_name + module Inline_tests_info = Inline_tests_info end include struct diff --git a/bin/runtest.ml b/bin/runtest.ml index 821986b84cc..a57d4f88f2d 100644 --- a/bin/runtest.ml +++ b/bin/runtest.ml @@ -43,6 +43,7 @@ let runtest_term = Build.run_build_command ~common ~config ~request:(fun setup -> Runtest_common.make_request ~contexts:setup.contexts + ~scontexts:setup.scontexts ~to_cwd:(Common.root common).to_cwd ~test_paths) | Error lock_held_by -> diff --git a/bin/runtest_common.ml b/bin/runtest_common.ml index 22d7777c5a8..9c1e5220e86 100644 --- a/bin/runtest_common.ml +++ b/bin/runtest_common.ml @@ -4,11 +4,22 @@ module Test_kind = struct type t = | Runtest of Path.t | Cram of Path.t * Source.Cram_test.t + | Test_executable of Path.t * string (* dir, executable name *) + | Inline_tests of Path.t * string (* dir, library name *) let alias ~contexts = function | Cram (dir, cram) -> let name = Dune_engine.Alias.Name.of_string (Source.Cram_test.name cram) in Alias.in_dir ~name ~recursive:false ~contexts dir + | Test_executable (dir, exe_name) -> + (* CR-someday Alizter: get the proper alias, also check js_of_ocaml + runtst aliases? *) + let name = Dune_engine.Alias.Name.of_string ("runtest-" ^ exe_name) in + Alias.in_dir ~name ~recursive:false ~contexts dir + | Inline_tests (dir, lib_name) -> + (* CR-someday Alizter: get the proper alias where it is defined. *) + let name = Dune_engine.Alias.Name.of_string ("runtest-" ^ lib_name) in + Alias.in_dir ~name ~recursive:false ~contexts dir | Runtest dir -> Alias.in_dir ~name:Dune_rules.Alias.runtest ~recursive:true ~contexts dir ;; @@ -34,13 +45,60 @@ let find_cram_test cram_tests path = | Error (Dune_rules.Cram_rules.Missing_run_t _) | Ok _ -> None) ;; -let all_tests_of_dir parent_dir = +let has_inline_tests (lib : Dune_rules.Library.t) = + Dune_rules.Sub_system_name.Map.mem + lib.sub_systems + Dune_rules.Inline_tests_info.Tests.name +;; + +let classify_ml_test ~sctx ~dir ~ml_file = + let open Memo.O in + let module_name = Filename.remove_extension ml_file in + match Dune_lang.Module_name.of_string_opt module_name with + | None -> Memo.return (Error `Not_a_test) + | Some module_name -> + let build_dir = + Path.Build.append_source (Super_context.context sctx |> Context.build_dir) dir + in + let* dir_contents = Dir_contents.get sctx ~dir:build_dir in + let* ml_sources = Dir_contents.ocaml dir_contents + and* scope = Dir_contents.dir dir_contents |> Dune_rules.Scope.DB.find_by_dir in + Dune_rules.Ml_sources.find_origin + ml_sources + ~libs:(Dune_rules.Scope.libs scope) + [ module_name ] + >>| (function + | Some (Library lib) when has_inline_tests lib -> + let lib_name = snd lib.name |> Lib_name.Local.to_string in + Ok (`Inline_tests_library lib_name) + | Some (Library _ | Executables _ | Melange _) | None -> Error `Not_a_test + | Some (Tests ({ exes; _ } as _test)) -> + let exe_names = Nonempty_list.to_list exes.names |> List.map ~f:snd in + if List.mem exe_names (Filename.remove_extension ml_file) ~equal:String.equal + then Ok (`Test_executable (Filename.remove_extension ml_file)) + else ( + match exe_names with + | [ single_exe ] -> Ok (`Test_executable single_exe) + | [] | _ :: _ -> Error `Not_an_entry_point)) +;; + +let all_tests_of_dir ~sctx parent_dir = let open Memo.O in let+ cram_candidates = cram_tests_of_dir parent_dir >>| List.filter_map ~f:(fun res -> Result.to_option res |> Option.map ~f:(fun test -> Source.Cram_test.path test |> Path.Source.to_string)) + and+ test_executable_candidates = + Source_tree.find_dir parent_dir + >>= function + | None -> Memo.return [] + | Some source_dir -> + Source_tree.Dir.filenames source_dir + |> Filename.Set.to_list + |> List.filter ~f:(fun f -> String.is_suffix f ~suffix:".ml") + |> Memo.List.filter ~f:(fun ml_file -> + classify_ml_test ~sctx ~dir:parent_dir ~ml_file >>| Result.is_ok) and+ dir_candidates = let* parent_source_dir = Source_tree.find_dir parent_dir in match parent_source_dir with @@ -53,23 +111,23 @@ let all_tests_of_dir parent_dir = >>| Source_tree.Dir.path >>| Path.Source.to_string) in - List.concat [ cram_candidates; dir_candidates ] + List.concat [ cram_candidates; test_executable_candidates; dir_candidates ] |> String.Set.of_list |> String.Set.to_list ;; -let explain_unsuccessful_search path ~parent_dir = +let explain_unsuccessful_search ~sctx path ~parent_dir = let open Memo.O in - let+ candidates = all_tests_of_dir parent_dir in + let+ candidates = all_tests_of_dir ~sctx parent_dir in User_error.raise ~hints:(User_message.did_you_mean (Path.Source.to_string path) ~candidates) [ Pp.textf "%S does not match any known test." (Path.Source.to_string path) ] ;; -(* [disambiguate_test_name path] is a function that takes in a - directory [path] and classifies it as either a cram test or a directory to +(* [disambiguate_test_name path] is a function that takes in a directory [path] + and classifies it as either a cram test, test executable, or a directory to run tests in. *) -let disambiguate_test_name path = +let disambiguate_test_name ~sctx path = match Path.Source.parent path with | None -> Memo.return @@ Test_kind.Runtest (Path.source Path.Source.root) | Some parent_dir -> @@ -80,27 +138,55 @@ let disambiguate_test_name path = (* If we find the cram test, then we request that is run. *) Memo.return (Test_kind.Cram (Path.source parent_dir, test)) | None -> - (* If we don't find it, then we assume the user intended a directory for - @runtest to be used. *) - Source_tree.find_dir path - >>= (function - (* We need to make sure that this directory or file exists. *) - | Some _ -> Memo.return (Test_kind.Runtest (Path.source path)) - | None -> explain_unsuccessful_search path ~parent_dir)) + (* Check for test executables *) + let filename = Path.Source.basename path in + let* test_kind_opt = + classify_ml_test ~sctx ~dir:parent_dir ~ml_file:filename + >>| function + | Ok (`Test_executable exe_name) -> Some (`Test_exe exe_name) + | Ok (`Inline_tests_library lib_name) -> Some (`Inline_tests lib_name) + | Error `Not_an_entry_point -> + User_error.raise + [ Pp.textf + "%S is used by multiple test executables and cannot be run directly." + filename + ] + | Error `Not_a_test -> None + in + (match test_kind_opt with + | Some (`Test_exe exe_name) -> + (* Found a test executable for this ML file *) + Memo.return (Test_kind.Test_executable (Path.source parent_dir, exe_name)) + | Some (`Inline_tests lib_name) -> + (* Found an inline tests library for this ML file *) + Memo.return (Test_kind.Inline_tests (Path.source parent_dir, lib_name)) + | None -> + (* If we don't find it, then we assume the user intended a directory for + @runtest to be used. *) + Source_tree.find_dir path + >>= (function + (* We need to make sure that this directory or file exists. *) + | Some _ -> Memo.return (Test_kind.Runtest (Path.source path)) + | None -> explain_unsuccessful_search ~sctx path ~parent_dir))) ;; -let make_request ~contexts ~to_cwd ~test_paths = +let make_request ~contexts ~scontexts ~to_cwd ~test_paths = List.map test_paths ~f:(fun dir -> let dir = Path.of_string dir |> Path.Expert.try_localize_external in - let contexts, src_dir = + let sctx, contexts, src_dir = match (Util.check_path contexts dir : Util.checked) with - | In_build_dir (context, dir) -> [ context ], dir + | In_build_dir (context, dir) -> + ( Dune_engine.Context_name.Map.find_exn scontexts (Context.name context) + , [ context ] + , dir ) | In_source_dir dir -> (* We need to adjust the path here to make up for the current working directory. *) let dir = Path.Source.L.relative Path.Source.root (to_cwd @ Path.Source.explode dir) in - contexts, dir + ( Dune_engine.Context_name.Map.find_exn scontexts Context_name.default + , contexts + , dir ) | In_private_context _ | In_install_dir _ -> User_error.raise [ Pp.textf "This path is internal to dune: %s" (Path.to_string_maybe_quoted dir) @@ -113,7 +199,7 @@ let make_request ~contexts ~to_cwd ~test_paths = ] in let open Action_builder.O in - Action_builder.of_memo (disambiguate_test_name src_dir) + Action_builder.of_memo (disambiguate_test_name ~sctx src_dir) >>| Test_kind.alias ~contexts >>= Alias.request) |> Action_builder.all_unit diff --git a/bin/runtest_common.mli b/bin/runtest_common.mli index 877d2cde0fe..a428492bee8 100644 --- a/bin/runtest_common.mli +++ b/bin/runtest_common.mli @@ -2,6 +2,7 @@ open Import val make_request : contexts:Context.t list + -> scontexts:Super_context.t Context_name.Map.t -> to_cwd:string list -> test_paths:string list -> unit Action_builder.t diff --git a/src/dune_rules/ml_sources.ml b/src/dune_rules/ml_sources.ml index 389f156a1a2..d1784c596c3 100644 --- a/src/dune_rules/ml_sources.ml +++ b/src/dune_rules/ml_sources.ml @@ -5,23 +5,27 @@ module Origin = struct type t = | Library of Library.t | Executables of Executables.t + | Tests of Tests.t | Melange of Melange_stanzas.Emit.t let loc = function | Library l -> l.buildable.loc | Executables e -> e.buildable.loc + | Tests t -> t.exes.buildable.loc | Melange mel -> mel.loc ;; let preprocess = function | Library l -> l.buildable.preprocess | Executables e -> e.buildable.preprocess + | Tests t -> t.exes.buildable.preprocess | Melange mel -> mel.preprocess ;; let to_dyn = function | Library _ -> Dyn.variant "Library" [ Dyn.Opaque ] | Executables _ -> Dyn.variant "Executables" [ Dyn.Opaque ] + | Tests _ -> Dyn.variant "Tests" [ Dyn.Opaque ] | Melange _ -> Dyn.variant "Melange" [ Dyn.Opaque ] ;; end @@ -58,10 +62,11 @@ module Per_stanza = struct type groups = { libraries : Library.t group_part list ; executables : Executables.t group_part list + ; tests : Tests.t group_part list ; melange_emits : Melange_stanzas.Emit.t group_part list } - let make { libraries = libs; executables = exes; melange_emits = emits } = + let make { libraries = libs; executables = exes; tests; melange_emits = emits } = let libraries, libraries_by_obj_dir = List.fold_left libs @@ -84,17 +89,29 @@ module Per_stanza = struct by_id, by_obj_dir) in let executables = - match - String.Map.of_list_map exes ~f:(fun (part : Executables.t group_part) -> - let first_exe = snd (Nonempty_list.hd part.stanza.names) in - let origin : Origin.t = Executables part.stanza in - first_exe, (origin, part.modules, part.obj_dir)) - with - | Ok x -> x - | Error (name, _, part) -> + let entries = + List.concat + [ List.map exes ~f:(fun (part : Executables.t group_part) -> + let first_exe = snd (Nonempty_list.hd part.stanza.names) in + let origin : Origin.t = Executables part.stanza in + first_exe, (origin, part.modules, part.obj_dir, part.stanza.buildable.loc)) + ; List.map tests ~f:(fun (part : Tests.t group_part) -> + let first_exe = snd (Nonempty_list.hd part.stanza.exes.names) in + let origin : Origin.t = Tests part.stanza in + ( first_exe + , (origin, part.modules, part.obj_dir, part.stanza.exes.buildable.loc) )) + ] + in + match String.Map.of_list entries with + | Ok map -> + String.Map.map map ~f:(fun (origin, modules, obj_dir, _loc) -> + origin, modules, obj_dir) + | Error (name, (_, _, _, loc1), (_, _, _, loc2)) -> User_error.raise - ~loc:part.stanza.buildable.loc - [ Pp.textf "Executable %S appears for the second time in this directory" name ] + ~loc:loc1 + [ Pp.textf "Executable %S appears for the second time in this directory" name + ; Pp.textf "Already defined at %s" (Loc.to_file_colon_line loc2) + ] in let melange_emits = match @@ -118,6 +135,8 @@ module Per_stanza = struct by_path (Library part.stanza, part.dir) part.sources) ; List.rev_concat_map exes ~f:(fun part -> by_path (Executables part.stanza, part.dir) part.sources) + ; List.rev_concat_map tests ~f:(fun part -> + by_path (Tests part.stanza, part.dir) part.sources) ; List.rev_concat_map emits ~f:(fun part -> by_path (Melange part.stanza, part.dir) part.sources) ] @@ -252,7 +271,7 @@ let find_origin (t : t) ~libs path = | Some origins -> Memo.List.filter_map origins ~f:(fun (origin, dir) -> match origin with - | Executables _ | Melange _ -> Memo.return (Some origin) + | Executables _ | Tests _ | Melange _ -> Memo.return (Some origin) | Library lib -> let src_dir = Path.drop_optional_build_context_src_exn (Path.build dir) in Lib.DB.available_by_lib_id libs (Local (Library.to_lib_id ~src_dir lib)) @@ -461,14 +480,18 @@ let modules_of_stanzas = | `Skip -> loop l acc | `Library y -> loop l { acc with libraries = y :: acc.libraries } | `Executables y -> loop l { acc with executables = y :: acc.executables } + | `Tests y -> loop l { acc with tests = y :: acc.tests } | `Melange_emit y -> loop l { acc with melange_emits = y :: acc.melange_emits }) in - fun l -> loop l { libraries = []; executables = []; melange_emits = [] } + fun l -> loop l { libraries = []; executables = []; tests = []; melange_emits = [] } in fun l -> - let { Per_stanza.libraries; executables; melange_emits } = rev_filter_partition l in + let { Per_stanza.libraries; executables; tests; melange_emits } = + rev_filter_partition l + in { Per_stanza.libraries = List.rev libraries ; executables = List.rev executables + ; tests = List.rev tests ; melange_emits = List.rev melange_emits } in @@ -496,6 +519,12 @@ let modules_of_stanzas = in `Executables { Per_stanza.stanza = exes; sources; modules; obj_dir; dir } in + let make_tests ~dir ~expander ~modules ~project tests = + let+ result = make_executables ~dir ~expander ~modules ~project tests.Tests.exes in + match result with + | `Executables group_part -> `Tests { group_part with stanza = tests } + | _ -> assert false + in fun stanzas ~expander ~project ~dir ~libs ~lookup_vlib ~modules ~include_subdirs -> Memo.parallel_map stanzas ~f:(fun stanza -> let enabled_if = @@ -532,7 +561,7 @@ let modules_of_stanzas = let obj_dir = Library.obj_dir lib ~dir in `Library { Per_stanza.stanza = lib; sources; modules; dir; obj_dir } | Executables.T exes -> make_executables ~dir ~expander ~modules ~project exes - | Tests.T { exes; _ } -> make_executables ~dir ~expander ~modules ~project exes + | Tests.T tests -> make_tests ~dir ~expander ~modules ~project tests | Melange_stanzas.Emit.T mel -> let obj_dir = Obj_dir.make_melange_emit ~dir ~name:mel.target in let+ sources, modules = @@ -652,9 +681,14 @@ let make part.stanza, part.modules, part.obj_dir) in let exes = - List.map - modules_of_stanzas.executables - ~f:(fun (part : _ Per_stanza.group_part) -> part.modules, part.obj_dir) + List.concat + [ List.map + modules_of_stanzas.executables + ~f:(fun { Per_stanza.modules; obj_dir; _ } -> modules, obj_dir) + ; List.map + modules_of_stanzas.tests + ~f:(fun { Per_stanza.modules; obj_dir; _ } -> modules, obj_dir) + ] in Artifacts_obj.make ~dir diff --git a/src/dune_rules/ml_sources.mli b/src/dune_rules/ml_sources.mli index 7b08c4388f3..9c5d4fde3c9 100644 --- a/src/dune_rules/ml_sources.mli +++ b/src/dune_rules/ml_sources.mli @@ -8,6 +8,7 @@ module Origin : sig type t = | Library of Library.t | Executables of Executables.t + | Tests of Tests.t | Melange of Melange_stanzas.Emit.t val preprocess : t -> Preprocess.With_instrumentation.t Preprocess.Per_module.t diff --git a/src/dune_rules/top_module.ml b/src/dune_rules/top_module.ml index aeed5582f97..3915455f4ca 100644 --- a/src/dune_rules/top_module.ml +++ b/src/dune_rules/top_module.ml @@ -42,6 +42,7 @@ let find_module sctx src = @@ fun () -> match origin with | Executables exes -> Exe_rules.rules ~sctx ~dir_contents ~scope ~expander exes + | Tests tests -> Exe_rules.rules ~sctx ~dir_contents ~scope ~expander tests.exes | Library lib -> Lib_rules.rules lib ~sctx ~dir_contents ~expander ~scope | Melange mel -> Melange_rules.setup_emit_cmj_rules ~sctx ~dir_contents ~expander ~scope mel diff --git a/test/blackbox-tests/test-cases/runtest-cmd-inline-tests.t b/test/blackbox-tests/test-cases/runtest-cmd-inline-tests.t new file mode 100644 index 00000000000..757307f3925 --- /dev/null +++ b/test/blackbox-tests/test-cases/runtest-cmd-inline-tests.t @@ -0,0 +1,100 @@ +Test running inline tests by specifying ML source files directly. + + $ cat > dune-project < (lang dune 3.21) + > EOF + +Set up a simple inline tests backend and libraries: + + $ cat > dune < (library + > (name test_backend) + > (modules ()) + > (inline_tests.backend + > (generate_runner (run sed "s/(\\\\*TEST:\\\\(.*\\\\)\\\\*)/let () = if \\"%{inline_tests}\\" = \\"enabled\\" then \\\\1;;/" %{impl-files})))) + > + > (library + > (name mylib) + > (modules lib) + > (inline_tests (backend test_backend))) + > + > (library + > (name regular_lib) + > (modules regular)) + > EOF + + $ cat > lib.ml < let add x y = x + y + > (*TEST: assert false *) + > EOF + + $ cat > regular.ml < let subtract x y = x - y + > EOF + +Error when specifying a library ML file without inline_tests: + + $ dune test regular.ml + Error: "regular.ml" does not match any known test. + [1] + +When specifying a library with inline_tests, it should run the test (and fail): + + $ dune test lib.ml + File "dune", line 10, characters 1-38: + 10 | (inline_tests (backend test_backend))) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fatal error: exception File ".mylib.inline-tests/main.ml-gen", line 2, characters 40-46: Assertion failed + [1] + +Error when specifying a non-existent ML file: + + $ dune test nonexistent.ml + Error: "nonexistent.ml" does not match any known test. + [1] + +Test that inline tests are included in suggestions: + + $ dune test li.ml + Error: "li.ml" does not match any known test. + Hint: did you mean lib.ml? + [1] + +Can run inline tests from _build directory: + + $ dune test _build/default/lib.ml + File "dune", line 10, characters 1-38: + 10 | (inline_tests (backend test_backend))) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fatal error: exception File ".mylib.inline-tests/main.ml-gen", line 2, characters 40-46: Assertion failed + [1] + +Test with multiple contexts: + + $ cat > dune-workspace < (lang dune 3.20) + > (context (default)) + > (context (default (name alt))) + > EOF + +Running inline tests in multiple contexts: + + $ dune test lib.ml + File "dune", line 10, characters 1-38: + 10 | (inline_tests (backend test_backend))) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fatal error: exception File ".mylib.inline-tests/main.ml-gen", line 2, characters 40-46: Assertion failed + File "dune", line 10, characters 1-38: + 10 | (inline_tests (backend test_backend))) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fatal error: exception File ".mylib.inline-tests/main.ml-gen", line 2, characters 40-46: Assertion failed + [1] + +Running inline tests in a specific build directory: + + $ dune test _build/alt/lib.ml + File "dune", line 10, characters 1-38: + 10 | (inline_tests (backend test_backend))) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fatal error: exception File ".mylib.inline-tests/main.ml-gen", line 2, characters 40-46: Assertion failed + [1] diff --git a/test/blackbox-tests/test-cases/runtest-cmd-tests.t b/test/blackbox-tests/test-cases/runtest-cmd-tests.t new file mode 100644 index 00000000000..dc2632612c9 --- /dev/null +++ b/test/blackbox-tests/test-cases/runtest-cmd-tests.t @@ -0,0 +1,215 @@ +Test running tests by specifying ML source files directly. + + $ cat > dune-project < (lang dune 3.21) + > EOF + +Set up a simple test with multiple test executables: + + $ cat > dune < (tests + > (names test1 test2) + > (modules test1 test2 test_lib)) + > EOF + + $ cat > test_lib.ml < let run_test name = + > Printf.printf "Running %s\n" name + > EOF + + $ cat > test1.ml < let () = + > Test_lib.run_test "test1"; + > assert true + > EOF + + $ cat > test2.ml < let () = + > Test_lib.run_test "test2"; + > assert false (* This test fails *) + > EOF + +Running tests by specifying ML files directly: + + $ dune test test1.ml + Running test1 + + $ dune test test2.ml + File "dune", line 2, characters 14-19: + 2 | (names test1 test2) + ^^^^^ + Running test2 + Fatal error: exception Assert_failure("test2.ml", 3, 2) + [1] + +Error when specifying a non-test ML file: + + $ cat > lib.ml < let x = 42 + > EOF + + $ cat > dune < (library + > (name mylib) + > (modules lib)) + > (tests + > (names test1 test2) + > (modules test1 test2 test_lib)) + > EOF + + $ dune test lib.ml + Error: "lib.ml" does not match any known test. + [1] + +Error when specifying a non-existent ML file: + + $ dune test nonexistent.ml + Error: "nonexistent.ml" does not match any known test. + [1] + + $ dune test test3.ml + Error: "test3.ml" does not match any known test. + Hint: did you mean test1.ml or test2.ml? + [1] + +Can run tests from _build directory: + + $ rm -rf _build + $ dune test _build/default/test1.ml + Running test1 + +Can specify multiple ML files: + + $ rm -rf _build + $ dune test test1.ml test2.ml + Running test1 + File "dune", line 5, characters 14-19: + 5 | (names test1 test2) + ^^^^^ + Running test2 + Fatal error: exception Assert_failure("test2.ml", 3, 2) + [1] + +-------------------------------------------------------------------------------- + +Test with a single test executable and multiple modules. + + $ cat > dune < (test + > (name single_test) + > (modules single_test helper1 helper2)) + > EOF + + $ cat > helper1.ml < let value = "from helper1" + > EOF + + $ cat > helper2.ml < let value = Helper1.value ^ " and helper2" + > EOF + + $ cat > single_test.ml < let () = + > Printf.printf "Test: %s\n" Helper2.value; + > assert false + > EOF + +Targeting the entry point: + + $ dune test single_test.ml + File "dune", line 2, characters 7-18: + 2 | (name single_test) + ^^^^^^^^^^^ + Test: from helper1 and helper2 + Fatal error: exception Assert_failure("single_test.ml", 3, 2) + [1] + +Targeting helper modules should also run the test: + + $ dune test helper1.ml + File "dune", line 2, characters 7-18: + 2 | (name single_test) + ^^^^^^^^^^^ + Test: from helper1 and helper2 + Fatal error: exception Assert_failure("single_test.ml", 3, 2) + [1] + + $ dune test helper2.ml + File "dune", line 2, characters 7-18: + 2 | (name single_test) + ^^^^^^^^^^^ + Test: from helper1 and helper2 + Fatal error: exception Assert_failure("single_test.ml", 3, 2) + [1] + +-------------------------------------------------------------------------------- + +Test with more complex module dependencies. +Set up: A <- B <- C -> D -> E where A and E are entry points. + + $ cat > dune < (tests + > (names a e) + > (modules a b c d e)) + > EOF + + $ cat > c.ml < let shared = "shared by both" + > EOF + + $ cat > b.ml < let helper = "used only by A via " ^ C.shared + > EOF + + $ cat > d.ml < let helper = "used only by E via " ^ C.shared + > EOF + + $ cat > a.ml < let () = + > Printf.printf "A using %s\n" B.helper; + > assert false + > EOF + + $ cat > e.ml < let () = + > Printf.printf "E using %s\n" D.helper; + > assert false + > EOF + +Targeting entry points A and E should run those specific tests: + + $ dune test a.ml + File "dune", line 2, characters 8-9: + 2 | (names a e) + ^ + A using used only by A via shared by both + Fatal error: exception Assert_failure("a.ml", 3, 2) + [1] + + $ dune test e.ml + File "dune", line 2, characters 10-11: + 2 | (names a e) + ^ + E using used only by E via shared by both + Fatal error: exception Assert_failure("e.ml", 3, 2) + [1] + +Targeting non-entry-point modules in a stanza with multiple tests errors. +Currently B, D, and C all error even though only C is actually shared: + + $ dune test b.ml + Error: "b.ml" is used by multiple test executables and cannot be run + directly. + [1] + + $ dune test d.ml + Error: "d.ml" is used by multiple test executables and cannot be run + directly. + [1] + + $ dune test c.ml + Error: "c.ml" is used by multiple test executables and cannot be run + directly. + [1] +