From 6556a5d22216a047c99e6c8d85e6ee0cef5d7b74 Mon Sep 17 00:00:00 2001 From: Rudi Grinberg Date: Sat, 18 Jul 2026 02:05:43 +0100 Subject: [PATCH] test(spawn): reproduce lost exec errors with closed standard fds The spawn error pipe can occupy file descriptors 0 or 1 when standard descriptors are closed. Child redirection then overwrites the pipe write end, so an exec failure looks like a clean close and the parent returns a pid. Fork a helper with stdin and stdout closed, redirect the spawned process to /dev/null, and show that spawning a nonexistent executable currently returns instead of raising. Signed-off-by: Rudi Grinberg --- otherlibs/stdune/test/spawn/tests.ml | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/otherlibs/stdune/test/spawn/tests.ml b/otherlibs/stdune/test/spawn/tests.ml index ee886e87486..310d4a925dc 100644 --- a/otherlibs/stdune/test/spawn/tests.ml +++ b/otherlibs/stdune/test/spawn/tests.ml @@ -27,6 +27,36 @@ let%expect_test "non-existing program" = |}] ;; +let%expect_test "exec failure returns if standard descriptors are closed" = + if Platform.OS.value = Linux + then + assert ( + match Unix.fork () with + | 0 -> + let dev_null = Unix.openfile "/dev/null" [ O_RDWR ] 0 in + Unix.close Unix.stdin; + Unix.close Unix.stdout; + (match + Spawn.spawn + () + ~prog:"/doesnt-exist" + ~argv:[ "blah" ] + ~stdin:dev_null + ~stdout:dev_null + ~stderr:dev_null + with + | pid -> + (match Proc.wait (Pid pid) [] with + | Some _ -> Unix._exit 0 + | None -> Unix._exit 2) + | exception Unix.Unix_error _ -> Unix._exit 1) + | child -> + (match Proc.wait (Pid (Pid.of_int_exn child)) [] with + | Some { status = WEXITED 0; _ } -> true + | Some _ | None -> false)); + [%expect {||}] +;; + let%expect_test "non-existing dir" = show_raise (fun () -> Spawn.spawn