@@ -4,11 +4,27 @@ module Test_kind = struct
44 type t =
55 | Runtest of Path .t
66 | Cram of Path .t * Source.Cram_test .t
7+ | Test_executable of
8+ { dir : Path .t
9+ ; exe_name : string
10+ }
11+ | Inline_tests of
12+ { dir : Path .t
13+ ; lib_name : Lib_name.Local .t
14+ }
715
816 let alias ~contexts = function
917 | Cram (dir , cram ) ->
1018 let name = Dune_engine.Alias.Name. of_string (Source.Cram_test. name cram) in
1119 Alias. in_dir ~name ~recursive: false ~contexts dir
20+ | Test_executable { dir; exe_name } ->
21+ let name = Dune_engine.Alias.Name. of_string (" runtest-" ^ exe_name) in
22+ Alias. in_dir ~name ~recursive: false ~contexts dir
23+ | Inline_tests { dir; lib_name } ->
24+ let name =
25+ Dune_engine.Alias.Name. of_string (" runtest-" ^ Lib_name.Local. to_string lib_name)
26+ in
27+ Alias. in_dir ~name ~recursive: false ~contexts dir
1228 | Runtest dir ->
1329 Alias. in_dir ~name: Dune_rules.Alias. runtest ~recursive: true ~contexts dir
1430 ;;
@@ -34,13 +50,65 @@ let find_cram_test cram_tests path =
3450 | Error (Dune_rules.Cram_rules. Missing_run_t _ ) | Ok _ -> None )
3551;;
3652
37- let all_tests_of_dir parent_dir =
53+ (* * [classify_ml_test ~sctx ~dir ~ml_file] checks if [ml_file] is part of a
54+ (tests) or (library (inline_tests ...)) stanza in [dir]. Returns:
55+ - [Ok (`Test_executable exe_name)] or [Ok (`Inline_tests_library lib_name)]
56+ - [Error `Not_an_entry_point] if the file belongs to a multi-test stanza
57+ - [Error `Not_a_test] if not part of any test stanza *)
58+ let classify_ml_test ~sctx ~dir ~ml_file =
59+ let open Memo.O in
60+ let ml_file_no_ext = Filename. remove_extension ml_file in
61+ match Dune_lang.Module_name. of_string_opt ml_file_no_ext with
62+ | None -> Memo. return (Error `Not_a_test )
63+ | Some module_name ->
64+ let * dir_contents =
65+ Dir_contents. get
66+ sctx
67+ ~dir:
68+ (Path.Build. append_source (Context. build_dir (Super_context. context sctx)) dir)
69+ in
70+ let * ml_sources = Dir_contents. ocaml dir_contents
71+ and * scope = Dir_contents. dir dir_contents |> Dune_rules.Scope.DB. find_by_dir in
72+ Dune_rules.Ml_sources. find_origin
73+ ml_sources
74+ ~libs: (Dune_rules.Scope. libs scope)
75+ [ module_name ]
76+ >> | (function
77+ | Some (Library lib ) ->
78+ if
79+ Dune_rules.Sub_system_name.Map. mem
80+ lib.sub_systems
81+ Dune_rules.Inline_tests_info.Tests. name
82+ then Ok (`Inline_tests_library (snd lib.name))
83+ else Error `Not_a_test
84+ | Some (Executables _ | Melange _ ) | None -> Error `Not_a_test
85+ | Some (Tests { exes; _ } ) ->
86+ let exe_names = Nonempty_list. to_list exes.names |> List. map ~f: snd in
87+ if List. mem exe_names ml_file_no_ext ~equal: String. equal
88+ then Ok (`Test_executable ml_file_no_ext)
89+ else (
90+ match exe_names with
91+ | [ single_exe ] -> Ok (`Test_executable single_exe)
92+ | [] | _ :: _ :: _ -> Error `Not_an_entry_point ))
93+ ;;
94+
95+ let all_tests_of_dir ~sctx parent_dir =
3896 let open Memo.O in
3997 let + cram_candidates =
4098 cram_tests_of_dir parent_dir
4199 >> | List. filter_map ~f: (fun res ->
42100 Result. to_option res
43101 |> Option. map ~f: (fun test -> Source.Cram_test. path test |> Path.Source. to_string))
102+ and + ml_test_candidates =
103+ Source_tree. find_dir parent_dir
104+ >> = function
105+ | None -> Memo. return []
106+ | Some source_dir ->
107+ Source_tree.Dir. filenames source_dir
108+ |> Filename.Set. to_list
109+ |> List. filter ~f: (String. is_suffix ~suffix: " .ml" )
110+ |> Memo.List. filter ~f: (fun ml_file ->
111+ classify_ml_test ~sctx ~dir: parent_dir ~ml_file >> | Result. is_ok)
44112 and + dir_candidates =
45113 let * parent_source_dir = Source_tree. find_dir parent_dir in
46114 match parent_source_dir with
@@ -53,23 +121,22 @@ let all_tests_of_dir parent_dir =
53121 >> | Source_tree.Dir. path
54122 >> | Path.Source. to_string)
55123 in
56- List. concat [ cram_candidates; dir_candidates ]
124+ List. concat [ cram_candidates; ml_test_candidates; dir_candidates ]
57125 |> String.Set. of_list
58126 |> String.Set. to_list
59127;;
60128
61- let explain_unsuccessful_search path ~parent_dir =
129+ let explain_unsuccessful_search ~ sctx path ~parent_dir =
62130 let open Memo.O in
63- let + candidates = all_tests_of_dir parent_dir in
131+ let + candidates = all_tests_of_dir ~sctx parent_dir in
64132 User_error. raise
65133 ~hints: (User_message. did_you_mean (Path.Source. to_string path) ~candidates )
66134 [ Pp. textf " %S does not match any known test." (Path.Source. to_string path) ]
67135;;
68136
69- (* [disambiguate_test_name path] is a function that takes in a
70- directory [path] and classifies it as either a cram test or a directory to
71- run tests in. *)
72- let disambiguate_test_name path =
137+ (* [disambiguate_test_name ~sctx path] classifies [path] as a cram test,
138+ ml test executable, inline tests library, or a directory to run @runtest in. *)
139+ let disambiguate_test_name ~sctx path =
73140 match Path.Source. parent path with
74141 | None -> Memo. return @@ Test_kind. Runtest (Path. source Path.Source. root)
75142 | Some parent_dir ->
@@ -80,27 +147,50 @@ let disambiguate_test_name path =
80147 (* If we find the cram test, then we request that is run. *)
81148 Memo. return (Test_kind. Cram (Path. source parent_dir, test))
82149 | None ->
83- (* If we don't find it, then we assume the user intended a directory for
84- @runtest to be used. *)
85- Source_tree. find_dir path
86- >> = (function
87- (* We need to make sure that this directory or file exists. *)
88- | Some _ -> Memo. return (Test_kind. Runtest (Path. source path))
89- | None -> explain_unsuccessful_search path ~parent_dir ))
150+ (* Check if it's an ML test file *)
151+ let filename = Path.Source. basename path in
152+ let * ml_test =
153+ if String. is_suffix filename ~suffix: " .ml"
154+ then classify_ml_test ~sctx ~dir: parent_dir ~ml_file: filename
155+ else Memo. return (Error `Not_a_test )
156+ in
157+ (match ml_test with
158+ | Ok (`Test_executable exe_name ) ->
159+ Memo. return
160+ (Test_kind. Test_executable { dir = Path. source parent_dir; exe_name })
161+ | Ok (`Inline_tests_library lib_name ) ->
162+ Memo. return (Test_kind. Inline_tests { dir = Path. source parent_dir; lib_name })
163+ | Error `Not_an_entry_point ->
164+ User_error. raise
165+ [ Pp. textf
166+ " %S is used by multiple test executables and cannot be run directly."
167+ filename
168+ ]
169+ | Error `Not_a_test ->
170+ (* Assume the user intended a directory for @runtest to be used. *)
171+ Source_tree. find_dir path
172+ >> = (function
173+ (* We need to make sure that this directory or file exists. *)
174+ | Some _ -> Memo. return (Test_kind. Runtest (Path. source path))
175+ | None -> explain_unsuccessful_search ~sctx path ~parent_dir )))
90176;;
91177
92- let make_request ~contexts ~to_cwd ~test_paths =
178+ let make_request ~scontexts ~to_cwd ~test_paths =
179+ let contexts =
180+ Context_name.Map. to_list_map scontexts ~f: (fun _ -> Super_context. context)
181+ in
93182 List. map test_paths ~f: (fun dir ->
94183 let dir = Path. of_string dir |> Path.Expert. try_localize_external in
95- let contexts, src_dir =
184+ let sctx, contexts, src_dir =
96185 match (Util. check_path contexts dir : Util.checked ) with
97- | In_build_dir (context , dir ) -> [ context ], dir
186+ | In_build_dir (context , dir ) ->
187+ Context_name.Map. find_exn scontexts (Context. name context), [ context ], dir
98188 | In_source_dir dir ->
99189 (* We need to adjust the path here to make up for the current working directory. *)
100190 let dir =
101191 Path.Source.L. relative Path.Source. root (to_cwd @ Path.Source. explode dir)
102192 in
103- contexts, dir
193+ Context_name.Map. find_exn scontexts Context_name. default, contexts, dir
104194 | In_private_context _ | In_install_dir _ ->
105195 User_error. raise
106196 [ Pp. textf " This path is internal to dune: %s" (Path. to_string_maybe_quoted dir)
@@ -113,7 +203,7 @@ let make_request ~contexts ~to_cwd ~test_paths =
113203 ]
114204 in
115205 let open Action_builder.O in
116- Action_builder. of_memo (disambiguate_test_name src_dir)
206+ Action_builder. of_memo (disambiguate_test_name ~sctx src_dir)
117207 >> | Test_kind. alias ~contexts
118208 >> = Alias. request)
119209 |> Action_builder. all_unit
0 commit comments