Skip to content

Commit 9d07a77

Browse files
committed
Check the presence and contents of captured output files
1 parent 049c5fb commit 9d07a77

File tree

8 files changed

+176
-87
lines changed

8 files changed

+176
-87
lines changed

core/Run.ml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,15 @@ let conditional_wrap condition wrapper func =
393393

394394
let wrap_test_function ~with_storage ~flip_xfail_outcome test
395395
(func : unit -> unit Promise.t) : unit -> unit Promise.t =
396-
func
397-
|> conditional_wrap with_storage (with_store_exception test)
398-
|> conditional_wrap flip_xfail_outcome (with_flip_xfail_outcome test)
399-
|> protect_globals test
400-
|> conditional_wrap with_storage (Store.with_result_capture test)
401-
|> with_current_test_ref test
396+
fun () ->
397+
Store.remove_stashed_output_files test;
398+
(func
399+
|> conditional_wrap with_storage (with_store_exception test)
400+
|> conditional_wrap flip_xfail_outcome (with_flip_xfail_outcome test)
401+
|> protect_globals test
402+
|> conditional_wrap with_storage (Store.with_result_capture test)
403+
|> with_current_test_ref test
404+
) ()
402405

403406
let to_alcotest_internal ~alcotest_skip ~with_storage ~flip_xfail_outcome tests
404407
=
@@ -571,6 +574,14 @@ let print_status ~highlight_test ~always_show_unchecked_output
571574
| Split_stdout_stderr _ -> "separate stdout and stderr"
572575
in
573576
printf "%sChecked output: %s\n" bullet text);
577+
(match test.checked_output_files with
578+
| [] -> ()
579+
| xs ->
580+
let names =
581+
Helpers.list_map (fun (x : T.checked_output_file) -> x.name) xs
582+
in
583+
printf "%sChecked output files: %s\n"
584+
bullet (String.concat ", " names));
574585
(* Details about results *)
575586
(match status.expectation.expected_output with
576587
| Error (Missing_files [ path ]) ->

core/Store.ml

Lines changed: 122 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ let get_snapshot_path (test : T.test) default_name
257257

258258
let get_file_snapshot_path (test : T.test) (x : T.checked_output_file) =
259259
match x.options.snapshot_path with
260-
| None -> get_expectation_workspace () / test.id / x.name
260+
| None -> get_expectation_workspace () / test.id / ("file-" ^ x.name)
261261
| Some path -> path
262262

263263
let short_name_of_checked_output_options default_name
@@ -298,71 +298,57 @@ let capture_paths_of_test (test : T.test) : capture_paths list =
298298
path_to_output = get_output_path test unchecked_filename;
299299
}
300300
in
301-
let std_output_paths =
302-
match test.checked_output with
303-
| Ignore_output -> [ unchecked_paths ]
304-
| Stdout options ->
305-
[
306-
{
307-
short_name =
308-
short_name_of_checked_output_options stdout_filename options;
309-
path_to_expected_output =
310-
Some (get_snapshot_path test stdout_filename options);
311-
path_to_output = get_output_path test stdout_filename;
312-
};
313-
unchecked_paths;
314-
]
315-
| Stderr options ->
316-
[
317-
{
318-
short_name =
319-
short_name_of_checked_output_options stderr_filename options;
320-
path_to_expected_output =
321-
Some (get_snapshot_path test stderr_filename options);
322-
path_to_output = get_output_path test stderr_filename;
323-
};
324-
unchecked_paths;
325-
]
326-
| Stdxxx options ->
327-
[
328-
{
329-
short_name =
330-
short_name_of_checked_output_options stdxxx_filename options;
331-
path_to_expected_output =
332-
Some (get_snapshot_path test stdxxx_filename options);
333-
path_to_output = get_output_path test stdxxx_filename;
334-
};
335-
]
336-
| Split_stdout_stderr (stdout_options, stderr_options) ->
337-
[
338-
{
339-
short_name =
340-
short_name_of_checked_output_options stdout_filename stdout_options;
341-
path_to_expected_output =
342-
Some (get_snapshot_path test stdout_filename stdout_options);
343-
path_to_output = get_output_path test stdout_filename;
344-
};
345-
{
346-
short_name =
347-
short_name_of_checked_output_options stderr_filename stderr_options;
348-
path_to_expected_output =
349-
Some (get_snapshot_path test stderr_filename stderr_options);
350-
path_to_output = get_output_path test stderr_filename;
351-
};
352-
]
353-
in
354-
let output_file_paths =
355-
test.checked_output_files
356-
|> Helpers.list_map (fun (x : T.checked_output_file) ->
357-
{
358-
short_name = x.name;
359-
path_to_expected_output =
360-
Some (get_file_snapshot_path test x);
361-
path_to_output = get_output_file_path test x;
362-
}
363-
)
364-
in
365-
std_output_paths @ output_file_paths
301+
match test.checked_output with
302+
| Ignore_output -> [ unchecked_paths ]
303+
| Stdout options ->
304+
[
305+
{
306+
short_name =
307+
short_name_of_checked_output_options stdout_filename options;
308+
path_to_expected_output =
309+
Some (get_snapshot_path test stdout_filename options);
310+
path_to_output = get_output_path test stdout_filename;
311+
};
312+
unchecked_paths;
313+
]
314+
| Stderr options ->
315+
[
316+
{
317+
short_name =
318+
short_name_of_checked_output_options stderr_filename options;
319+
path_to_expected_output =
320+
Some (get_snapshot_path test stderr_filename options);
321+
path_to_output = get_output_path test stderr_filename;
322+
};
323+
unchecked_paths;
324+
]
325+
| Stdxxx options ->
326+
[
327+
{
328+
short_name =
329+
short_name_of_checked_output_options stdxxx_filename options;
330+
path_to_expected_output =
331+
Some (get_snapshot_path test stdxxx_filename options);
332+
path_to_output = get_output_path test stdxxx_filename;
333+
};
334+
]
335+
| Split_stdout_stderr (stdout_options, stderr_options) ->
336+
[
337+
{
338+
short_name =
339+
short_name_of_checked_output_options stdout_filename stdout_options;
340+
path_to_expected_output =
341+
Some (get_snapshot_path test stdout_filename stdout_options);
342+
path_to_output = get_output_path test stdout_filename;
343+
};
344+
{
345+
short_name =
346+
short_name_of_checked_output_options stderr_filename stderr_options;
347+
path_to_expected_output =
348+
Some (get_snapshot_path test stderr_filename stderr_options);
349+
path_to_output = get_output_path test stderr_filename;
350+
};
351+
]
366352

