Skip to content

test(cie): add minimal pub/sub launch_testing for CIE#57

Merged
atsushi421 merged 3 commits into
mainfrom
test/cie-launch-testing
Jun 21, 2026
Merged

test(cie): add minimal pub/sub launch_testing for CIE#57
atsushi421 merged 3 commits into
mainfrom
test/cie-launch-testing

Conversation

@atsushi421

@atsushi421 atsushi421 commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

Description

Add a launch_testing integration test that exercises CallbackIsolatedExecutor (CIE) at runtime with a minimal publisher and subscriber, and wire CI to run it on both Humble and Jazzy. Until now the repository had no functional tests (the BUILD_TESTING blocks only ran ament_lint_auto, and CI did colcon build with a # TODO: run tests), so "the build passes" did not imply "CIE actually works". This test closes that gap by verifying both that messages flow end-to-end and that CIE actually isolates callbacks onto separate threads.

What it does:

  • Test node (callback_isolated_executor/test/cie_pubsub_test_node.cpp): a talker node publishes an incrementing std_msgs/Int32 on chatter from a wall-timer in a MutuallyExclusive callback group, and a listener node subscribes to chatter and republishes the value to echo from its own callback group. Both nodes are added to a single CallbackIsolatedExecutor and spun. Two separate nodes are used so the multi-node path in spin() is exercised and the subscriber's execution is observable from outside the process (via echo).
  • Launch test (callback_isolated_executor/test/test_cie_pubsub.launch.py): an rclpy observer runs two checks against the live process:
    • test_echo_messages_flow: subscribes to echo and asserts it receives at least 5 messages within a timeout and that the values increase monotonically, proving both the publisher and subscriber callbacks actually ran inside CIE.
    • test_callback_groups_isolated: subscribes to /cie_thread_configurator/callback_group_info (the topic CIE publishes a (callback_group_id, thread_id) pair on for each callback group) and asserts at least two distinct thread ids are reported. This directly verifies CIE's defining behavior: isolating callbacks onto separate threads, which the echo round-trip alone does not establish. The topic is transient_local, so the late-joining observer still receives all latched samples without a timing race.
    • A post-shutdown check asserts the process exits cleanly on SIGINT.
  • Build/packaging: the test node is built and installed to lib/${PROJECT_NAME} and the launch test registered via add_launch_test, all under a second BUILD_TESTING block (placed after the library target so it can link against it). launch_testing_ament_cmake, launch_ros, and rclpy are added as test_depend (cie_config_msgs, needed for the CallbackGroupInfo message, is already a regular dependency).
  • CI: the # TODO: run tests is replaced with a colcon test step that runs on both Humble and Jazzy.

Design notes / trade-offs:

  • CIE runs standalone: the thread_configurator node is not required, because publish_callback_group_info just publishes on a transient-local topic that no-ops when nobody subscribes. The test observes that topic directly but does not launch the configurator.
  • Shutdown is driven by SIGINT. CIE's spin() spawns one thread per callback group and joins them, and cancel() is not propagated to the per-group executors; on SIGINT the per-group spin loops observe rclcpp::ok() == false and return, so the process exits cleanly. The exit-code assertion accepts both 0 and -2 to absorb distro differences.
  • The CI test step intentionally runs only the launch_test label (--ctest-args -L launch_test). The ament C++ linters (copyright/cpplint/uncrustify) are not used by this project, which enforces style via clang-format in the separate run-pre-commit workflow, and the existing sources predate those linters. Keeping the linters out of the CI gate avoids failing on pre-existing, unadopted lint while still running the new functional test. The lint scaffolding in CMakeLists.txt is left untouched.
  • Flakiness is mitigated with a 10 Hz talker, QoS matching the publishers, generous timeouts, low thresholds, and a monotonic-increase (not exact-start) assertion. The callback_group_info check relies on transient_local durability so it is not sensitive to subscribe timing.

Verified locally on Humble: colcon build + colcon test --ctest-args -L launch_test passes (all three checks green, clean SIGINT shutdown).

Related links

How was this PR tested?

Notes for reviewers

Add a launch_testing integration test that runs a minimal publisher and
subscriber on a CallbackIsolatedExecutor and asserts messages flow
end-to-end, exercising CIE's per-callback-group threading at runtime.

- Add a test-only talker/listener node (test/cie_pubsub_test_node.cpp):
  the talker publishes an incrementing Int32 on "chatter" from a
  MutuallyExclusive timer group, the listener echoes it to "echo" from
  its own group, both spun by one CallbackIsolatedExecutor.
- Add the launch test (test/test_cie_pubsub.launch.py): an rclpy node
  subscribes to "echo" and asserts >= 5 messages arrive and increase
  monotonically, plus a clean-exit check on SIGINT shutdown.
- Build/install the test node and register the launch test under
  BUILD_TESTING; add launch_testing_ament_cmake, launch_ros, rclpy as
  test dependencies.
- Wire CI to run the launch test (colcon test ... -L launch_test) on both
  Humble and Jazzy. Only the launch_test label is run; code style stays
  owned by the run-pre-commit workflow (clang-format).

Signed-off-by: atsushi421 <atsushi.yano.2@tier4.jp>
Subscribe to /cie_thread_configurator/callback_group_info from the rclpy
observer and assert CIE published per-callback-group thread info with at
least two distinct thread ids. This checks CIE's defining behavior
(isolating callbacks onto separate threads), which the echo round-trip
alone does not. The topic is transient_local, so the late-joining
observer still receives all latched samples.

Signed-off-by: atsushi421 <atsushi.yano.2@tier4.jp>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a minimal ROS 2 launch_testing integration test to validate CallbackIsolatedExecutor end-to-end (pub/sub message flow + per-callback-group thread isolation) and wires GitHub Actions CI to run the functional test on Humble and Jazzy.

Changes:

  • Add a C++ pub/sub test executable that spins two nodes under a single CallbackIsolatedExecutor.
  • Add a Python launch_testing test that asserts message flow on echo and validates callback-group thread isolation via /cie_thread_configurator/callback_group_info.
  • Update build/packaging and CI to build/install the test node and run the launch_test-labeled test via colcon test.

Reviewed changes

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

Show a summary per file
File Description
callback_isolated_executor/test/test_cie_pubsub.launch.py New launch test: starts the test process and performs runtime assertions via an rclpy observer.
callback_isolated_executor/test/cie_pubsub_test_node.cpp New C++ runtime test node: minimal talker/listener under CallbackIsolatedExecutor.
callback_isolated_executor/package.xml Adds test dependencies needed for launch testing and rclpy observer.
callback_isolated_executor/CMakeLists.txt Builds/installs the test executable and registers the launch test under BUILD_TESTING.
.github/workflows/build-and-test.yaml Adds a colcon test step to execute only the functional launch test in CI.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread callback_isolated_executor/test/test_cie_pubsub.launch.py
Comment thread .github/workflows/build-and-test.yaml
- Drop name= from the launch_ros Node so its global __node remap no longer
  renames the executable's internal talker/listener nodes to the same name
  (which triggered duplicate-name rosout warnings).
- Harden the check_diff_cpp regex (leading '*' -> '.*') so the change gate
  does not depend on grep's lenient handling of an empty subexpression.

Signed-off-by: atsushi421 <atsushi.yano.2@tier4.jp>
@atsushi421 atsushi421 marked this pull request as ready for review June 21, 2026 23:24
@atsushi421 atsushi421 merged commit 8963603 into main Jun 21, 2026
5 checks passed
@atsushi421 atsushi421 deleted the test/cie-launch-testing branch June 21, 2026 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants