commit f31c041d2a0b4aeb1904a4f5187b619280fa8e06
parent cf6325b779deb9dad1192b12eab9f0fc342cf2bb
Author: triesap <tyson@radroots.org>
Date: Tue, 22 Sep 2026 22:16:45 +0000
test(hyf): prove partial-pipe, fork and duplicate-report cleanup
- Route pipe creation and every fork site through shared rollback handlers so
a real pipe/fork failure closes the owned descriptors, with injected
causes exercising the same handler as a real failure
- Reject a second report line as a duplicate_report instead of accepting the
first as success, with a forged two-line child control
- 67/67 provider-helper tests pass with the affected lanes still green
Diffstat:
5 files changed, 99 insertions(+), 49 deletions(-)
diff --git a/tests/jev_provider_helper.mojo b/tests/jev_provider_helper.mojo
@@ -20,7 +20,7 @@ from parent_lifecycle import (
child_exit,
close_fd,
dup2_fd,
- fork_pid,
+ fork_owned_or_close,
make_pipe,
parse_ready_or_cleanup,
set_alarm,
@@ -405,7 +405,13 @@ struct SpawnedJevStub(Movable):
parsed.requests,
parsed.connections,
)
- if not report_status_matches_exit(
+ if self.state.pending != "":
+ # A second report line after the first is a duplicate/malformed
+ # report, never a success.
+ self.state.ok = False
+ self.state.phase = "parse"
+ self.state.reason = "duplicate_report"
+ elif not report_status_matches_exit(
status.exited, status.exit_code, parsed.ok
):
self.state.ok = False
@@ -613,13 +619,7 @@ def _spawn_jev_scripted(
) raises -> SpawnedJevStubAuto:
var total = len(scripts)
var pipe = make_pipe()
- var pid = 0
- try:
- pid = fork_pid()
- except e:
- close_fd(pipe.read_fd)
- close_fd(pipe.write_fd)
- raise Error("jev stub fork failed: " + String(e))
+ var pid = fork_owned_or_close(pipe.copy())
if pid == 0:
if dup2_fd(pipe.write_fd, 1) < 0:
child_exit(126)
@@ -644,13 +644,7 @@ def _spawn_jev_stub(
port: Int, mode: String, requests: Int, deadline_ms: Int
) raises -> SpawnedJevStubAuto:
var pipe = make_pipe()
- var pid = 0
- try:
- pid = fork_pid()
- except e:
- close_fd(pipe.read_fd)
- close_fd(pipe.write_fd)
- raise Error("jev stub fork failed: " + String(e))
+ var pid = fork_owned_or_close(pipe.copy())
if pid == 0:
if dup2_fd(pipe.write_fd, 1) < 0:
child_exit(126)
diff --git a/tests/max_local_process_helper.mojo b/tests/max_local_process_helper.mojo
@@ -22,7 +22,7 @@ from parent_lifecycle import (
child_exit,
close_fd,
dup2_fd,
- fork_pid,
+ fork_owned_or_close,
make_pipe,
parse_ready_or_cleanup,
set_alarm,
@@ -510,7 +510,13 @@ struct SpawnedMaxLocalStub(Movable):
parsed.requests,
parsed.connections,
)
- if not report_status_matches_exit(
+ if self.state.pending != "":
+ # A second report line after the first is a duplicate/malformed
+ # report, never a success.
+ self.state.ok = False
+ self.state.phase = "parse"
+ self.state.reason = "duplicate_report"
+ elif not report_status_matches_exit(
status.exited, status.exit_code, parsed.ok
):
self.state.ok = False
@@ -658,13 +664,7 @@ def _spawn_max_local(
deadline_ms: Int,
) raises -> SpawnedMaxLocalStub:
var pipe = make_pipe()
- var pid = 0
- try:
- pid = fork_pid()
- except e:
- close_fd(pipe.read_fd)
- close_fd(pipe.write_fd)
- raise Error("max_local stub fork failed: " + String(e))
+ var pid = fork_owned_or_close(pipe.copy())
if pid == 0:
if dup2_fd(pipe.write_fd, 1) < 0:
child_exit(126)
diff --git a/tests/parent_lifecycle.mojo b/tests/parent_lifecycle.mojo
@@ -84,26 +84,30 @@ struct PipeTriple(Movable):
var stderr_pipe: PipeFds
-def close_pipe(var pipe: PipeFds):
+def close_pipe(pipe: PipeFds):
close_fd(pipe.read_fd)
close_fd(pipe.write_fd)
def make_three_pipes(inject_fail_after: Int = -1) raises -> PipeTriple:
- """Create three owned pipes, closing earlier ones if a later one fails.
+ """Create three owned pipes, closing earlier ones if any creation fails.
- ``inject_fail_after`` is a test-only control: when >= 0 the constructor
- raises after that many successful pipes, proving the rollback path.
+ ``inject_fail_after`` is a test-only control: when >= 0 a failure is
+ raised after that many successful pipes. The injected failure and a real
+ ``make_pipe`` failure share the same rollback handler.
"""
var fds = InlineArray[Int, 6](fill=-1)
- for index in range(3):
- if inject_fail_after >= 0 and index == inject_fail_after:
- for slot in range(6):
- close_fd(fds[slot])
- raise Error("lifecycle: injected pipe creation failure")
- var pipe = make_pipe()
- fds[index * 2] = pipe.read_fd
- fds[index * 2 + 1] = pipe.write_fd
+ try:
+ for index in range(3):
+ if inject_fail_after >= 0 and index == inject_fail_after:
+ raise Error("lifecycle: injected pipe creation failure")
+ var pipe = make_pipe()
+ fds[index * 2] = pipe.read_fd
+ fds[index * 2 + 1] = pipe.write_fd
+ except:
+ for slot in range(6):
+ close_fd(fds[slot])
+ raise
return PipeTriple(
PipeFds(fds[0], fds[1]),
PipeFds(fds[2], fds[3]),
@@ -111,6 +115,40 @@ def make_three_pipes(inject_fail_after: Int = -1) raises -> PipeTriple:
)
+def fork_owned_or_close(
+ pipe: PipeFds, inject_failure: Bool = False
+) raises -> Int:
+ """Fork the owned child, closing both pipe ends if the fork fails.
+
+ A real ``fork`` failure and the test-only injected failure share the same
+ rollback handler, so partial-startup cleanup is execution-proven.
+ """
+ try:
+ if inject_failure:
+ raise Error("lifecycle: injected fork failure")
+ return fork_pid()
+ except:
+ close_pipe(pipe.copy())
+ raise
+ return -1
+
+
+def fork_owned_or_close3(
+ pipes: PipeTriple, inject_failure: Bool = False
+) raises -> Int:
+ """Fork the stdio child, closing all three pipe pairs if the fork fails."""
+ try:
+ if inject_failure:
+ raise Error("lifecycle: injected fork failure")
+ return fork_pid()
+ except:
+ close_pipe(pipes.stdin_pipe.copy())
+ close_pipe(pipes.stdout_pipe.copy())
+ close_pipe(pipes.stderr_pipe.copy())
+ raise
+ return -1
+
+
def ignore_sigpipe():
"""Ignore SIGPIPE so a peer-close race surfaces as EPIPE, not parent death.
diff --git a/tests/stdio_process_helper.mojo b/tests/stdio_process_helper.mojo
@@ -26,7 +26,7 @@ from parent_lifecycle import (
child_exit,
close_fd,
dup2_fd,
- fork_pid,
+ fork_owned_or_close3,
make_three_pipes,
now_ms,
poll_three,
@@ -190,17 +190,7 @@ def run_stdio_entrypoint_with_deadline(
var command_ptr = command.as_c_string_slice().unsafe_ptr()
var argv_ptr = argv.unsafe_ptr()
- var pid = 0
- try:
- pid = fork_pid()
- except e:
- close_fd(stdin_read_fd)
- close_fd(stdin_write_fd)
- close_fd(stdout_read_fd)
- close_fd(stdout_write_fd)
- close_fd(stderr_read_fd)
- close_fd(stderr_write_fd)
- raise Error("stdio-entrypoint fork failed: " + String(e))
+ var pid = fork_owned_or_close3(pipes)
if pid == 0:
if dup2_fd(stdin_read_fd, 0) < 0:
child_exit(126)
diff --git a/tests/test_provider_helpers.mojo b/tests/test_provider_helpers.mojo
@@ -20,6 +20,7 @@ from parent_lifecycle import (
close_fd,
descriptor_census,
dup2_fd,
+ fork_owned_or_close,
fork_pid,
make_pipe,
make_three_pipes,
@@ -1264,6 +1265,33 @@ def test_partial_pipe_failure_rolls_back() raises:
assert_equal(open_fd_count_checked(), before)
+def test_fork_failure_closes_owned_pipes() raises:
+ # LC01: a fork failure must close both ends of the owned pipe.
+ var before = open_fd_count_checked()
+ var pipe = make_pipe()
+ var message = ""
+ try:
+ _ = fork_owned_or_close(pipe.copy(), True)
+ except e:
+ message = String(e)
+ assert_true(message.find("injected fork failure") >= 0)
+ assert_equal(open_fd_count_checked(), before)
+
+
+def test_result_truth_rejects_duplicate_report_line() raises:
+ var stub = _owned_report_child(
+ 0,
+ (
+ "result ok phase=complete case=- reason=ok requests=1"
+ " connections=1\nresult ok phase=complete case=- reason=ok"
+ " requests=1 connections=1\n"
+ ),
+ )
+ stub.reap()
+ assert_true(not stub.ok())
+ assert_equal(stub.reason(), "duplicate_report")
+
+
def test_cleanup_failure_is_observable() raises:
# LC01/D36: cleanup failure must be observable, never silently swallowed.
var state = PipedChildState(