367353
let describe_unchecked_output (output : T.checked_output_kind) : string option =
368354
match output with
@@ -419,7 +405,11 @@ let set_expected_output (test : T.test) (capture_paths : capture_paths list)
419405

420406
let clear_expected_output (test : T.test) =
421407
test |> capture_paths_of_test
422-
|> List.iter (fun x -> Option.iter remove_file x.path_to_expected_output)
408+
|> List.iter (fun x -> Option.iter remove_file x.path_to_expected_output);
409+
test.checked_output_files
410+
|> List.iter (fun (x : T.checked_output_file) ->
411+
remove_file (get_file_snapshot_path test x)
412+
)
423413

424414
let read_name_file ~dir =
425415
let name_file_path = get_name_file_path_from_dir dir in
@@ -644,6 +634,12 @@ let stash_output_file (test : T.test) (x : T.checked_output_file) : unit =
644634
let dst = get_output_file_path test x in
645635
Helpers.copy_file src dst
646636

637+
let remove_stashed_output_files (test : T.test) =
638+
test.checked_output_files
639+
|> List.iter (fun (x : T.checked_output_file) ->
640+
remove_file (get_output_file_path test x)
641+
)
642+
647643
(**************************************************************************)
648644
(* High-level interface *)
649645
(**************************************************************************)
@@ -679,20 +675,52 @@ let get_expectation (test : T.test) (paths : capture_paths list) : T.expectation
679675
| Ok x -> Ok (expected_output_of_data test.checked_output x)
680676
| Error missing_files -> Error (T.Missing_files missing_files)
681677
in
682-
{ expected_outcome = test.expected_outcome; expected_output }
678+
let expected_output_files =
679+
test.checked_output_files
680+
|> Helpers.list_map (fun (checked_file : T.checked_output_file) ->
681+
match read_file (get_file_snapshot_path test checked_file) with
682+
| Ok contents ->
683+
Ok ({ checked_file; contents } : T.checked_output_file_with_contents)
684+
| Error _path ->
685+
Error checked_file
686+
)
687+
in
688+
{ expected_outcome = test.expected_outcome;
689+
expected_output;
690+
expected_output_files }
683691

