From dfa1e7735766012df4f1be3650559fbbbb7bff45 Mon Sep 17 00:00:00 2001
From: Patrick Baudin <patrick.baudin@cea.fr>
Date: Mon, 22 Mar 2021 13:13:32 +0100
Subject: [PATCH] [Tests] adds EXIT directive to ptests

---
 ptests/ptests.ml | 120 ++++++++++++++++++++++++++++++++---------------
 1 file changed, 83 insertions(+), 37 deletions(-)

diff --git a/ptests/ptests.ml b/ptests/ptests.ml
index 1a09de07227..884f56a54c6 100644
--- a/ptests/ptests.ml
+++ b/ptests/ptests.ml
@@ -596,7 +596,14 @@ end
 
 
 (** configuration of a directory/test. *)
-type cmd = { toplevel:string; opts:string; macros: Macros.t ; logs:string list ; timeout:string }
+type cmd = {
+  toplevel: string;
+  opts: string;
+  macros: Macros.t;
+  exit_code: string option;
+  logs:string list;
+  timeout:string
+}
 
 type config =
   {
@@ -609,7 +616,8 @@ type config =
     (** full path of the default toplevel. *)
     dc_filter     : string option; (** optional filter to apply to
                                        standard output *)
-    dc_commands    : cmd list;
+    dc_exit_code  : string option; (** required exit code *)
+    dc_commands   : cmd list;
     (** toplevel full path, options to launch the toplevel on, and list
         of output files to monitor beyond stdout and stderr. *)
     dc_dont_run   : bool;
@@ -630,8 +638,9 @@ let default_config () =
     dc_macros = default_macros ();
     dc_execnow = [];
     dc_filter = None ;
+    dc_exit_code = None;
     dc_default_toplevel = !toplevel_path;
-    dc_commands = [ { toplevel= !toplevel_path; opts=default_options; macros=Macros.empty; logs= []; timeout= ""} ];
+    dc_commands = [ { toplevel= !toplevel_path; opts=default_options; macros=Macros.empty; exit_code=None; logs= []; timeout= ""} ];
     dc_dont_run = false;
     dc_framac = true;
     dc_default_log = [];
@@ -785,11 +794,11 @@ let config_options =
          {toplevel= current.dc_default_toplevel;
           opts= s;
           logs= current.dc_default_log;
+          exit_code= current.dc_exit_code;
           macros= current.dc_macros;
           timeout= current.dc_timeout}
        in
        { current with
-         (*           dc_default_toplevel = !current_default_toplevel;*)
          dc_default_log = !default_parsing_env.current_default_log;
          dc_commands = t :: current.dc_commands });
 
@@ -801,6 +810,7 @@ let config_options =
               { command with opts= make_custom_opts command.opts s;
                              logs= command.logs @ current.dc_default_log;
                              macros = current.dc_macros;
+                             exit_code = current.dc_exit_code;
                              timeout= current.dc_timeout
               })
            !default_parsing_env.current_default_cmds
@@ -814,6 +824,9 @@ let config_options =
     "FILTER",
     (fun _ s current -> { current with dc_filter = Some s });
 
+    "EXIT",
+    (fun _ s current -> { current with dc_exit_code = Some s });
+
     "GCC",
     (fun _ _ acc -> acc);
 
@@ -923,6 +936,7 @@ type toplevel_command =
     options : string ;
     toplevel: string ;
     filter : string option ;
+    exit_code : int ;
     directory : SubDir.t ;
     n : int;
     execnow:bool;
@@ -941,7 +955,7 @@ type diff =
   | Log_error of SubDir.t (** directory *) * string (** file *)
 
 type cmps =
-  | Cmp_Toplevel of toplevel_command
+  | Cmp_Toplevel of toplevel_command * bool (** returns with the required exit_code *)
   | Cmp_Log of SubDir.t (** directory *) * string (** file *)
 
 type shared =
@@ -959,6 +973,7 @@ type shared =
     mutable commands_finished : bool ;
     mutable cmp_finished : bool ;
     mutable summary_time : float ;
+    mutable summary_ret : int ; (* number of run with the required exit code *)
     mutable summary_run : int ;
     mutable summary_ok : int ;
     mutable summary_log : int;
@@ -978,6 +993,7 @@ let shared =
     cmp_finished = false ;
     summary_time = (Unix.times()).Unix.tms_cutime ;
     summary_run = 0 ;
+    summary_ret = 0 ;
     summary_ok = 0 ;
     summary_log = 0 }
 
