Skip to content

test(interop): stop passing when the C++ node fails - #306

Merged
YuanYuYuan merged 4 commits into
mainfrom
test/interop-assert-child-outcome
Aug 14, 2026
Merged

test(interop): stop passing when the C++ node fails#306
YuanYuYuan merged 4 commits into
mainfrom
test/interop-assert-child-outcome

Conversation

@YuanYuYuan

@YuanYuYuan YuanYuYuan commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two of the three hiroz → C++ interop tests asserted nothing about the C++ process. They passed whether it worked, crashed, or never started.

Before / after

Test Before After
test_hiroz_talker_to_rcl_listener pipes the listener's stdout, never reads it, sleeps 1s, prints "Test passed" polls stdout and stderr for I heard on a 15s deadline
test_hiroz_fibonacci_action_server_to_rcl_client sleeps 10s, prints "Test passed" waits on the client's real exit, requires success, prints its captured output on failure
test_hiroz_add_two_ints_server_to_rcl_client asserts exit status unchanged, plus the finish() fix below

wait_for_ready is thread::sleep. Neither of the first two read the child's output or its exit status, so neither could fail because of the C++ side.

Two details behind the "after" column. ROS 2 logs to stderr by default, so the listener assertion reads both streams. The action client's budget is 15s because its server lives 10s. A client that has not succeeded by then never will.

A defect this also fixes on main

OutputCapture::finish() joins the reader threads, and they block on the open pipes until the child exits. Both timeout branches called it while the child was still running, so the panic never rendered: the test stalled until nextest's 120s kill and printed nothing. That is the failure the capture exists to prevent.

add_two_ints carries that defect on main from #304. Both call sites now kill the child before rendering.

Evidence

27 checks green, 0 failed. Both new assertions pass on humble, jazzy, kilted and lyrical.

Note

No failing baseline on hiroz code, and none claimed — this adds assertions and changes no hiroz behaviour. The check that they are not over-strict is that all four distros pass with them in place.

The finish() fix is not exercised by a green run: that branch needs a client that never exits. It was verified by reading, not by execution.

Breaking changes

None. Test-only.

Two of the three hiroz-to-C++ interop tests asserted nothing about the
C++ process. The talker test piped the listener's stdout and never read
it; the action test slept for ten seconds and printed 'Test passed'. Both
pass whether the C++ node consumes hiroz output, crashes, or never starts.

That mattered for #303: the double-free there looked confined to the
service path, but it was confined to the only test that checks. These two
could have been failing identically all along.

The listener never exits on its own, so OutputCapture now appends line by
line and exposes a snapshot, rather than only yielding text at EOF.
The listener assertion looked at stdout only and saw an empty buffer, so
it reported that the C++ node received nothing. ROS 2 logging goes to
stderr by default: the I heard lines were never on stdout, and the test
was piping stdout while sending stderr to /dev/null.

The three previously-green distros caught this, which is what they were
there for.
@YuanYuYuan
YuanYuYuan force-pushed the test/interop-assert-child-outcome branch from 8377c5d to dad9049 Compare August 14, 2026 11:47
OutputCapture::finish joins the reader threads, and they block on the open
pipes until the child exits. Both timeout branches called it while the
child was provably still running, so the panic never rendered: the test
stalled until nextest's 120s kill and printed nothing. That is the exact
failure the capture exists to prevent.

The add_two_ints branch has this defect on main already, from #304. Fixed
in both call sites.

Two smaller changes from the same review:

- The listener assertion sampled once after a fixed 1s sleep. The talker
  publishes a finite ~900ms burst, so a single sample races CI-load
  stalls. It now polls on a 15s deadline, as the reverse-direction test
  already does for the same reason.
- The action client's 30s deadline was decorative. Its server lives 10s
  and the client starts ~2s in, so a client that has not succeeded by
  then never will. Bounded at 15s and named.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Strengthens ROS 2 C++ interoperability tests so they verify spawned processes actually succeed.

Changes:

  • Captures and polls listener output for received messages.
  • Validates the Fibonacci client’s exit and diagnostics.
  • Prevents output capture from blocking before child termination.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
crates/hiroz-tests/tests/demo_nodes.rs Adds C++ process assertions and safer timeout cleanup.
crates/hiroz-tests/tests/common/mod.rs Supports snapshots of concurrently captured output.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/hiroz-tests/tests/demo_nodes.rs
Comment thread crates/hiroz-tests/tests/demo_nodes.rs
…the guard

A zero exit status did not prove the client completed the action.
action_tutorials_cpp calls rclcpp::shutdown() when its 10s
wait_for_action_server expires, so the client exits 0 after logging
'Action server not available after waiting'. The assertion therefore
passed in the one case it exists to catch: the hiroz server never
discovered.

Verified against ros2/demos: 'Result received: ' is written only in the
SUCCEEDED arm of the result callback. The test now requires it.

Separately, try_wait reaps the child but left it in ProcessGuard. Drop
signals the process group by negative PID, and for the action test that
runs only after the 10s server thread joins -- by which point the PGID may
have been recycled. Both tests now take the child out of the guard as soon
as an exit status is observed.
@YuanYuYuan YuanYuYuan changed the title test(interop): assert the C++ node actually worked test(interop): stop passing when the C++ node fails Aug 14, 2026
@YuanYuYuan
YuanYuYuan merged commit 7138961 into main Aug 14, 2026
28 checks passed
@YuanYuYuan
YuanYuYuan deleted the test/interop-assert-child-outcome branch August 14, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants