@@ -174,12 +174,14 @@ let dyn_of_metadata_result =
174174type full_block_result =
175175 { block : block_result
176176 ; metadata : metadata_result
177+ ; duration : Time.Span .t option
177178 }
178179
179180type sh_script =
180181 { script : Path .t
181182 ; cram_to_output : block_result Cram_lexer .block list
182183 ; metadata_file : Path .t option
184+ ; time_file : Path .t option
183185 }
184186
185187let read_exit_codes_and_prefix_maps file =
@@ -210,22 +212,49 @@ let read_exit_codes_and_prefix_maps file =
210212 loop [] (String. split ~on: '\000' s)
211213;;
212214
215+ let read_times file =
216+ let open Option.O in
217+ let * file = file in
218+ let + contents = Option. try_with (fun () -> Io. read_file file ~binary: true ) in
219+ String. split_lines contents
220+ |> List. filter_map ~f: (fun s ->
221+ match String. trim s with
222+ | "" -> None
223+ | s -> Float. of_string s >> | Time.Span. of_secs)
224+ ;;
225+
213226let read_and_attach_exit_codes (sh_script : sh_script )
214227 : full_block_result Cram_lexer. block list
215228 =
216229 let metadata_entries = read_exit_codes_and_prefix_maps sh_script.metadata_file in
217- let rec loop acc entries blocks =
230+ let times = read_times sh_script.time_file in
231+ let next_time = function
232+ | None -> None , None
233+ | Some [] -> None , Some []
234+ | Some (time :: times ) -> Some time, Some times
235+ in
236+ let rec loop acc entries blocks times =
218237 match blocks, entries with
219238 | [] , [] -> List. rev acc
220- | (Cram_lexer. Comment _ as comment ) :: blocks , _ ->
221- loop (comment :: acc) entries blocks
239+ | (Cram_lexer. Comment _ as comment ) :: blocks , __ ->
240+ loop (comment :: acc) entries blocks times
222241 | Command block :: blocks , metadata_entry :: entries ->
223- loop (Command { block; metadata = Present metadata_entry } :: acc) entries blocks
242+ let duration, times = next_time times in
243+ loop
244+ (Command { block; metadata = Present metadata_entry; duration } :: acc)
245+ entries
246+ blocks
247+ times
224248 | Cram_lexer. Command block :: blocks , [] ->
225- loop (Command { block; metadata = Missing_unreachable } :: acc) entries blocks
249+ let duration, times = next_time times in
250+ loop
251+ (Command { block; metadata = Missing_unreachable ; duration } :: acc)
252+ entries
253+ blocks
254+ times
226255 | [] , _ :: _ -> Code_error. raise " more blocks than metadata" []
227256 in
228- loop [] metadata_entries sh_script.cram_to_output
257+ loop [] metadata_entries sh_script.cram_to_output times
229258;;
230259
231260let line_number =
@@ -279,7 +308,7 @@ let sanitize ~parent_script cram_to_output : command_out Cram_lexer.block list =
279308 List. map cram_to_output ~f: (fun (t : full_block_result Cram_lexer.block ) ->
280309 match t with
281310 | Cram_lexer. Comment t -> Cram_lexer. Comment t
282- | Command { block; metadata } ->
311+ | Command { block; metadata; duration = _ } ->
283312 let output =
284313 match metadata with
285314 | Missing_unreachable -> " ***** UNREACHABLE *****"
@@ -338,7 +367,9 @@ let cram_commmands commands =
338367 Buffer. contents buf
339368;;
340369
341- let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber. t =
370+ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts (shell : Cram_stanza.Shell.t )
371+ : sh_script Fiber. t
372+ =
342373 let script = Path. relative temp_dir " main.sh" in
343374 let oc = Io. open_out ~binary: true script in
344375 Fiber. finalize ~finally: (fun () -> Fiber. return @@ close_out oc)
@@ -350,6 +381,18 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
350381 quote_for_sh path
351382 in
352383 let metadata_file = file " cram.metadata" in
384+ let user_shell_time_file =
385+ match shell with
386+ | Sh -> None
387+ | Bash -> Some (file " time" )
388+ in
389+ let * user_shell_time_file_sh_path =
390+ match user_shell_time_file with
391+ | None -> Fiber. return None
392+ | Some f ->
393+ let + file = sh_path f in
394+ Some file
395+ in
353396 let * metadata_file_sh_path = sh_path metadata_file in
354397 let i = ref 0 in
355398 let loop block =
@@ -370,11 +413,18 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
370413 (* Where we store the output of shell code written by the user *)
371414 let user_shell_code_output_file = file ~ext: " .output" in
372415 let + user_shell_code_output_file_sh_path = sh_path user_shell_code_output_file in
416+ let untimed_command =
417+ sprintf
418+ " . %s > %s 2>&1"
419+ user_shell_code_file_sh_path
420+ user_shell_code_output_file_sh_path
421+ in
373422 fprln
374423 oc
375- " . %s > %s 2>&1"
376- user_shell_code_file_sh_path
377- user_shell_code_output_file_sh_path;
424+ " %s"
425+ (match user_shell_time_file_sh_path with
426+ | None -> untimed_command
427+ | Some time_file -> sprintf " { time %s; } 2>> %s" untimed_command time_file);
378428 fprln
379429 oc
380430 {| printf " %%d\0 %%s\0 " $? " $%s" >> % s| }
@@ -396,10 +446,14 @@ let create_sh_script cram_stanzas ~temp_dir ~setup_scripts : sh_script Fiber.t =
396446 | External _ -> ()
397447 | In_build_dir _ -> fprln oc " rm -f %s" script_sh_path)
398448 in
449+ (* Needed for us to capture timing information. Users shouldn't really care,
450+ but if they do, they can always set this again within their command (and
451+ break timings) *)
452+ fprln oc " TIMEFORMAT=\" %%3R\" " ;
399453 let + cram_to_output = Fiber. sequential_map ~f: loop cram_stanzas in
400454 let command_count = ! i in
401455 let metadata_file = Option. some_if (command_count > 0 ) metadata_file in
402- { script; cram_to_output; metadata_file }
456+ { script; cram_to_output; metadata_file; time_file = user_shell_time_file }
403457;;
404458
405459let _display_with_bars s = List. iter (String. split_lines s) ~f: (Printf. eprintf " | %s\n " )
@@ -447,7 +501,7 @@ let run_cram_test
447501 (shell : Cram_stanza.Shell.t )
448502 =
449503 let open Fiber.O in
450- let * sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts in
504+ let * sh_script = create_sh_script cram_stanzas ~temp_dir ~setup_scripts shell in
451505 let env = make_run_env env ~temp_dir ~cwd in
452506 let open Fiber.O in
453507 let sh =
@@ -481,7 +535,19 @@ let run_cram_test
481535 sh
482536 [ Path. to_string sh_script.script ]
483537 >> | function
484- | Ok () -> read_and_attach_exit_codes sh_script |> sanitize ~parent_script: script
538+ | Ok () ->
539+ let detailed_output = read_and_attach_exit_codes sh_script in
540+ Dune_trace. emit Cram (fun () ->
541+ (* CR-someday rgrinberg: a little lame that we don't have a good way
542+ to relate these to the underlying process event. *)
543+ List. filter_map detailed_output ~f: (function
544+ | Comment _ -> None
545+ | Command { duration; block = { command; _ } ; _ } ->
546+ (match duration with
547+ | None -> None
548+ | Some duration -> Some { Dune_trace.Event.Cram. command; dur = duration }))
549+ |> Dune_trace.Event.Cram. test);
550+ sanitize ~parent_script: script detailed_output
485551 | Error `Timed_out ->
486552 let timeout_loc, timeout = Option. value_exn timeout in
487553 let timeout_set_message =
0 commit comments