@@ -1167,7 +1183,7 @@ let command_string command =
   let command_string = match filter with
     | None -> command_string
     | Some filter ->
-      Printf.sprintf "%s && %s < %s >%s && rm -f %s"
+      Printf.sprintf "%s && %s < %s >%s && rm -f %s" (* exit code ? *)
         command_string
         filter
         (Filename.sanitize stderr)
@@ -1243,34 +1259,38 @@ end
 module Report_run=Make_Report(struct type t=int*float
 (* At some point will contain the running time*)
   end)
+let report_run cmd r = Report_run.record cmd r
 
-let report_run cmp r = Report_run.record cmp r
-module Report_cmp=Make_Report(struct type t=int*int end)
+type cmp = { res : int; err:int ; ret:bool }
+module Report_cmp=Make_Report(struct type t=cmp end)
 let report_cmp = Report_cmp.record
+
 let pretty_report fmt =
   Report_run.iter
-    (fun test (_run_result,time_result) ->
+    (fun test (run_result,time_result) ->
        Format.fprintf fmt
          "<testcase classname=%S name=%S time=\"%f\">%s</testcase>@."
          (Filename.basename (SubDir.get test.directory)) test.file time_result
-         (let res,err = Report_cmp.find test in
+         (let {res;err;ret} = Report_cmp.find test in
           Report_cmp.remove test;
-          (if res=0 && err=0 then "" else
+          (if res=0 && err=0 && ret then "" else
              Format.sprintf "<failure type=\"Regression\">%s</failure>"
-               (if res=1 then "Stdout oracle difference"
+               (if not ret then Format.sprintf "Unexpected return code (returns %d instead of %d)" run_result test.exit_code
+                else if res=1 then "Stdout oracle difference"
                 else if res=2 then "Stdout System Error (missing oracle?)"
                 else if err=1 then "Stderr oracle difference"
                 else if err=2 then "Stderr System Error (missing oracle?)"
                 else "Unexpected errror"))));
   (* Test that were compared but not runned *)
   Report_cmp.iter
-    (fun test (res,err) ->
+    (fun test {res;err;ret} ->
        Format.fprintf fmt
          "<testcase classname=%S name=%S>%s</testcase>@."
          (Filename.basename (SubDir.get test.directory)) test.file
-         (if res=0 && err=0 then "" else
+         (if res=0 && err=0 && ret  then "" else
             Format.sprintf "<failure type=\"Regression\">%s</failure>"
-              (if res=1 then "Stdout oracle difference"
+              (if not ret then "Unexpected return code"
+               else if res=1 then "Stdout oracle difference"
                else if res=2 then "Stdout System Error (missing oracle?)"
                else if err=1 then "Stderr oracle difference"
                else if err=2 then "Stderr System Error (missing oracle?)"
@@ -1281,8 +1301,9 @@ let xunit_report () =
     let fmt = Format.formatter_of_out_channel out in
     Format.fprintf fmt
       "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\
-       @\n<testsuite errors=\"0\" failures=\"%d\" name=\"%s\" tests=\"%d\" time=\"%f\" timestamp=\"%f\">\
+       @\n<testsuite errors=\"%d\" failures=\"%d\" name=\"%s\" tests=\"%d\" time=\"%f\" timestamp=\"%f\">\
        @\n%t</testsuite>@."
+      (shared.summary_run-shared.summary_ret)
       (shared.summary_log-shared.summary_ok)
       "Frama-C"
       shared.summary_log
@@ -1315,18 +1336,29 @@ let do_command command =
         (* command string also replaces macros in logfiles names, which
            is useful for Examine as well. *)
         let command_string = command_string command in
-        if !behavior <> Examine
-        then begin
-          if !verbosity >= 1
-          then lock_printf "%% launch %s@." command_string ;
-          let launch_result = launch command_string in
-          let time = 0. (* Individual time is difficult to compute correctly
-                           for now, and currently unused *) in
-          report_run command (launch_result, time)
-        end;
+        let summary_ret =
+          if !behavior <> Examine
+          then begin
+            if !verbosity >= 1
+            then lock_printf "%% launch %s@." command_string ;
+            let launch_result = launch command_string in
+            let time = 0. (* Individual time is difficult to compute correctly
+                             for now, and currently unused *) in
+            report_run command (launch_result, time) ;
+            let summary_ret = (launch_result = command.exit_code)
+                              || (command.filter <> None) (* cannot checks exit code *)
+            in
+            if not summary_ret then
+              lock_printf "%% Unexpected code (returns %d instead of %d) for command: %s@." launch_result command.exit_code command_string ;
+            summary_ret
+          end
+          else false
+        in
         lock ();
+        if summary_ret then
+          shared.summary_ret <- succ shared.summary_ret;
         shared.summary_run <- succ shared.summary_run ;
-        Queue.push (Cmp_Toplevel command) shared.cmps;
+        Queue.push (Cmp_Toplevel (command,summary_ret)) shared.cmps;
         List.iter
           (fun f -> Queue.push (Cmp_Log (command.directory, f)) shared.cmps)
           command.log_files;
@@ -1354,11 +1386,13 @@ let do_command command =
       else begin
         let rec treat_cmd = function
             Toplevel cmd ->
-            shared.summary_run <- shared.summary_run + 1;
+            shared.summary_run <- succ shared.summary_run;
+            shared.summary_ret <- succ shared.summary_ret;
             let log_prefix = log_prefix cmd in
             unlink (log_prefix ^ ".res.log ")
           | Target (execnow,cmds) ->
             shared.summary_run <- succ shared.summary_run;
+            shared.summary_ret <- succ shared.summary_ret;
             remove_execnow_results execnow;
             Queue.iter treat_cmd cmds
         in
@@ -1391,6 +1425,7 @@ let do_command command =
             lock_printf "%% launch %s@." cmd;
           end;
           shared.summary_run <- succ shared.summary_run;
+          shared.summary_ret <- succ shared.summary_ret;
           let r = launch cmd in
           (* mark as already executed. For EXECNOW in test_config files,
              other instances (for example another test of the same
@@ -1490,12 +1525,15 @@ let compare_one_log_file dir file =
               ~cmp_string ~log_file ~oracle_file)
 
 let do_cmp = function
-  | Cmp_Toplevel cmp ->
-    let log_prefix = log_prefix cmp in
-    let oracle_prefix = oracle_prefix cmp in
-    let res = compare_one_file cmp log_prefix oracle_prefix Res in
-    let err = compare_one_file cmp log_prefix oracle_prefix Err in
-    report_cmp cmp (res,err)
+  | Cmp_Toplevel (cmd,ret) ->
+    let log_prefix = log_prefix cmd in
+    let oracle_prefix = oracle_prefix cmd in
+    let cmp = { res = compare_one_file cmd log_prefix oracle_prefix Res;
+                err = compare_one_file cmd log_prefix oracle_prefix Err;
+                ret
+              }
+    in
+    report_cmp cmd cmp
   | Cmp_Log(dir, f) ->
     ignore (compare_one_log_file dir f)
 
@@ -1741,10 +1779,17 @@ let dispatcher () =
       let i = ref 0 in
       let e = ref 0 in
       let nb_files = List.length config.dc_commands in
-      let make_toplevel_cmd {toplevel; opts=options; logs=log_files; macros; timeout} =
+      let make_toplevel_cmd {toplevel; opts=options; logs=log_files; macros; exit_code; timeout} =
         let n = !i in
         {file; options; toplevel; nb_files; directory; n; log_files;
          filter = config.dc_filter; macros;
+         exit_code = begin
+           match exit_code with
+           | None -> 0
+           | Some exit_code ->
+             try int_of_string exit_code with
+             | _ -> Format.eprintf "@[%s: integer required for directive EXIT: %s (defaults to 0)@]@." file exit_code ; 0
+         end;
          execnow=false; timeout;
         }
       in
@@ -1755,6 +1800,7 @@ let dispatcher () =
           log_files = [];
           options = "";
           toplevel = s;
+          exit_code = 0;
           n = !e;
           directory = directory;
           filter = config.dc_filter;
@@ -1848,12 +1894,12 @@ let () =
   Thread.join diff_id;
   if !behavior = Run
   then
-    lock_printf "%% Diffs finished. Summary:@\nRun = %d@\nOk   = %d of %d@\nTime = %f s.@."
-      shared.summary_run shared.summary_ok shared.summary_log
+    lock_printf "%% Diffs finished. Summary:@\nRun  = %d of %d@\nOk   = %d of %d@\nTime = %f s.@."
+      shared.summary_ret shared.summary_run shared.summary_ok shared.summary_log
       ((Unix.times()).Unix.tms_cutime -. shared.summary_time);
   xunit_report ();
   let error_code =
-    if !do_error_code && shared.summary_log <> shared.summary_ok
+    if !do_error_code && ((shared.summary_log <> shared.summary_ok) || (shared.summary_ret <> shared.summary_run))
     then 1
     else 0
   in
-- 
GitLab