692+
(* Fail if any capture file is missing *)
684693
let get_result (test : T.test) (paths : capture_paths list) :
685694
(T.result, T.missing_files) Result.t =
686695
match get_completion_status test with
687696
| Error missing_file -> Error (Missing_files [ missing_file ])
688697
| Ok completion_status -> (
698+
(* captured output files *)
699+
let captured_output_files, missing_output_files =
700+
test.checked_output_files
701+
|> Helpers.list_map (fun (checked_file : T.checked_output_file) ->
702+
match read_file (get_output_file_path test checked_file) with
703+
| Ok contents ->
704+
Ok ({ checked_file; contents } : T.checked_output_file_with_contents)
705+
| Error missing_file -> Error missing_file
706+
)
707+
|> Helpers.split_result_list
708+
in
709+
(* captured standard output *)
689710
let opt_captured_output =
690711
paths |> get_output |> list_result_of_result_list
691712
|> Result.map (captured_output_of_data test.checked_output)
692713
in
693-
match opt_captured_output with
694-
| Error missing_files -> Error (Missing_files missing_files)
695-
| Ok captured_output -> Ok { completion_status; captured_output })
714+
match opt_captured_output, missing_output_files with
715+
| Ok captured_output, [] ->
716+
Ok { completion_status;
717+
captured_output;
718+
captured_output_files }
719+
| Ok _, missing_output_files ->
720+
Error (Missing_files missing_output_files)
721+
| Error missing_std_files, missing_output_files ->
722+
Error (Missing_files (missing_std_files @ missing_output_files))
723+
)
696724

697725
let get_status (test : T.test) : T.status =
698726
let paths = capture_paths_of_test test in
@@ -716,13 +744,33 @@ let outcome_of_expectation_and_result
716744
(expect : T.expectation) (result : T.result)
717745
: T.outcome * bool =
718746
let has_expected_output, output_matches =
719-
match (expect.expected_output, result.captured_output) with
747+
match expect.expected_output, result.captured_output with
720748
| Ok output1, output2 when T.equal_checked_output output1 output2 ->
721749
(true, true)
722750
| Ok _, _ -> (true, false)
723751
| Error _, _ -> (false, true)
724752
in
725-
outcome_of_pair result.completion_status output_matches, has_expected_output
753+
let has_expected_output_files, output_files_match =
754+
(* Assume the lists of expected files and captured files are complete
755+
and in the same order. *)
756+
List.fold_left2 (
757+
fun
758+
(has_expected_output_files, output_files_match)
759+
(expect : (T.checked_output_file_with_contents, T.checked_output_file) Result.t)
760+
(result : T.checked_output_file_with_contents) ->
761+
match expect with
762+
| Ok expected ->
763+
assert (expected.checked_file = result.checked_file);
764+
if expected.contents = result.contents then
765+
has_expected_output_files, output_files_match
766+
else
767+
has_expected_output_files, false
768+
| Error missing_expected_file ->
769+
assert (missing_expected_file = result.checked_file);
770+
false, false
771+
) (true, true) expect.expected_output_files result.captured_output_files
772+
in
773+
outcome_of_pair result.completion_status (output_matches && output_files_match), (has_expected_output && has_expected_output_files)
726774

727775
let status_summary_of_status (status : T.status) : T.status_summary =
728776
match status.result with
@@ -771,6 +819,7 @@ type changed = Changed | Unchanged
771819

