From 3210e65165466e0c2d80cb68e66d16982888ff9c Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Mon, 14 Oct 2024 14:16:03 +0100 Subject: [PATCH] WIP: factorise process execution --- .depend | 2 + tools/test_install.ml | 805 ++++++++++++++++++++++++++---------------- 2 files changed, 497 insertions(+), 310 deletions(-) diff --git a/.depend b/.depend index 7b8f468ca699..567cfba60b82 100644 --- a/.depend +++ b/.depend @@ -8220,6 +8220,7 @@ tools/sync_dynlink.cmx : \ tools/sync_dynlink.cmi : tools/test_install.cmo : \ otherlibs/unix/unix.cmi \ + utils/misc.cmi \ utils/config.cmi \ driver/compmisc.cmi \ utils/ccomp.cmi \ @@ -8227,6 +8228,7 @@ tools/test_install.cmo : \ tools/test_install.cmi tools/test_install.cmx : \ otherlibs/unix/unix.cmx \ + utils/misc.cmx \ utils/config.cmx \ driver/compmisc.cmx \ utils/ccomp.cmx \ diff --git a/tools/test_install.ml b/tools/test_install.ml index 4db3e9a8d307..aa6f5fc40a4d 100644 --- a/tools/test_install.ml +++ b/tools/test_install.ml @@ -16,11 +16,22 @@ directories to use for bindir and libdir in both phases of the test. *) type config = { supports_shared_libraries: bool; + (* $(SUPPORTS_SHARED_LIBRARIES) - Makefile.config *) has_ocamlnat: bool; + (* $(INSTALL_OCAMLNAT) - Makefile.build_config *) has_ocamlopt: bool; + (* $(NATIVE_COMPILER) - Makefile.config *) libraries: string list + (* Sorted basenames of libraries to test. + Derived from $(OTHERLIBRARIES) - Makefile.config *) } +(* bindir, libdir and config come from the command line. Validate that bindir + and libdir exist and share a common prefix (i.e. there is some prefix /foo + or C:\foo which they share) as otherwise it's not possible to rename the + installation directory. prefix is thus the common prefix of bindir and libdir + and [Filename.concat prefix bindir_suffix = bindir], etc. + *) let bindir, libdir, prefix, bindir_suffix, libdir_suffix, config = (* Map directory names for otherlibs to library names and sort them in a dependency-compatible order. *) @@ -77,10 +88,12 @@ let bindir, libdir, prefix, bindir_suffix, libdir_suffix, config = let usage = "\n\ Usage: test_install --bindir --libdir [libraries]\n\ options are:" in - let error msg = - Printf.eprintf "%s: %s\n" Sys.executable_name msg; - Arg.usage args usage; - exit 2 in + let error fmt = + let f msg = + Printf.eprintf "%s: %s\n" Sys.executable_name msg; + Arg.usage args usage; + exit 2 in + Printf.ksprintf f fmt in let split_to_prefix bindir libdir = let rec split_dir acc dir = let dirname = Filename.dirname dir in @@ -121,91 +134,405 @@ directories given for --bindir and --libdir do not have a common prefix"; exit 2 else let prefix, bindir_suffix, libdir_suffix = split_to_prefix bindir libdir in + if Sys.file_exists (prefix ^ ".new") then + error "can't rename %s to %s.new as the latter already exists!" + prefix prefix; + Misc.Style.setup None; + let no_markup ansi = { Misc.Style.ansi; text_close = ""; text_open = "" } in + Misc.Style.(set_styles { + warning = no_markup [Bold; FG Yellow]; + error = no_markup [Bold; FG Red]; + loc = no_markup [Bold; FG Blue]; + hint = no_markup [Bold; FG Green]; + inline_code = no_markup [FG Blue]}); + let summary = + let choose b t f = (if b then t else f), true in + let puzzle = [ + "native and ", config.has_ocamlopt; + "bytecode", true; + " only", not config.has_ocamlopt; + " for ", true; + choose config.supports_shared_libraries + "shared and static linking" + "static linking only"; + " with ocamlnat", config.has_ocamlnat + ] in + let summary = + List.filter_map (fun (s, b) -> if b then Some s else None) puzzle in + String.concat "" summary in + Format.printf + "@{Test Environment@}\n\ + \ @{prefix@} = %s\n\ + \ @{bindir@} = [$prefix/]%s\n\ + \ @{libdir@} = [$prefix/]%s\n\ + Testing %s\n%!" prefix bindir_suffix libdir_suffix summary; bindir, libdir, prefix, bindir_suffix, libdir_suffix, config -module StringSet = Set.Make(String) +let test_root = Sys.getcwd () -let exe = - if Sys.win32 then - Fun.flip (^) ".exe" - else - Fun.id +module Filename = struct + include Filename -let is_path = - if Sys.win32 then - fun name -> String.lowercase_ascii name = "path" - else - String.equal "PATH" - -let scrub = - StringSet.of_list [ - "BUILD_PATH_PREFIX_MAP"; - "CAMLLIB"; - "CAMLRUNPARAM"; - "CAML_LD_LIBRARY_PATH"; - "OCAMLLIB"; - "OCAMLPARAM"; - "OCAMLRUNPARAM"; - "OCAMLTOP_INCLUDE_PATH"; - "OCAML_RUNTIME_EVENTS_DIR"; - "OCAML_RUNTIME_EVENTS_PRESERVE"; - "OCAML_RUNTIME_EVENTS_START"; - ] - -(* Returns an environment where any variables in scrub have been removed and - with effectively PATH=$bindir:$PATH and - LD_LIBRARY_PATH=$libdir:$LD_LIBRARY_PATH on Unix or - PATH=$bindir;$libdir;$PATH on Windows. *) -let make_env ?(caml_ld_library_path=false) ?(ocamllib=false) bindir libdir = - let keep binding = - let equals = String.index binding '=' in - let name = String.sub binding 0 equals in - let value = - String.sub binding (equals + 1) (String.length binding - equals - 1) in - if StringSet.mem name scrub then - None - else if is_path name then - if Sys.win32 then - if String.index_opt bindir ';' <> None then - Some (Printf.sprintf "%s=\"%s\";%s" name bindir value) - else - Some (Printf.sprintf "%s=%s;%s" name bindir value) - else - Some (Printf.sprintf "%s=%s:%s" name bindir value) - else if not Sys.win32 && name = "LD_LIBRARY_PATH" then - Some (Printf.sprintf "%s=%s:%s" name libdir value) + let is_dir_sep = + if Sys.win32 then + function '\\' | '/' -> true | _ -> false else - Some binding - in - let bindings = - List.filter_map keep (Array.to_list (Unix.environment ())) in - let bindings = - if Sys.win32 - || List.exists (String.starts_with ~prefix:"LD_LIBRARY_PATH=") bindings then - bindings + (=) '/' +end + +module String = struct + include String + + let remove_prefix ~prefix s = + if starts_with ~prefix s then + let l = String.length prefix in + Some (String.sub s l (String.length s - l)) else - ("LD_LIBRARY_PATH=" ^ libdir)::bindings in - let bindings = - if ocamllib then - ("OCAMLLIB=" ^ libdir) :: bindings + None + + let find s p = + let max = length s - 1 in + if max = -1 then + None else - bindings + let rec loop i = + if p s.[i] then + Some i + else if i < max then + loop (succ i) + else + None in + loop 0 +end + +(* Jump through some mildly convoluted hoops to create diff'able output. + [display_path path] applies the following transformations: + - ["$bindir"] or ["$libdir"] if [path] is exactly [bindir_suffix] or + [libdir_suffix] (this captures passing those two variabes to the test + programs) + - if [path] begins with [prefix] then the text is replaced with ["$prefix"] + (which can create ["$prefix.new/"], etc.). Additionally, if the next part + of [path] after the following directory separator is [bindir_suffix] or + [libdir_suffix] then this is replaced with ["$bindir"] or ["$libdir"] + (i.e. this can generate ["$prefix.new/$bindir"] but not + ["$prefix.new/foo/$bindir"] + - if [path] begins [test_root] (i.e. the current directory) then this + is replaced with [Filename.current_dir_name] as long as [path] is either + exactly [test_root] or [test_root] is followed by a directory separator + (i.e. it generates ["./"] but never [".new/"]) + Both simpler and more convoluted ways of doing this are available. *) +let display_path f path = + match String.remove_prefix ~prefix path with + | Some remainder -> + if remainder = "" then + Format.pp_print_string f "$prefix" + else begin + match String.find remainder Filename.is_dir_sep with + | None -> + Format.fprintf f "$prefix%s" remainder + | Some idx -> + let suffix, path = + let idx = idx + 1 in + let suffix = String.sub remainder 0 idx in + let path = + String.sub remainder idx (String.length remainder - idx) in + suffix, path in + match String.remove_prefix ~prefix:bindir_suffix path with + | Some path when path = "" || Filename.is_dir_sep path.[0] -> + Format.fprintf f "$prefix%s$bindir%s" suffix path + | _ -> + match String.remove_prefix ~prefix:libdir_suffix path with + | Some path when path = "" || Filename.is_dir_sep path.[0] -> + Format.fprintf f "$prefix%s$libdir%s" suffix path + | _ -> + Format.pp_print_string f ("$prefix" ^ remainder) + end + | None -> + match String.remove_prefix ~prefix:test_root path with + | Some path when path = "" || Filename.is_dir_sep path.[0] -> + Format.pp_print_string f (Filename.current_dir_name ^ path) + | _ -> + if path = libdir_suffix then + Format.pp_print_string f "$libdir" + else if path = bindir_suffix then + Format.pp_print_string f "$bindir" + else + Format.pp_print_string f path + +type bytecode_classification = [ + | `Not_tendered_bytecode (* Produced by ocamlopt, -output-complete-exe, etc. + *) + | `Shebang (* Launched using #! header *) + | `Launcher (* Launched using the executable launcher *) + | `Custom (* Compiled with -custom *) +] + +let classify_bytecode_image file = + try + In_channel.with_open_bin file (fun ic -> + let start = really_input_string ic 2 in + let is_RNTM = function + | Bytesections.{name = Name.RNTM; _} -> true + | _ -> false in + let sections = Bytesections.(all (read_toc ic)) in + if start = "#!" then + `Shebang + else if List.exists is_RNTM sections then + `Launcher + else + `Custom) + with End_of_file | Bytesections.Bad_magic_number -> + `Not_tendered_bytecode + +let fail_because fmt = + let f s = + prerr_endline s; + exit 1 in - let bindings = - if caml_ld_library_path then - ("CAML_LD_LIBRARY_PATH=" ^ Filename.concat libdir "stublibs") :: bindings + Format.ksprintf f fmt + +type _ output = +| Stdout : unit output +| Return : (int * string list) output + +let print_process_status () = function +| Unix.WEXITED n -> "exited with " ^ string_of_int n +| Unix.WSIGNALED _ -> "signalled" +| Unix.WSTOPPED _ -> "stopped" + +module Environment : sig + type t + + val make : + ?caml_ld_library_path:bool -> ?ocamllib:bool -> string -> string -> t + + val run_process : + 'a output + -> ?runtime:string -> string -> string list -> ?no_stderr:bool -> t -> 'a +end = struct + type t = { + env: string array; + serial: int; + bindir: string; + libdir: string; + set_CAML_LD_LIBRARY_PATH: bool; + set_OCAMLLIB: bool; + } + + module StringSet = Set.Make(String) + + (* List of environment variables to remove from the calling environment *) + let scrub = + StringSet.of_list [ + "BUILD_PATH_PREFIX_MAP"; + "CAMLLIB"; + "CAMLRUNPARAM"; + "CAML_LD_LIBRARY_PATH"; + "OCAMLLIB"; + "OCAMLPARAM"; + "OCAMLRUNPARAM"; + "OCAMLTOP_INCLUDE_PATH"; + "OCAML_RUNTIME_EVENTS_DIR"; + "OCAML_RUNTIME_EVENTS_PRESERVE"; + "OCAML_RUNTIME_EVENTS_START"; + ] + + (* Tests whether the name of an environment variable is in fact PATH, + masking the fact that environment variable names are case-insensitive on + Windows. *) + let is_path_env = + if Sys.win32 then + fun name -> String.lowercase_ascii name = "path" else - bindings - in - Array.of_list bindings + String.equal "PATH" + + let environments = Hashtbl.create 15 + + (* Returns an environment where any variables in scrub have been removed and + with effectively PATH=$bindir:$PATH and + LD_LIBRARY_PATH=$libdir:$LD_LIBRARY_PATH on Unix or + PATH=$bindir;$libdir;$PATH on Windows. *) + let make ?(caml_ld_library_path=false) ?(ocamllib=false) bindir libdir = + let keep binding = + let equals = String.index binding '=' in + let name = String.sub binding 0 equals in + let value = + String.sub binding (equals + 1) (String.length binding - equals - 1) in + if StringSet.mem name scrub then + None + else if is_path_env name then + if Sys.win32 then + if String.index_opt bindir ';' <> None then + Some (Printf.sprintf "%s=\"%s\";%s" name bindir value) + else + Some (Printf.sprintf "%s=%s;%s" name bindir value) + else + Some (Printf.sprintf "%s=%s:%s" name bindir value) + else if not Sys.win32 && name = "LD_LIBRARY_PATH" then + Some (Printf.sprintf "%s=%s:%s" name libdir value) + else + Some binding + in + let bindings = + List.filter_map keep (Array.to_list (Unix.environment ())) in + let bindings = + if Sys.win32 + || List.exists (String.starts_with ~prefix:"LD_LIBRARY_PATH=") + bindings then + bindings + else + ("LD_LIBRARY_PATH=" ^ libdir)::bindings in + let bindings = + if ocamllib then + ("OCAMLLIB=" ^ libdir)::bindings + else + bindings + in + let bindings = + if caml_ld_library_path then + ("CAML_LD_LIBRARY_PATH=" ^ Filename.concat libdir "stublibs")::bindings + else + bindings + in + let env = Array.of_list bindings in + try {env; serial = Hashtbl.find environments env; bindir; libdir; + set_CAML_LD_LIBRARY_PATH = caml_ld_library_path; + set_OCAMLLIB = ocamllib} + with Not_found -> + let serial = Hashtbl.length environments + 1 in + Hashtbl.add environments env serial; + {env; serial; bindir; libdir; + set_CAML_LD_LIBRARY_PATH = caml_ld_library_path; set_OCAMLLIB = ocamllib} + + type program = + | Direct of string + | Tender of string * string + + let null = Unix.openfile Filename.null [Unix.O_WRONLY] 0o200 + + let last_environment = ref (-1) + + let rec run_process ~to_stdout ?runtime program args ?(no_stderr = false) + ({env; _} as environment) = + flush stderr; + flush stdout; + let captured_output = "process-output" in + let stdout, stderr = + let flags = Unix.([O_RDWR; O_CREAT; O_TRUNC; O_CLOEXEC]) in + let fd = Unix.openfile captured_output flags 0o600 in + fd, fd in + let stderr = if no_stderr || runtime <> None then null else stderr in + let runtime, classification = + match runtime with + | None -> + "", `Not_tendered_bytecode + | Some runtime -> + runtime, classify_bytecode_image program in + let summarise f () = + display_path f program; + List.iter (fun x -> Format.pp_print_char f ' '; display_path f x) args; + if not to_stdout then + Format.pp_print_string f (" 1> " ^ captured_output); + if no_stderr then + Format.pp_print_string f (" 2>" ^ Filename.null) in + let display_environment () = + if environment.serial <> !last_environment then begin + last_environment := environment.serial; + (* For ease of diff'ing, the environment is displayed in Posix format + and ignores the fact Windows doesn't set LD_LIBRARY_PATH *) + Format.printf "\ +@{> @}@{Environment@}\n\ +@{> @} @{PATH@}=%a:$PATH\n\ +@{> @} @{LD_LIBRARY_PATH@}=%a:$LD_LIBRARY_PATH\n" + display_path environment.bindir + display_path environment.libdir; + if environment.set_CAML_LD_LIBRARY_PATH then + Format.printf "\ +@{> @} @{CAML_LD_LIBRARY_PATH@}=\ + %a/stublibs:$CAML_LD_LIBRARY_PATH\n" + display_path libdir; + if environment.set_OCAMLLIB then + Format.printf "\ +@{> @} @{OCAMLLIB@}=%a:$OCAMLLIB\n" display_path libdir + end in + try + let pid = + Unix.create_process_env program (Array.of_list (program::args)) env + Unix.stdin stdout stderr in + let (_, status) = Unix.waitpid [] pid in + begin + match status with + | Unix.WEXITED n -> + if n = 2 && (not to_stdout || classification = `Launcher) then + Format.printf "@{%a@} <@{exit 2@}>\n%!" + summarise () + else if n = 0 then + Format.printf "@{%a@}\n%!" summarise () + else + Format.printf "@{%a@} <@{exit %d@}>\n%!" + summarise () n + | _ -> + Format.printf "@{%a@}\n%!" summarise () + end; + display_environment (); + let result = + let _ = Unix.lseek stdout 0 Unix.SEEK_SET in + let lines = + let ic = Unix.in_channel_of_descr stdout in + if to_stdout then + let format_line () s = + Format.printf "@{>@} %s\n%!" s in + let () = In_channel.fold_lines format_line () ic in + [] + else + In_channel.input_lines ic in + Unix.close stdout; + Sys.remove captured_output; + lines in + match status with + | Unix.WEXITED n when runtime = "" && (n = 0 || not to_stdout) -> + n, result + | Unix.WEXITED 2 when classification = `Launcher -> + run_process ~to_stdout runtime (program::args) ~no_stderr + environment + | status -> + fail_because "%s did not terminate as expected (%a)" + program print_process_status status + with Unix.(Unix_error(ENOENT, "create_process", _)) as e -> + Format.printf "@{%a@} <@{exit 2@}>\n%!" summarise (); + display_environment (); + if classification = `Shebang || classification = `Launcher then + run_process ~to_stdout runtime (program::args) ~no_stderr environment + else + raise e + + let run_process : type s . s output -> ?runtime:string -> string + -> string list -> ?no_stderr:bool -> t -> s = + fun output ?runtime program args ?no_stderr env -> + match output with + | Stdout -> + run_process ~to_stdout:true ?runtime program args ?no_stderr env + |> ignore + | Return -> + run_process ~to_stdout:false ?runtime program args ?no_stderr env +end + +(* exe ["foo" = "foo.exe"] on Windows or ["foo"] otherwise. *) +let exe = + if Sys.win32 then + Fun.flip (^) ".exe" + else + Fun.id (* This test verifies that a series of libraries can be loaded in a toplevel. Any failures cause the script to be aborted. *) let load_libraries_in_toplevel env ?runtime toplevel ext libraries = - Printf.printf "\nTesting loading of libraries in %s\n%!" toplevel; + Format.printf "\nTesting loading of libraries in %a\n%!" + display_path toplevel; Out_channel.with_open_text "test_install_script.ml" (fun oc -> List.iter (fun library -> - (* dynlink.cmxs does not exist *) + (* dynlink.cmxs does not exist, for obvious reasons, but we can check + loading the library in ocamlnat "works". *) let ext = if library = "dynlink" && ext = "cmxs" then "cmxa" @@ -218,24 +545,10 @@ let load_libraries_in_toplevel env ?runtime toplevel ext libraries = print_endline \" Loaded %s.%s\";;" library library ext library ext) libraries; Printf.fprintf oc "#quit;;\n"); - let pid = - let executable, args = - match runtime with - | Some runtime when not Sys.win32 -> - runtime, [| runtime; toplevel; "-noinit"; "-no-version"; "-noprompt"; - "test_install_script.ml" |] - | _ -> toplevel, [| toplevel; "-noinit"; "-no-version"; "-noprompt"; - "test_install_script.ml" |] - in - Unix.create_process_env - executable args - env Unix.stdin Unix.stdout Unix.stderr - in - let result = Unix.waitpid [] pid in - Sys.remove "test_install_script.ml"; - match result with - | (_, Unix.WEXITED 0) -> () - | _ -> exit 1 + let args = + ["-noinit"; "-no-version"; "-noprompt"; "test_install_script.ml"] in + Environment.run_process Stdout ?runtime toplevel args env; + Sys.remove "test_install_script.ml" (* This test verifies that a series of libraries can be loaded via Dynlink. Any failures will cause either an exception or a compilation error. *) @@ -261,40 +574,20 @@ let load_libraries_in_prog env ?runtime libdir compiler ~native libraries = ); flush stdout; let test_program = - Filename.concat (Unix.getcwd ()) (exe "test_install_script") in - let pid = - let dynlink = if native then "dynlink.cmxa" else "dynlink.cma" in - let executable, args = - match runtime with - | Some runtime when not Sys.win32 && not config.has_ocamlopt -> - (* XXX All the dupl, etc. *) - runtime, [| runtime; compiler; "-I"; "+dynlink"; dynlink; "-linkall"; - "-o"; test_program; "test_install_script.ml" |] - | _ -> - compiler, [| compiler; "-I"; "+dynlink"; dynlink; "-linkall"; - "-o"; test_program; "test_install_script.ml" |] - in - Unix.create_process_env - executable args env Unix.stdin Unix.stdout Unix.stderr - in + Filename.concat test_root (exe "test_install_script") in let () = - match Unix.waitpid [] pid with - | (_, Unix.WEXITED 0) -> () - | _ -> - print_endline "Unexpected compiler error"; - exit 1 - in - let pid = - (* XXX Code dup with toplevels etc. *) - let executable, args = - match runtime with - | None -> test_program, [| test_program |] - | Some runtime -> runtime, [| runtime; test_program |] - in - Unix.create_process_env - executable args env Unix.stdin Unix.stdout Unix.stderr - in - let (_, result) = Unix.waitpid [] pid in + let dynlink = if native then "dynlink.cmxa" else "dynlink.cma" in + let args = [ + "-I"; "+dynlink"; dynlink; "-linkall"; + "-o"; test_program; "test_install_script.ml" + ] in + let runtime = + if Sys.win32 || config.has_ocamlopt then + None + else + runtime in + Environment.run_process Stdout ?runtime compiler args env in + let () = Environment.run_process Stdout ?runtime test_program [] env in let files = [ test_program; "test_install_script.ml"; @@ -303,12 +596,10 @@ let load_libraries_in_prog env ?runtime libdir compiler ~native libraries = ] in let files = if native then - ("test_install_script" ^ Config.ext_obj) :: files + ("test_install_script" ^ Config.ext_obj)::files else files in - List.iter Sys.remove files; - if result <> Unix.WEXITED 0 then - exit 1 + List.iter Sys.remove files let is_executable = if Sys.win32 then @@ -318,28 +609,6 @@ let is_executable = try Unix.access binary [Unix.X_OK]; true with Unix.Unix_error _ -> false -let classify_bytecode_image file = - try - In_channel.with_open_bin file (fun ic -> - let start = really_input_string ic 2 in - let is_RNTM = function - | Bytesections.{name = Name.RNTM; _} -> true - | _ -> false in - let sections = Bytesections.(all (read_toc ic)) in - if start = "#!" then - `Shebang - else if List.exists is_RNTM sections then - `Launcher - else - `Custom) - with End_of_file | Bytesections.Bad_magic_number -> - `Bytecode_not_found - -let print_process_status oc = function -| Unix.WEXITED n -> Printf.fprintf oc "exited with %d" n -| Unix.WSIGNALED _ -> output_string oc "signalled" -| Unix.WSTOPPED _ -> output_string oc "stopped" - (* This test verifies that a series of libraries can be loaded via Dynlink. Any failures will cause either an exception or a compilation error. *) let test_bytecode_binaries ~full env bindir = @@ -349,46 +618,47 @@ let test_bytecode_binaries ~full env bindir = let binary = Filename.concat bindir binary in if is_executable binary then match classify_bytecode_image binary with - | `Bytecode_not_found -> () + | `Not_tendered_bytecode -> () | (`Shebang | `Launcher | `Custom) as kind -> - Printf.printf " Testing %s -vnum: %!" binary; try - let pid = - Unix.create_process_env binary [| binary; "-vnum" |] - env Unix.stdin Unix.stdout Unix.stderr in - let (_, result) = Unix.waitpid [] pid in + let (result, output) = + Environment.run_process Return + binary ["-vnum"] ~no_stderr:true env in + print_string " Result: "; + List.iter print_endline output; + flush stdout; let incorrect_status = if full then (* First time around, everything is supposed to work! *) - result <> Unix.WEXITED 0 + result <> 0 else match kind with | `Custom -> (* Executables compiled with -custom should work regardless *) - result <> Unix.WEXITED 0 + result <> 0 | `Launcher -> (* Second time around, the executable launchers should fail, except on Windows (since PATH is adjusted) *) - not Sys.win32 && result <> Unix.WEXITED 2 + not Sys.win32 && result <> 2 | `Shebang -> (* Second time around, the shebangs should all be broken so Unix_error should already have been raised! *) true in - if incorrect_status then begin - Printf.eprintf "%s did not terminate as expected (%a)\n" - binary print_process_status result; - exit 1 - end + if incorrect_status then + fail_because "%s did not terminate as expected (%a)" + binary print_process_status (Unix.WEXITED result); + if result = 2 then + print_endline "unable to run" with Unix.Unix_error(_, "create_process", _) as e -> if full || Sys.win32 then raise e else - Printf.printf "unable to run\n%!" + print_endline " Result: unable to run" in let binaries = Sys.readdir bindir in - Printf.printf "\nTesting bytecode binaries in %s\n" bindir; + Format.printf "\nTesting bytecode binaries in %a\n" display_path bindir; Array.sort String.compare binaries; Array.iter test_binary binaries @@ -401,8 +671,22 @@ let is_directory dir = try Sys.is_directory dir with Sys_error _ -> false +let display_lib = + let dir = Config.standard_library in + let dir = + if String.starts_with ~prefix:Sys.argv.(2) dir then + let l = String.length Sys.argv.(2) in + "$prefix" ^ String.sub dir l (String.length dir - l) + else + dir in + if String.ends_with ~suffix:Sys.argv.(3) dir then + let l = String.length Sys.argv.(3) in + String.sub dir 0 (String.length dir - l) ^ "$libdir" + else + dir + let () = - Printf.printf " %s: %%s\n%%!" Config.standard_library; + Printf.printf " %s: %%s\n%%!" display_lib; if is_directory Config.standard_library <> state then let () = Printf.eprintf " *** Directory %%sfound!\n" @@ -416,30 +700,13 @@ let compile_with_options env compiler ~native ?runtime options Printf.printf " Compiling %s\n%!" description; write_test_program description; let test_program = - Filename.concat (Unix.getcwd ()) (exe test_program) in - let pid = - let args = - (compiler :: "-o" :: test_program :: "-I" :: "+compiler-libs" :: - (if native then "ocamlcommon.cmxa" else "ocamlcommon.cma") :: - "test_install_script.ml" :: options) - in - let executable, args = - match runtime with - | Some runtime -> - runtime, runtime :: args - | None -> - compiler, args - in - Unix.create_process_env - executable (Array.of_list args) env Unix.stdin Unix.stdout Unix.stderr - in - let () = - match Unix.waitpid [] pid with - | (_, Unix.WEXITED 0) -> () - | _ -> - print_endline "Unexpected compiler error"; - exit 1 - in + Filename.concat test_root (exe test_program) in + let ocamlcommon = if native then "ocamlcommon.cmxa" else "ocamlcommon.cma" in + let args = + "-I" :: "+compiler-libs" :: ocamlcommon :: + "-o" :: test_program :: + "test_install_script.ml" :: options in + let () = Environment.run_process Stdout ?runtime compiler args env in let files = [ "test_install_script.ml"; "test_install_script.cmi"; @@ -447,7 +714,7 @@ let compile_with_options env compiler ~native ?runtime options ] in let files = if native then - ("test_install_script" ^ Config.ext_obj) :: files + ("test_install_script" ^ Config.ext_obj)::files else files in List.iter Sys.remove files; @@ -458,46 +725,14 @@ let compile_obj env standard_library compiler ~native ?runtime Printf.printf " Compiling %s\n%!" description; write_test_program description; let test_program = - Filename.concat (Unix.getcwd ()) (exe test_program) in - let pid = - let args = [ - compiler; "-o"; "test_install_ocaml" ^ Config.ext_obj; - "-I"; "+compiler-libs"; "-output-obj"; - (if native then "ocamlcommon.cmxa" else "ocamlcommon.cma"); - "test_install_script.ml" - ] in - let executable, args = - match runtime with - | Some runtime -> - runtime, runtime :: args - | None -> - compiler, args - in - Unix.create_process_env - executable (Array.of_list args) env Unix.stdin Unix.stdout Unix.stderr - in - let () = - match Unix.waitpid [] pid with - | (_, Unix.WEXITED 0) -> () - | _ -> - print_endline "Unexpected compiler error"; - exit 1 - in - (*Out_channel.with_open_text "test_install_main.c" (fun oc -> - output_string oc - "#define CAML_INTERNALS\n\ - #include \n\ - \n\ - int main_os(int argc, char_os **argv)\n\ - {\n\ - \ caml_startup(argv);\n\ - \ caml_shutdown();\n\ - \ return 0;\n\ - }\n"); - if Ccomp.compile_file ~standard_library "test_install_main.c" <> 0 then begin - print_endline "Unexpected C compiler error"; - exit 1 - end;*) + Filename.concat test_root (exe test_program) in + let ocamlcommon = if native then "ocamlcommon.cmxa" else "ocamlcommon.cma" in + let args = [ + "-I"; "+compiler-libs"; ocamlcommon; + "-output-obj"; "-o"; "test_install_ocaml" ^ Config.ext_obj; + "test_install_script.ml" + ] in + let () = Environment.run_process Stdout ?runtime compiler args env in let files = [ "test_install_script.ml"; "test_install_script.cmi"; @@ -506,7 +741,7 @@ let compile_obj env standard_library compiler ~native ?runtime ] in let files = if native then - ("test_install_script" ^ Config.ext_obj) :: files + ("test_install_script" ^ Config.ext_obj)::files else files in let objects = [ @@ -523,76 +758,20 @@ let compile_obj env standard_library compiler ~native ?runtime Config.bytecomp_c_libraries in runtime_lib ^ " " ^ libraries in - (* let flags = - match standard_library with - | Some libdir -> "-L " ^ Filename.quote libdir ^ " " ^ flags - | None -> flags - in*) - if Ccomp.call_linker Ccomp.Exe test_program objects flags <> 0 then begin - print_endline "Unexpected linker error"; - exit 1 - end; + if Ccomp.call_linker Ccomp.Exe test_program objects flags <> 0 then + fail_because "Unexpected linker error"; List.iter Sys.remove files; Some (f test_program) let compiler_where env ?runtime compiler = - let from_compiler, stdout = Unix.pipe ~cloexec:true () in - let executable, args = - match runtime with - | Some runtime -> - runtime, [| runtime; compiler; "-where" |] - | None -> - compiler, [| compiler; "-where" |] - in - let pid = - Unix.create_process_env - executable args - env Unix.stdin stdout Unix.stderr - in - let ic = Unix.in_channel_of_descr from_compiler in - In_channel.set_binary_mode ic false; - let where = input_line ic in - Unix.close from_compiler; - if snd (Unix.waitpid [] pid) = Unix.WEXITED 0 then - where - else begin - Printf.eprintf "Unexpected response from %s -where\n" compiler; - exit 1 - end + match Environment.run_process Return ?runtime compiler ["-where"] env with + | (0, [where]) -> where + | _ -> + fail_because "Unexpected response from %s -where" compiler let run_program env ?runtime test_program ~arg = - let pid = - try - let pid = - Unix.create_process_env - test_program [| test_program; string_of_bool arg |] - env Unix.stdin Unix.stdout Unix.stderr - in - match runtime with - | None -> pid - | Some runtime -> - (* We should get here if the program used the executable launcher, not - a shebang *) - let (_, result) = Unix.waitpid [] pid in - if not Sys.win32 && result <> Unix.WEXITED 2 then begin - Printf.eprintf "%s did not terminate as expected (launcher %a)\n" - test_program print_process_status result; - exit 1 - end; - raise (Unix.Unix_error(Unix.ENOENT, "create_process", "")) - with Unix.Unix_error(Unix.ENOENT, "create_process", _) - when runtime <> None -> - let runtime = Option.get runtime in - Unix.create_process_env - runtime [| runtime; test_program; string_of_bool arg |] - env Unix.stdin Unix.stdout Unix.stderr - in - let (_, result) = Unix.waitpid [] pid in - if result <> Unix.WEXITED 0 then begin - Printf.eprintf "%s did not terminate as expected (%a)\n" - test_program print_process_status result; - exit 1 - end + let args = [string_of_bool arg; prefix; libdir_suffix] in + Environment.run_process Stdout ?runtime test_program args env (* XXX Code dup between these two paths *) let compile_with_options ?(unix_only=false) @@ -605,11 +784,11 @@ let compile_with_options ?(unix_only=false) None else let cont test_program ?runtime env ~arg = - let runtime = if tendered then runtime else None in + let runtime = if tendered && not Sys.win32 then runtime else None in run_program env ?runtime test_program ~arg; if full then Some (fun ?runtime env ~arg -> - let runtime = if tendered then runtime else None in + let runtime = if tendered && not Sys.win32 then runtime else None in run_program env ?runtime test_program ~arg; Sys.remove test_program; None) @@ -644,9 +823,10 @@ let compile_obj ?(unix_only=false) each of these programs can correctly identify the Standard Library location. Any failures will cause either an exception or a compilation error. *) let test_standard_library_location ~full env bindir libdir ocamlc ocamlopt = - Printf.printf "\nTesting compilation mechanisms for %s\n%!" bindir; + Format.printf "\nTesting compilation mechanisms for %a\n%!" + display_path bindir; let runtime = - if config.has_ocamlopt || Sys.win32 then + if full || config.has_ocamlopt || Sys.win32 then None else Some (exe (Filename.concat bindir "ocamlrun")) @@ -658,8 +838,8 @@ let test_standard_library_location ~full env bindir libdir ocamlc ocamlopt = else "n/a" in - Printf.printf " ocamlc -where: %s\n ocamlopt -where: %s\n%!" - ocamlc_where ocamlopt_where; + Format.printf " ocamlc -where: %a\n ocamlopt -where: %a\n%!" + display_path ocamlc_where display_path ocamlopt_where; let unix_only = true in let tendered = true in let needs_shared = true in @@ -717,7 +897,10 @@ let run_tests ~full env bindir libdir libraries = let ocamlc = exe (Filename.concat bindir "ocamlc") in let ocamlopt = exe (Filename.concat bindir "ocamlopt") in let runtime = - if full then None else Some (exe (Filename.concat bindir "ocamlrun")) in + if full || Sys.win32 then + None + else + Some (exe (Filename.concat bindir "ocamlrun")) in if config.supports_shared_libraries then begin load_libraries_in_toplevel env ?runtime ocaml "cma" config.libraries end; if config.has_ocamlnat then @@ -731,25 +914,27 @@ let run_tests ~full env bindir libdir libraries = let () = Compmisc.init_path (); - let env = make_env bindir libdir in + let env = Environment.make bindir libdir in let programs = run_tests ~full:true env bindir libdir config.libraries in let new_prefix = prefix ^ ".new" in let bindir = Filename.concat new_prefix bindir_suffix in let libdir = Filename.concat new_prefix libdir_suffix in - Printf.printf "\nRenaming %s to %s\n\n%!" prefix new_prefix; + Format.printf "\nRenaming %a to %a\n\n%!" display_path prefix + display_path new_prefix; Sys.rename prefix new_prefix; at_exit (fun () -> flush stderr; flush stdout; - Printf.printf "\nRestoring %s to %s\n" new_prefix prefix; + Format.printf "\nRestoring %a to %a\n" display_path new_prefix + display_path prefix; Sys.rename new_prefix prefix); Printf.printf "Re-running test programs\n%!"; - let env = make_env bindir libdir in + let env = Environment.make bindir libdir in let runtime = Some (exe (Filename.concat bindir "ocamlrun")) in List.iter (fun f -> assert (f ?runtime env ~arg:false = None)) programs; let env = - make_env ~caml_ld_library_path:true ~ocamllib:true bindir libdir in + Environment.make ~caml_ld_library_path:true ~ocamllib:true bindir libdir in Compmisc.reinit_path ~standard_library:libdir (); let programs = run_tests ~full:false env bindir libdir config.libraries in assert (programs = [])