diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 48f256df1..22d981903 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,25 +14,25 @@ jobs: strategy: fail-fast: false matrix: - os: - - ubuntu-latest - ocaml-compiler: - - "4.14" - - "5.0" - - "5.1" - - "5.2" - - "5.3" - - "5.4" - libev: - - true - - false + # os: + # - ubuntu-latest + # ocaml-compiler: + # - "4.14" + # - "5.0" + # - "5.1" + # - "5.2" + # - "5.3" + # - "5.4" + # libev: + # - true + # - false include: - - os: ubuntu-24.04-arm - ocaml-compiler: "5.4" - libev: false - - os: macos-latest - ocaml-compiler: "5.4" - libev: false + # - os: ubuntu-24.04-arm + # ocaml-compiler: "5.4" + # libev: false + # - os: macos-latest + # ocaml-compiler: "5.4" + # libev: false - os: windows-latest ocaml-compiler: "5.4" libev: false @@ -42,10 +42,10 @@ jobs: - os: windows-latest ocaml-compiler: "ocaml-variants.4.14.2+options,host-system-mingw" libev: false - - os: ubuntu-latest - ocaml-name: "5.4.0+32bit" - ocaml-compiler: "ocaml-variants.5.4.0+options,ocaml-option-32bit" - libev: false + # - os: ubuntu-latest + # ocaml-name: "5.4.0+32bit" + # ocaml-compiler: "ocaml-variants.5.4.0+options,ocaml-option-32bit" + # libev: false runs-on: ${{ matrix.os }} diff --git a/src/unix/lwt_process_stubs.c b/src/unix/lwt_process_stubs.c index 629682bac..d60ce0087 100644 --- a/src/unix/lwt_process_stubs.c +++ b/src/unix/lwt_process_stubs.c @@ -9,12 +9,25 @@ #include "lwt_unix.h" -#if OCAML_VERSION < 41300 +/* needed for caml_stat_strdup_to_os before ocaml 4.13, and for + caml_win32_multi_byte_to_wide_char, at least as of ocaml 5.0 */ #define CAML_INTERNALS +#if OCAML_VERSION < 50000 +#define caml_win32_multi_byte_to_wide_char win_multi_byte_to_wide_char +#endif +#if OCAML_VERSION == 52000 +/* see https://github.com/ocsigen/lwt/pull/967#issuecomment-2273495094 + * TL;DR: some OCaml upstream issue means this extern is not included on the + * windows, it's added explicitly here instead. */ +CAMLextern int caml_win32_multi_byte_to_wide_char(const char* s, + int slen, + wchar_t *out, + int outlen); #endif #include #include +#include #include #include @@ -68,6 +81,7 @@ CAMLprim value lwt_process_create_process(value prog, value cmdline, value env, HANDLE hp, fd0, fd1, fd2; HANDLE to_close0 = INVALID_HANDLE_VALUE, to_close1 = INVALID_HANDLE_VALUE, to_close2 = INVALID_HANDLE_VALUE; + int size; fd0 = get_handle(Field(fds, 0)); fd1 = get_handle(Field(fds, 1)); @@ -94,11 +108,24 @@ CAMLprim value lwt_process_create_process(value prog, value cmdline, value env, char_os *progs = string_option(prog), *cmdlines = caml_stat_strdup_to_os(String_val(cmdline)), - *envs = string_option(env), *cwds = string_option(cwd); #undef string_option + char_os *envs; + if (Is_some(env)) { + env = Some_val(env); + size = + caml_win32_multi_byte_to_wide_char(String_val(env), + caml_string_length(env), NULL, 0); + envs = caml_stat_alloc((size + 1)*sizeof(char_os)); + caml_win32_multi_byte_to_wide_char(String_val(env), + caml_string_length(env), envs, size); + envs[size] = 0; + } else { + envs = NULL; + } + flags |= CREATE_UNICODE_ENVIRONMENT; if (! CreateProcess(progs, cmdlines, NULL, NULL, TRUE, flags, envs, cwds, &si, &pi)) { diff --git a/src/unix/windows_c/windows_bytes_read.c b/src/unix/windows_c/windows_bytes_read.c index d200f2b8e..8440f54f1 100644 --- a/src/unix/windows_c/windows_bytes_read.c +++ b/src/unix/windows_c/windows_bytes_read.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_bytes_read_job.c b/src/unix/windows_c/windows_bytes_read_job.c index b050b3668..436a9446b 100644 --- a/src/unix/windows_c/windows_bytes_read_job.c +++ b/src/unix/windows_c/windows_bytes_read_job.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_bytes_write.c b/src/unix/windows_c/windows_bytes_write.c index 3a980f574..21c60480b 100644 --- a/src/unix/windows_c/windows_bytes_write.c +++ b/src/unix/windows_c/windows_bytes_write.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_bytes_write_job.c b/src/unix/windows_c/windows_bytes_write_job.c index 456fd84ab..5be192a79 100644 --- a/src/unix/windows_c/windows_bytes_write_job.c +++ b/src/unix/windows_c/windows_bytes_write_job.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_is_socket.c b/src/unix/windows_c/windows_is_socket.c index d3e675917..d1cc90eee 100644 --- a/src/unix/windows_c/windows_is_socket.c +++ b/src/unix/windows_c/windows_is_socket.c @@ -7,6 +7,7 @@ #if defined(LWT_ON_WINDOWS) +#include #include #include #include diff --git a/src/unix/windows_c/windows_pread.c b/src/unix/windows_c/windows_pread.c index d1d560763..c1c903648 100644 --- a/src/unix/windows_c/windows_pread.c +++ b/src/unix/windows_c/windows_pread.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_pread_job.c b/src/unix/windows_c/windows_pread_job.c index fa454ea9a..9630e7d45 100644 --- a/src/unix/windows_c/windows_pread_job.c +++ b/src/unix/windows_c/windows_pread_job.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_pwrite.c b/src/unix/windows_c/windows_pwrite.c index 29ce392d2..ba1e77533 100644 --- a/src/unix/windows_c/windows_pwrite.c +++ b/src/unix/windows_c/windows_pwrite.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_pwrite_job.c b/src/unix/windows_c/windows_pwrite_job.c index d56247a90..cef05566a 100644 --- a/src/unix/windows_c/windows_pwrite_job.c +++ b/src/unix/windows_c/windows_pwrite_job.c @@ -8,6 +8,7 @@ #if defined(LWT_ON_WINDOWS) #include +#include #include #include #include diff --git a/src/unix/windows_c/windows_read.c b/src/unix/windows_c/windows_read.c index caa773150..7e069f4bf 100644 --- a/src/unix/windows_c/windows_read.c +++ b/src/unix/windows_c/windows_read.c @@ -7,6 +7,7 @@ #if defined(LWT_ON_WINDOWS) +#include #include #include #include diff --git a/src/unix/windows_c/windows_read_job.c b/src/unix/windows_c/windows_read_job.c index 0ceda8076..ce40da2e8 100644 --- a/src/unix/windows_c/windows_read_job.c +++ b/src/unix/windows_c/windows_read_job.c @@ -7,6 +7,7 @@ #if defined(LWT_ON_WINDOWS) +#include #include #include #include diff --git a/src/unix/windows_c/windows_system_job.c b/src/unix/windows_c/windows_system_job.c index 883776cfb..dca8732da 100644 --- a/src/unix/windows_c/windows_system_job.c +++ b/src/unix/windows_c/windows_system_job.c @@ -10,9 +10,9 @@ #if OCAML_VERSION < 41300 #define CAML_INTERNALS #endif +#include #include #include -#include #include #include diff --git a/src/unix/windows_c/windows_write.c b/src/unix/windows_c/windows_write.c index 154877717..c826bc063 100644 --- a/src/unix/windows_c/windows_write.c +++ b/src/unix/windows_c/windows_write.c @@ -7,6 +7,7 @@ #if defined(LWT_ON_WINDOWS) +#include #include #include #include diff --git a/src/unix/windows_c/windows_write_job.c b/src/unix/windows_c/windows_write_job.c index ddbd86d92..4b8d2694c 100644 --- a/src/unix/windows_c/windows_write_job.c +++ b/src/unix/windows_c/windows_write_job.c @@ -7,6 +7,7 @@ #if defined(LWT_ON_WINDOWS) +#include #include #include #include diff --git a/test/test.ml b/test/test.ml index 037c1e325..be3aecc2e 100644 --- a/test/test.ml +++ b/test/test.ml @@ -1,6 +1,7 @@ (* This file is part of Lwt, released under the MIT license. See LICENSE.md for details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) +let () = Printexc.record_backtrace true type test = { test_name : string; @@ -9,6 +10,12 @@ type test = { run : unit -> bool Lwt.t; } +type suite = { + suite_name : string; + suite_tests : test list; + skip_suite_if_this_is_false : unit -> bool; +} + type outcome = | Passed | Failed @@ -28,15 +35,28 @@ let test_direct test_name ?(only_if = fun () -> true) run = let test test_name ?(only_if = fun () -> true) ?(sequential = false) run = {test_name; skip_if_this_is_false = only_if; sequential; run} +let running_in_ci = Option.is_some (Sys.getenv_opt "CI") + module Log = struct - let log_file = + let filename = let pid = Unix.getpid () in let ms = Unix.gettimeofday () |> modf |> fst in - let filename = Printf.sprintf "test.%i.%03.0f.log" pid (ms *. 1e3) in - open_out filename + Printf.sprintf "test.%i.%03.0f.log" pid (ms *. 1e3) + let log_file = open_out filename let () = - at_exit (fun () -> close_out_noerr log_file) + at_exit (fun () -> + if running_in_ci then + let ic = open_in filename in + try + while true do + let line = input_line ic in + print_endline line + done + with End_of_file -> + close_in ic ; + flush_all (); + close_out_noerr log_file) let start_time = ref None let elapsed () = @@ -59,15 +79,15 @@ end let log = Log.log -let run_test : test -> outcome Lwt.t = fun test -> +let run_test : suite -> test -> outcome Lwt.t = fun suite test -> if test.skip_if_this_is_false () = false then begin - log @@ (fun k -> k test.test_name "skipping"); + log @@ (fun k -> k (Printf.sprintf "[%s] %s" suite.suite_name test.test_name) "skipping"); Lwt.return Skipped end else begin let start_time = Unix.gettimeofday () in - log @@ (fun k -> k test.test_name "starting"); + log @@ (fun k -> k (Printf.sprintf "[%s] %s" suite.suite_name test.test_name) "starting"); (* Lwt.async_exception_hook handling inspired by https://github.com/mirage/alcotest/issues/45 *) @@ -102,7 +122,7 @@ let run_test : test -> outcome Lwt.t = fun test -> (fun () -> Lwt.async_exception_hook := old_async_exception_hook; let elapsed = Unix.gettimeofday () -. start_time in - log @@ (fun k -> k test.test_name "finished in %.3f s" elapsed); + log @@ (fun k -> k (Printf.sprintf "[%s] %s" suite.suite_name test.test_name) "finished in %.3f s" elapsed); Lwt.return_unit) end @@ -114,12 +134,6 @@ let outcome_to_character : outcome -> string = function -type suite = { - suite_name : string; - suite_tests : test list; - skip_suite_if_this_is_false : unit -> bool; -} - let contains_dup_tests suite tests = let names = List.map (fun t -> "suite:" ^ suite ^ " test:" ^ t.test_name) tests in @@ -145,6 +159,7 @@ let suite name ?(only_if = fun () -> true) tests = let run_test_suite : suite -> ((string * outcome) list) Lwt.t = fun suite -> if suite.skip_suite_if_this_is_false () = false then + let () = log @@ (fun k -> k suite.suite_name "skipping") in let outcomes = suite.suite_tests |> List.map (fun {test_name; _} -> (test_name, Skipped)) @@ -158,7 +173,7 @@ let run_test_suite : suite -> ((string * outcome) list) Lwt.t = fun suite -> else suite.suite_tests |> Lwt_list.map_s begin fun test -> - Lwt.bind (run_test test) (fun outcome -> + Lwt.bind (run_test suite test) (fun outcome -> outcome |> outcome_to_character |> print_string; flush stdout; Lwt.return (test.test_name, outcome)) @@ -281,7 +296,7 @@ let concurrent library_name suites = if suite.skip_suite_if_this_is_false () = false then Lwt.return Skipped else - run_test test + run_test suite test end >|= fun outcome -> print_string (outcome_to_character outcome); @@ -331,8 +346,14 @@ let concurrent library_name suites = | (suite, test), Exception exn -> Printf.eprintf "Test '%s' in suite '%s' raised '%s'\n" test.test_name suite.suite_name (Printexc.to_string exn) - | _ -> - ()); + | (suite, test), Skipped -> + if running_in_ci then + Printf.eprintf "Test '%s' in suite '%s' skipped\n" + test.test_name suite.suite_name + | (suite, test), Passed -> + if running_in_ci then + Printf.eprintf "Test '%s' in suite '%s' passed\n" + test.test_name suite.suite_name); exit 1 end diff --git a/test/unix/cloexec.ml b/test/unix/cloexec.ml new file mode 100644 index 000000000..2b2beb022 --- /dev/null +++ b/test/unix/cloexec.ml @@ -0,0 +1,71 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + +(* The CLOEXEC tests use execv(2) to execute this code, by passing --cloexec to + the copy of the tester in the child process. This is a module side effect + that interprets that --cloexec argument. *) +let () = + let is_fd_open fd = + let fd = (Obj.magic (int_of_string fd) : Unix.file_descr) in + let buf = Bytes.create 1 in + try + ignore (Unix.read fd buf 0 1); + true + with Unix.Unix_error (Unix.EBADF, _, _) -> + false + in + + match Sys.argv with + | [|_; "--cloexec"; fd; "--open"|] -> + if is_fd_open fd then + exit 0 + else + exit 1 + | [|_; "--cloexec"; fd; "--closed"|] -> + if is_fd_open fd then + exit 1 + else + exit 0 + | _ -> + () + +let test_cloexec ~closed flags = + let open Lwt.Infix in + Lwt_unix.openfile "/dev/zero" (Unix.O_RDONLY :: flags) 0o644 >>= fun fd -> + match Lwt_unix.fork () with + | 0 -> + let fd = string_of_int (Obj.magic (Lwt_unix.unix_file_descr fd)) in + let expected_status = if closed then "--closed" else "--open" in + (* There's no portable way to obtain the tester executable name (which may + even no longer exist at this point), but argv[0] fortunately has the + right value when the tests are run in the Lwt dev environment. *) + Unix.execv Sys.argv.(0) [|""; "--cloexec"; fd; expected_status|] + | n -> + Lwt_unix.close fd >>= fun () -> + Lwt_unix.waitpid [] n >>= function + | _, Unix.WEXITED 0 -> Lwt.return_true + | _, (Unix.WEXITED _ | Unix.WSIGNALED _ | Unix.WSTOPPED _) -> + Lwt.return_false + +let openfile_tests = [ + Test.test "openfile: O_CLOEXEC" ~only_if:(fun () -> not Sys.win32) + (fun () -> test_cloexec ~closed:true [Unix.O_CLOEXEC]); + + Test.test "openfile: O_CLOEXEC not given" ~only_if:(fun () -> not Sys.win32) + (fun () -> test_cloexec ~closed:false []); + + Test.test "openfile: O_KEEPEXEC" ~only_if:(fun () -> not Sys.win32) + (fun () -> test_cloexec ~closed:false [Unix.O_KEEPEXEC]); + + Test.test "openfile: O_CLOEXEC, O_KEEPEXEC" ~only_if:(fun () -> not Sys.win32) + (fun () -> test_cloexec ~closed:true [Unix.O_CLOEXEC; Unix.O_KEEPEXEC]); + + Test.test "openfile: O_KEEPEXEC, O_CLOEXEC" ~only_if:(fun () -> not Sys.win32) + (fun () -> test_cloexec ~closed:true [Unix.O_KEEPEXEC; Unix.O_CLOEXEC]); +] + + +let () = + Test.concurrent "unix.cloexec" [ + Test.suite "lwt_unix.cloexec" openfile_tests + ] diff --git a/test/unix/dummy.ml b/test/unix/dummy.ml index 2ec9f2577..f650450ac 100644 --- a/test/unix/dummy.ml +++ b/test/unix/dummy.ml @@ -23,9 +23,18 @@ let read () = let write fd = assert (test_input_len = Unix.write fd test_input 0 test_input_len) +let printenv () = + (* stdout is in text mode by default, which converts \n to \r\n on Windows. + switch to binary mode to prevent this, so the output is the same across + platforms. *) + set_binary_mode_out stdout true; + Array.iter (Printf.printf "%s\n") (Unix.unsafe_environment ()); + flush stdout + let () = match Sys.argv.(1) with | "read" -> exit @@ if read () then 0 else 1 | "write" -> write Unix.stdout | "errwrite" -> write Unix.stderr + | "printenv" -> printenv () | _ -> invalid_arg "Sys.argv" diff --git a/test/unix/dune b/test/unix/dune index ec809813f..5d48ac6ea 100644 --- a/test/unix/dune +++ b/test/unix/dune @@ -12,7 +12,9 @@ ocaml_runtime_exc_4 ocaml_runtime_exc_5 ocaml_runtime_exc_6 - set_engine))) + set_engine + cloexec + wait))) (executable (name dummy) @@ -67,3 +69,15 @@ (name set_engine) (libraries lwt lwt.unix) (modules set_engine)) + +(test + (package lwt) + (name cloexec) + (libraries lwt lwt.unix lwttester tester) + (modules cloexec)) + +(test + (package lwt) + (name wait) + (libraries lwt lwt.unix lwttester tester) + (modules wait)) diff --git a/test/unix/test_lwt_process.ml b/test/unix/test_lwt_process.ml index cad23768b..7404f336d 100644 --- a/test/unix/test_lwt_process.ml +++ b/test/unix/test_lwt_process.ml @@ -8,7 +8,6 @@ open Lwt.Infix let expected_str = "the quick brown fox jumps over the lazy dog" let expected = Bytes.of_string expected_str -let expected_len = Bytes.length expected let check_status ?(status=(=) 0) = function | Unix.WEXITED n when status n -> Lwt.return_true @@ -22,7 +21,8 @@ let check_status ?(status=(=) 0) = function Printf.eprintf "stopped with signal %d" x; Lwt.return_false -let pwrite ~stdin pout = +let pwrite ~stdin pout expected = + let expected_len = Bytes.length expected in let args = [|"dummy.exe"; "read"|] in let proc = Lwt_process.exec ~stdin ("./dummy.exe", args) in let write = Lwt.finalize @@ -33,26 +33,47 @@ let pwrite ~stdin pout = assert (n = expected_len); check_status r -let pread ?stdout ?stderr pin = - let buf = Bytes.create expected_len in - let proc = match stdout, stderr with - | Some stdout, None -> - let args = [|"dummy.exe"; "write"|] in - Lwt_process.exec ~stdout ("./dummy.exe", args) - | None, Some stderr -> - let args = [|"dummy.exe"; "errwrite"|] in - Lwt_process.exec ~stderr ("./dummy.exe", args) - | _ -> assert false +let read_all ic buf ofs len = + let rec loop ic buf ofs len = + Lwt_unix.read ic buf ofs len >>= function + | 0 -> + Lwt.return ofs + | n -> + let ofs = ofs + n in + let len = len - n in + if len = 0 then + Lwt.return ofs + else + loop ic buf ofs len in - let read = Lwt_unix.read pin buf 0 expected_len in + loop ic buf ofs len + +let pread ?env ?stdout ?stderr pin cmd expected = + (match stdout, stderr with + | Some _, None + | None, Some _ -> + () + | _ -> assert false); + let expected_len = Bytes.length expected in + let buf = Bytes.create expected_len in + let args = [|"dummy.exe"; cmd|] in + let proc = Lwt_process.exec ?env ?stdout ?stderr ("./dummy.exe", args) in + let read = read_all pin buf 0 expected_len in proc >>= fun r -> read >>= fun n -> - assert (n = expected_len); + (if n <> expected_len then Printf.ksprintf failwith "expected %d bytes, got %d" expected_len n); assert (Bytes.equal buf expected); Lwt_unix.read pin buf 0 1 >>= fun n -> - assert (n = 0); + if n <> 0 then Printf.ksprintf failwith "expected 0 bytes remaining, got %d" n; check_status r +let bytes_of_env env = + env + |> Array.map (Printf.sprintf "%s\n") + |> Array.to_list + |> String.concat "" + |> Bytes.of_string + let suite = suite "lwt_process" [ (* The sleep command is not available on Win32. *) test "lazy_undefined" ~only_if:(fun () -> not Sys.win32) @@ -93,15 +114,36 @@ let suite = suite "lwt_process" [ test "can write to subproc stdin" (fun () -> let pin, pout = Lwt_unix.pipe_out ~cloexec:true () in - pwrite ~stdin:(`FD_move pin) pout); + pwrite ~stdin:(`FD_move pin) pout expected); test "can read from subproc stdout" (fun () -> let pin, pout = Lwt_unix.pipe_in ~cloexec:true () in - pread ~stdout:(`FD_move pout) pin); + pread ~stdout:(`FD_move pout) pin "write" expected); test "can read from subproc stderr" (fun () -> let pin, perr = Lwt_unix.pipe_in ~cloexec:true () in - pread ~stderr:(`FD_move perr) pin); + pread ~stderr:(`FD_move perr) pin "errwrite" expected); + + test "overrides env" + (fun () -> + let env = [| "FOO=1" |] in + let expected = Bytes.of_string "FOO=1\n" in + let pin, pout = Lwt_unix.pipe_in ~cloexec:true () in + pread ~env ~stdout:(`FD_move pout) pin "printenv" expected); + + test "passes env" + (fun () -> + let env = Unix.unsafe_environment () in + let expected = bytes_of_env env in + let pin, pout = Lwt_unix.pipe_in ~cloexec:true () in + pread ~env ~stdout:(`FD_move pout) pin "printenv" expected); + + test "inherits env" + (fun () -> + let env = Unix.unsafe_environment () in + let expected = bytes_of_env env in + let pin, pout = Lwt_unix.pipe_in ~cloexec:true () in + pread ?env:None ~stdout:(`FD_move pout) pin "printenv" expected); ] diff --git a/test/unix/test_lwt_unix.ml b/test/unix/test_lwt_unix.ml index 7f02bc0e7..17435d08d 100644 --- a/test/unix/test_lwt_unix.ml +++ b/test/unix/test_lwt_unix.ml @@ -6,132 +6,6 @@ open Test open Lwt.Infix -(* An instance of the tester for the wait/waitpid tests. *) -let () = - match Sys.argv with - | [|_; "--child"|] -> - exit 42 - | _ -> - () - -let wait_tests = [ - test "wait" ~sequential:true ~only_if:(fun () -> not Sys.win32) begin fun () -> - match Lwt_unix.fork () with - | 0 -> - Unix.execv Sys.argv.(0) [|""; "--child"|] - | child_pid -> - Lwt_unix.wait () >|= function - | exited_pid, Lwt_unix.WEXITED 42 when exited_pid = child_pid -> true - | _ -> false - end; - - test "waitpid" ~sequential:true ~only_if:(fun () -> not Sys.win32) - begin fun () -> - match Lwt_unix.fork () with - | 0 -> - Unix.execv Sys.argv.(0) [|""; "--child"|] - | child_pid -> - Lwt_unix.waitpid [] child_pid >|= function - | exited_pid, Lwt_unix.WEXITED 42 when exited_pid = child_pid -> true - | _ -> false - end; - - test "waitpid: any child" ~sequential:true ~only_if:(fun () -> not Sys.win32) - begin fun () -> - match Lwt_unix.fork () with - | 0 -> - Unix.execv Sys.argv.(0) [|""; "--child"|] - | child_pid -> - Lwt_unix.waitpid [] 0 >|= function - | exited_pid, Lwt_unix.WEXITED 42 when exited_pid = child_pid -> true - | _ -> false - end; - - test "wait4" ~sequential:true ~only_if:(fun () -> not Sys.win32) - begin fun () -> - match Lwt_unix.fork () with - | 0 -> - Unix.execv Sys.argv.(0) [|""; "--child"|] - | child_pid -> - Lwt_unix.wait4 [] child_pid >|= function - | exited_pid, Lwt_unix.WEXITED 42, _ when exited_pid = child_pid -> true - | _ -> false - end; - - test "wait4: any child" ~sequential:true ~only_if:(fun () -> not Sys.win32) - begin fun () -> - match Lwt_unix.fork () with - | 0 -> - Unix.execv Sys.argv.(0) [|""; "--child"|] - | child_pid -> - Lwt_unix.wait4 [] 0 >|= function - | exited_pid, Lwt_unix.WEXITED 42, _ when exited_pid = child_pid -> true - | _ -> false - end; -] - -(* The CLOEXEC tests use execv(2) to execute this code, by passing --cloexec to - the copy of the tester in the child process. This is a module side effect - that interprets that --cloexec argument. *) -let () = - let is_fd_open fd = - let fd = (Obj.magic (int_of_string fd) : Unix.file_descr) in - let buf = Bytes.create 1 in - try - ignore (Unix.read fd buf 0 1); - true - with Unix.Unix_error (Unix.EBADF, _, _) -> - false - in - - match Sys.argv with - | [|_; "--cloexec"; fd; "--open"|] -> - if is_fd_open fd then - exit 0 - else - exit 1 - | [|_; "--cloexec"; fd; "--closed"|] -> - if is_fd_open fd then - exit 1 - else - exit 0 - | _ -> - () - -let test_cloexec ~closed flags = - Lwt_unix.openfile "/dev/zero" (Unix.O_RDONLY :: flags) 0o644 >>= fun fd -> - match Lwt_unix.fork () with - | 0 -> - let fd = string_of_int (Obj.magic (Lwt_unix.unix_file_descr fd)) in - let expected_status = if closed then "--closed" else "--open" in - (* There's no portable way to obtain the tester executable name (which may - even no longer exist at this point), but argv[0] fortunately has the - right value when the tests are run in the Lwt dev environment. *) - Unix.execv Sys.argv.(0) [|""; "--cloexec"; fd; expected_status|] - | n -> - Lwt_unix.close fd >>= fun () -> - Lwt_unix.waitpid [] n >>= function - | _, Unix.WEXITED 0 -> Lwt.return_true - | _, (Unix.WEXITED _ | Unix.WSIGNALED _ | Unix.WSTOPPED _) -> - Lwt.return_false - -let openfile_tests = [ - test "openfile: O_CLOEXEC" ~only_if:(fun () -> not Sys.win32) - (fun () -> test_cloexec ~closed:true [Unix.O_CLOEXEC]); - - test "openfile: O_CLOEXEC not given" ~only_if:(fun () -> not Sys.win32) - (fun () -> test_cloexec ~closed:false []); - - test "openfile: O_KEEPEXEC" ~only_if:(fun () -> not Sys.win32) - (fun () -> test_cloexec ~closed:false [Unix.O_KEEPEXEC]); - - test "openfile: O_CLOEXEC, O_KEEPEXEC" ~only_if:(fun () -> not Sys.win32) - (fun () -> test_cloexec ~closed:true [Unix.O_CLOEXEC; Unix.O_KEEPEXEC]); - - test "openfile: O_KEEPEXEC, O_CLOEXEC" ~only_if:(fun () -> not Sys.win32) - (fun () -> test_cloexec ~closed:true [Unix.O_KEEPEXEC; Unix.O_CLOEXEC]); -] - let utimes_tests = [ test "utimes: basic" (fun () -> @@ -1060,7 +934,7 @@ let lwt_preemptive_tests = [ Lwt.pause () >>= fun () -> Lwt.wakeup r 42; Lwt.return ()) - (function _ -> Stdlib.exit 2); + (function _ -> assert false); Lwt.return ()) in Lwt_preemptive.detach f () >>= fun () -> @@ -1251,9 +1125,7 @@ let dup_tests ~blocking = let suite = suite "lwt_unix" - (wait_tests @ - openfile_tests @ - utimes_tests @ + (utimes_tests @ readdir_tests @ io_vectors_byte_count_tests @ readv_tests @ diff --git a/test/unix/wait.ml b/test/unix/wait.ml new file mode 100644 index 000000000..cf9fd1b2c --- /dev/null +++ b/test/unix/wait.ml @@ -0,0 +1,77 @@ +(* This file is part of Lwt, released under the MIT license. See LICENSE.md for + details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) + + +(* An instance of the tester for the wait/waitpid tests. *) +let () = + match Sys.argv with + | [|_; "--child"|] -> + exit 42 + | _ -> + () + +let wait_tests = [ + Test.test "wait" ~sequential:true ~only_if:(fun () -> not Sys.win32) begin fun () -> + let open Lwt.Infix in + match Lwt_unix.fork () with + | 0 -> + Unix.execv Sys.argv.(0) [|""; "--child"|] + | child_pid -> + Lwt_unix.wait () >|= function + | exited_pid, Lwt_unix.WEXITED 42 when exited_pid = child_pid -> true + | _ -> false + end; + + Test.test "waitpid" ~sequential:true ~only_if:(fun () -> not Sys.win32) + begin fun () -> + let open Lwt.Infix in + match Lwt_unix.fork () with + | 0 -> + Unix.execv Sys.argv.(0) [|""; "--child"|] + | child_pid -> + Lwt_unix.waitpid [] child_pid >|= function + | exited_pid, Lwt_unix.WEXITED 42 when exited_pid = child_pid -> true + | _ -> false + end; + + Test.test "waitpid: any child" ~sequential:true ~only_if:(fun () -> not Sys.win32) + begin fun () -> + let open Lwt.Infix in + match Lwt_unix.fork () with + | 0 -> + Unix.execv Sys.argv.(0) [|""; "--child"|] + | child_pid -> + Lwt_unix.waitpid [] 0 >|= function + | exited_pid, Lwt_unix.WEXITED 42 when exited_pid = child_pid -> true + | _ -> false + end; + + Test.test "wait4" ~sequential:true ~only_if:(fun () -> not Sys.win32) + begin fun () -> + let open Lwt.Infix in + match Lwt_unix.fork () with + | 0 -> + Unix.execv Sys.argv.(0) [|""; "--child"|] + | child_pid -> + Lwt_unix.wait4 [] child_pid >|= function + | exited_pid, Lwt_unix.WEXITED 42, _ when exited_pid = child_pid -> true + | _ -> false + end; + + Test.test "wait4: any child" ~sequential:true ~only_if:(fun () -> not Sys.win32) + begin fun () -> + let open Lwt.Infix in + match Lwt_unix.fork () with + | 0 -> + Unix.execv Sys.argv.(0) [|""; "--child"|] + | child_pid -> + Lwt_unix.wait4 [] 0 >|= function + | exited_pid, Lwt_unix.WEXITED 42, _ when exited_pid = child_pid -> true + | _ -> false + end; +] + +let () = + Test.concurrent "unix.wait" [ + Test.suite "lwt_unix.wait" wait_tests + ]