772820
exception Local_error of string
773821

822+
(* TODO: approve the captured output files *)
774823
let approve_new_output (test : T.test) : (changed, string) Result.t =
775824
match test.skipped with
776825
| Some _reason -> Ok Unchanged

core/Store.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ val mark_test_as_timed_out : Types.test -> unit
120120
(* Copy a checked output file identified by its unique name (not its path) *)
121121
val stash_output_file : Types.test -> Types.checked_output_file -> unit
122122

123+
(* Clean up stashed output files from previous runs. *)
124+
val remove_stashed_output_files : Types.test -> unit
125+
123126
(**************************************************************************)
124127
(* User-facing utilities *)
125128
(**************************************************************************)

core/Testo.ml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ type completion_status = T.completion_status =
2626
type fail_reason = T.fail_reason = Raised_exception | Incorrect_output | Timeout
2727
type outcome = T.outcome = Succeeded | Failed of fail_reason
2828

29+
(* abstract types *)
30+
type checked_output_kind = T.checked_output_kind
31+
type checked_output_file = T.checked_output_file
32+
type checked_output_file_with_contents = T.checked_output_file_with_contents
33+
2934
type captured_output = T.captured_output =
3035
| Ignored of string
3136
| Captured_stdout of string * string
@@ -43,13 +48,16 @@ type expected_output = T.expected_output =
4348
type result = T.result = {
4449
completion_status : completion_status;
4550
captured_output : captured_output;
51+
captured_output_files : checked_output_file_with_contents list;
4652
}
4753

4854
type missing_files = T.missing_files = Missing_files of Fpath.t list
4955

5056
type expectation = T.expectation = {
5157
expected_outcome : expected_outcome;
5258
expected_output : (expected_output, missing_files) Result.t;
59+
expected_output_files :
60+
(checked_output_file_with_contents, checked_output_file) Result.t list;
5361
}
5462

5563
type status = T.status = {
@@ -81,10 +89,6 @@ type subcommand_result = Cmd.subcommand_result =
8189
module Promise = Promise
8290
module Tag = Tag
8391

84-
(* abstract types *)
85-
type checked_output_kind = T.checked_output_kind
86-
type checked_output_file = T.checked_output_file
87-
8892
type t = T.test = {
8993
id : string;
9094
internal_full_name : string;

core/Testo.mli

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ type completion_status =
3434
type fail_reason = Raised_exception | Incorrect_output | Timeout
3535
type outcome = Succeeded | Failed of fail_reason
3636

37+
type checked_output_file
38+
(** A test's output file whose contents captured from stdout or stderr
39+
should be checked and reported when it changes. *)
40+
41+
type checked_output_file_with_contents
42+
(** A test's output file that is produced by the test function, checked,
43+
and reported when it changes. *)
44+
3745
type captured_output =
3846
| Ignored of string (** unchecked combined output *)
3947
| Captured_stdout of string * string (** stdout, unchecked output *)
@@ -51,13 +59,16 @@ type expected_output =
5159
type result = {
5260
completion_status : completion_status;
5361
captured_output : captured_output;
62+
captured_output_files : checked_output_file_with_contents list;
5463
}
5564

5665
type missing_files = Types.missing_files = Missing_files of Fpath.t list
5766

5867
type expectation = {
5968
expected_outcome : expected_outcome;
6069
expected_output : (expected_output, missing_files) Result.t;
70+
expected_output_files :
71+
(checked_output_file_with_contents, checked_output_file) Result.t list;
6172
}
6273

6374
type status = {
@@ -111,10 +122,6 @@ val split_stdout_stderr :
111122
checked_output_kind
112123
(** Same as {!val:stdxxx} but keep stdout and stderr separate. *)
113124

114-
type checked_output_file
115-
(** A test's output file whose contents should be checked and reported
116-
when it changes. *)
117-
118125
val checked_output_file :
119126
?path:Fpath.t ->
120127
?snapshot_path:Fpath.t ->

0 commit comments

Comments
 (0)