288 lines
7 KiB
OCaml
288 lines
7 KiB
OCaml
open Sys
|
|
open Array
|
|
open List
|
|
open Filename
|
|
open Printf
|
|
open Auxiliary
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Settings. *)
|
|
|
|
let create_expected =
|
|
ref false
|
|
|
|
let verbosity =
|
|
ref 0
|
|
|
|
let usage =
|
|
sprintf "Usage: %s\n" argv.(0)
|
|
|
|
let spec = Arg.align [
|
|
"--create-expected", Arg.Set create_expected,
|
|
" recreate the expected-output files";
|
|
"--verbosity", Arg.Int ((:=) verbosity),
|
|
" set the verbosity level (0-2)";
|
|
]
|
|
|
|
let () =
|
|
Arg.parse spec (fun _ -> ()) usage
|
|
|
|
let create_expected =
|
|
!create_expected
|
|
|
|
let verbosity =
|
|
!verbosity
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Logging. *)
|
|
|
|
(* 0 is minimal verbosity;
|
|
1 shows some progress messages;
|
|
2 is maximal verbosity. *)
|
|
|
|
let log level format =
|
|
kprintf (fun s ->
|
|
if level <= verbosity then
|
|
print_string s
|
|
) format
|
|
|
|
(* Extend [fail] to display an information message along the way.
|
|
The message is immediately emitted by the worker, depending on
|
|
the verbosity level, whereas the failure message is sent back
|
|
to the master. *)
|
|
|
|
let fail id format =
|
|
log 1 "[FAIL] %s\n%!" id;
|
|
fail format
|
|
|
|
(* When issuing an external command, log it along the way. *)
|
|
|
|
let command cmd =
|
|
log 2 "%s\n%!" cmd;
|
|
command cmd
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Paths. *)
|
|
|
|
let root =
|
|
absolute_directory ".."
|
|
|
|
let src =
|
|
root
|
|
|
|
let good =
|
|
root ^ "/tests"
|
|
|
|
let good_slash filename =
|
|
good ^ "/" ^ filename
|
|
|
|
let joujou =
|
|
src ^ "/joujou"
|
|
|
|
let gcc =
|
|
sprintf "gcc -O2 -I %s" src
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Test files. *)
|
|
|
|
let thisfile =
|
|
"this input file"
|
|
|
|
let lambda basename =
|
|
basename ^ ".lambda"
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Test inputs and outputs. *)
|
|
|
|
(* A test input is a basename, without the .lambda extension. *)
|
|
|
|
type input =
|
|
| PositiveTest of filename
|
|
|
|
type inputs = input list
|
|
|
|
let print_input = function
|
|
| PositiveTest basename ->
|
|
basename
|
|
|
|
type outcome =
|
|
| OK
|
|
| Fail of string (* message *)
|
|
|
|
let print_outcome = function
|
|
| OK ->
|
|
""
|
|
| Fail msg ->
|
|
msg
|
|
|
|
type output =
|
|
input * outcome
|
|
|
|
type outputs = output list
|
|
|
|
let print_output (input, outcome) =
|
|
printf "\n[FAIL] %s\n%s"
|
|
(print_input input)
|
|
(print_outcome outcome)
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Auxiliary functions. *)
|
|
|
|
let check_expected directory id result expected =
|
|
let cmd = sep ["cd"; directory; "&&"; "cp"; "-f"; result; expected] in
|
|
let copy() =
|
|
if command cmd <> 0 then
|
|
fail id "Failed to create %s.\n" expected
|
|
in
|
|
(* If we are supposed to create the [expected] file, do so. *)
|
|
if create_expected then
|
|
copy()
|
|
(* Otherwise, check that the file [expected] exists. If it does not exist,
|
|
create it by renaming [result] to [expected], then fail and invite the
|
|
user to review the newly created file. *)
|
|
else if not (file_exists (directory ^ "/" ^ expected)) then begin
|
|
copy();
|
|
let cmd = sep ["more"; directory ^ "/" ^ expected] in
|
|
fail id "The file %s did not exist.\n\
|
|
I have just created it. Please review it.\n %s\n"
|
|
expected cmd
|
|
end
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Running a positive test. *)
|
|
|
|
(*
|
|
Conventions:
|
|
The file %.c stores the output of joujou.
|
|
The file %.exe is the compiled program produced by gcc.
|
|
The file %.out stores the output of the compiled program.
|
|
The file %.exp stores the expected output.
|
|
The file %.err stores error output (at several stages).
|
|
*)
|
|
|
|
let process_positive_test (base : string) : unit =
|
|
|
|
(* Display an information message. *)
|
|
log 2 "Testing %s...\n%!" base;
|
|
|
|
(* A suggested command. *)
|
|
let more filename =
|
|
sep ["more"; good_slash filename]
|
|
in
|
|
|
|
(* Run joujou. *)
|
|
let source = lambda base in
|
|
let c = base ^ ".c" in
|
|
let errors = base ^ ".err" in
|
|
let cmd = sep (
|
|
"cd" :: good :: "&&" ::
|
|
joujou :: source :: sprintf ">%s" c :: sprintf "2>%s" errors :: []
|
|
) in
|
|
if command cmd <> 0 then
|
|
fail base "joujou fails on this source file.\n\
|
|
To see the source file:\n %s\n\
|
|
To see the error messages:\n %s\n"
|
|
(more source) (more errors);
|
|
|
|
(* Run the C compiler. *)
|
|
let binary = base ^ ".exe" in
|
|
let errors = base ^ ".err" in
|
|
let cmd = sep (
|
|
"cd" :: good :: "&&" ::
|
|
gcc :: c :: "-o" :: binary :: sprintf "2>%s" errors :: []
|
|
) in
|
|
if command cmd <> 0 then
|
|
fail base "The C compiler rejects the C file.\n\
|
|
To see the C file:\n %s\n\
|
|
To see the compiler's error messages:\n %s\n"
|
|
(more c) (more errors);
|
|
|
|
(* Run the compiled program. *)
|
|
let out = base ^ ".out" in
|
|
let cmd = sep (
|
|
"cd" :: good :: "&&" ::
|
|
sprintf "./%s" binary :: sprintf ">%s" out :: "2>&1" :: []
|
|
) in
|
|
if command cmd <> 0 then
|
|
fail base "The compiled program fails.\n\
|
|
To see its output:\n %s\n" (more out);
|
|
|
|
(* Check that the file [exp] exists. *)
|
|
let exp = base ^ ".exp" in
|
|
check_expected good base out exp;
|
|
|
|
(* Check that the output coincides with what was expected. *)
|
|
let cmd = sep ("cd" :: good :: "&&" :: "diff" :: exp :: out :: []) in
|
|
if command (silent cmd) <> 0 then
|
|
fail base "joujou accepts this input file, but produces incorrect output.\n\
|
|
To see the difference:\n (%s)\n"
|
|
cmd;
|
|
|
|
(* Succeed. *)
|
|
log 1 "[OK] %s\n%!" base
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Running a test. *)
|
|
|
|
let process input : output =
|
|
try
|
|
begin match input with
|
|
| PositiveTest basenames ->
|
|
process_positive_test basenames
|
|
end;
|
|
input, OK
|
|
with Failure msg ->
|
|
input, Fail msg
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* [run] runs a bunch of tests in sequence. *)
|
|
|
|
let run (inputs : inputs) : outputs =
|
|
flush stdout; flush stderr;
|
|
let outputs = map process inputs in
|
|
outputs
|
|
|
|
(* -------------------------------------------------------------------------- *)
|
|
|
|
(* Main. *)
|
|
|
|
(* Menhir can accept several .mly files at once. By convention, if several
|
|
files have the same name up to a numeric suffix, then they belong in a
|
|
single group and should be fed together to Menhir. *)
|
|
|
|
let inputs directory : filename list =
|
|
readdir directory
|
|
|> to_list
|
|
|> filter (has_suffix ".lambda")
|
|
|> map chop_extension
|
|
|> sort compare
|
|
|
|
let positive : inputs =
|
|
inputs good
|
|
|> map (fun basename -> PositiveTest basename)
|
|
|
|
let inputs =
|
|
positive
|
|
|
|
let outputs : outputs =
|
|
printf "Preparing to run %d tests...\n%!" (length inputs);
|
|
run inputs
|
|
|
|
let successful, failed =
|
|
partition (fun (_, o) -> o = OK) outputs
|
|
|
|
let () =
|
|
printf "%d out of %d tests are successful.\n"
|
|
(length successful) (length inputs);
|
|
failed |> iter (fun (input, outcome) ->
|
|
printf "\n[FAIL] %s\n%s" (print_input input) (print_outcome outcome)
|
|
)
|