hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

commit cf6325b779deb9dad1192b12eab9f0fc342cf2bb
parent 6b59a8ec82cd7754605b71a5aefe72ba069bb724
Author: triesap <tyson@radroots.org>
Date:   Tue, 22 Sep 2026 22:08:56 +0000

test(hyf): close C002C pipe-rollback and accounting review findings

- Create the stdio runner pipes through a rollback constructor so a later
  pipe failure closes the earlier descriptors, with an injected-failure
  control proving the partial-pipe path
- Add executed controls for cleanup-failure observability, wrong request and
  connection accounting, and a non-timeout completion-probe error treated as
  failure rather than success
- Close the descriptor planted by the census control and assert the count
  returns to its baseline

Diffstat:
Mtests/parent_lifecycle.mojo | 40+++++++++++++++++++++++++++++++++++++++-
Mtests/stdio_process_helper.mojo | 18++++++++----------
Mtests/test_provider_helpers.mojo | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 130 insertions(+), 13 deletions(-)

diff --git a/tests/parent_lifecycle.mojo b/tests/parent_lifecycle.mojo @@ -68,10 +68,48 @@ def now_ms() -> Int: @fieldwise_init -struct PipeFds(Movable): +struct PipeFds(Copyable, Movable): var read_fd: Int var write_fd: Int + def __copyinit__(out self, existing: Self): + self.read_fd = existing.read_fd + self.write_fd = existing.write_fd + + +@fieldwise_init +struct PipeTriple(Movable): + var stdin_pipe: PipeFds + var stdout_pipe: PipeFds + var stderr_pipe: PipeFds + + +def close_pipe(var 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. + + ``inject_fail_after`` is a test-only control: when >= 0 the constructor + raises after that many successful pipes, proving the rollback path. + """ + 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 + return PipeTriple( + PipeFds(fds[0], fds[1]), + PipeFds(fds[2], fds[3]), + PipeFds(fds[4], fds[5]), + ) + 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 @@ -27,7 +27,7 @@ from parent_lifecycle import ( close_fd, dup2_fd, fork_pid, - make_pipe, + make_three_pipes, now_ms, poll_three, read_fd, @@ -152,9 +152,7 @@ def run_stdio_entrypoint_with_deadline( arg1: String, deadline_ms: Int, ) raises -> Value: - var stdin_pipe = make_pipe() - var stdout_pipe = make_pipe() - var stderr_pipe = make_pipe() + var pipes = make_three_pipes() var command = String("mojo") var include_flag = String("-I") @@ -183,12 +181,12 @@ def run_stdio_entrypoint_with_deadline( process_arg1.as_c_string_slice() ) - var stdin_read_fd = stdin_pipe.read_fd - var stdin_write_fd = stdin_pipe.write_fd - var stdout_read_fd = stdout_pipe.read_fd - var stdout_write_fd = stdout_pipe.write_fd - var stderr_read_fd = stderr_pipe.read_fd - var stderr_write_fd = stderr_pipe.write_fd + var stdin_read_fd = pipes.stdin_pipe.read_fd + var stdin_write_fd = pipes.stdin_pipe.write_fd + var stdout_read_fd = pipes.stdout_pipe.read_fd + var stdout_write_fd = pipes.stdout_pipe.write_fd + var stderr_read_fd = pipes.stderr_pipe.read_fd + var stderr_write_fd = pipes.stderr_pipe.write_fd var command_ptr = command.as_c_string_slice().unsafe_ptr() var argv_ptr = argv.unsafe_ptr() diff --git a/tests/test_provider_helpers.mojo b/tests/test_provider_helpers.mojo @@ -9,6 +9,7 @@ from std.testing import TestSuite, assert_true, assert_equal from std.ffi import ErrNo, c_int, external_call from flare.net import SocketAddr +from flare.net.socket import RawSocket from flare.tcp import TcpListener, TcpStream from parent_lifecycle import ( @@ -21,7 +22,9 @@ from parent_lifecycle import ( dup2_fd, fork_pid, make_pipe, + make_three_pipes, open_fd_count, + open_fd_count_checked, parse_ready_line, parse_ready_or_cleanup, pid_not_waitable, @@ -33,6 +36,7 @@ from parent_lifecycle import ( write_raw, ) from strict_fixture import ( + ConnectionReader, ExchangeScript, FramedRequest, authorization_reason, @@ -1243,9 +1247,86 @@ def test_descriptor_census_detects_planted_high_fd() raises: assert_true(planted >= 0) assert_true(with_pipe > before) close_fd(planted) + close_fd(pipe.read_fd) close_fd(pipe.write_fd) - var after = open_fd_count() - assert_true(after <= with_pipe) + assert_equal(open_fd_count(), before) + + +def test_partial_pipe_failure_rolls_back() raises: + # LC01: a later pipe failure must close the pipes already created. + var before = open_fd_count_checked() + var message = "" + try: + _ = make_three_pipes(1) + except e: + message = String(e) + assert_true(message.find("injected pipe creation failure") >= 0) + assert_equal(open_fd_count_checked(), before) + + +def test_cleanup_failure_is_observable() raises: + # LC01/D36: cleanup failure must be observable, never silently swallowed. + var state = PipedChildState( + pid=0, + report_fd=-1, + pending="", + eof=False, + closed=False, + deadline_ms=100, + expected_requests=1, + reaped=False, + ok=False, + phase="pending", + case_label="-", + reason="not_reaped", + requests=0, + connections=0, + cleanup_error="", + status=ProcessStatus("pending", False, -1, 0, 0, ""), + observed=ProcessStatus("pending", False, -1, 0, 0, ""), + observed_valid=False, + ) + var stub = SpawnedMaxLocalStub(0, 0, state^) + stub.cleanup() + assert_true(stub.cleanup_error().find("unreaped") >= 0) + assert_true(not stub.status().cleanup_proved()) + + +def test_result_truth_rejects_wrong_request_count() raises: + var stub = _owned_report_child( + 0, + "result ok phase=complete case=- reason=ok requests=2 connections=1\n", + ) + stub.reap() + assert_true(not stub.ok()) + assert_equal(stub.reason(), "request_count_mismatch") + + +def test_result_truth_rejects_invalid_connection_count() raises: + var stub = _owned_report_child( + 0, + "result ok phase=complete case=- reason=ok requests=1 connections=5\n", + ) + stub.reap() + assert_true(not stub.ok()) + assert_equal(stub.reason(), "connection_count_invalid") + + +def test_completion_probe_error_is_not_success() raises: + # LC05: a non-timeout completion-probe I/O/setup error must not be read as + # a successful completion. + var pipe = make_pipe() + var sock = RawSocket(c_int(pipe.read_fd), c_int(2), c_int(1), True) + var stream = TcpStream(sock^, SocketAddr.localhost(UInt16(1))) + var reader = ConnectionReader(stream^) + var raised = False + try: + _ = reader.probe_completion(20) + except e: + raised = True + _ = String(e) + close_fd(pipe.write_fd) + assert_true(raised) def test_descriptor_census_detects_planted_socket() raises: