Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt

Use this file to discover all available pages before exploring further.

Writing tests in C++

For C++ packages, Roboticks ships roboticks_cpp: a header-only ament_cmake INTERFACE library that adds a single macro on top of gtest, plus a thread-safe registry that records which requirements each test confirms.
v0.1 is registry-only. The C++ side records confirms IDs; a small downstream stitcher merges them into the JUnit XML that gtest emits. Phase 11 will add a native JUnit reporter so the macro writes properties directly. The wire shape on the platform side is identical either way.

Install the package

In your package.xml:
<test_depend>roboticks_cpp</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
In your CMakeLists.txt:
if(BUILD_TESTING)
  find_package(ament_cmake_gtest REQUIRED)
  find_package(roboticks_cpp REQUIRED)

  ament_add_gtest(test_estop
    test/test_estop.cpp
  )
  target_link_libraries(test_estop
    roboticks_cpp::roboticks_cpp
    ${rclcpp_LIBRARIES}
  )
endif()
roboticks_cpp::roboticks_cpp is an INTERFACE target — no objects to compile, just headers and one inline registry definition.

The ROBOTICKS_CONFIRMS macro

The macro takes a SuiteName_TestName token and a comma-separated list of requirement IDs. Place it at the top of the test body so the registration happens before any assertion fires:
#include <gtest/gtest.h>
#include <rclcpp/rclcpp.hpp>
#include <roboticks_cpp/confirms.hpp>

TEST(EmergencyStop, HaltsWithin100ms) {
  ROBOTICKS_CONFIRMS(EmergencyStop_HaltsWithin100ms, "REQ-001", "REQ-014");

  rclcpp::init(0, nullptr);
  auto node = std::make_shared<MyEStopNode>();

  const auto t0 = std::chrono::steady_clock::now();
  node->trigger_estop();
  spin_until(node, [&]{ return node->is_stopped(); }, std::chrono::milliseconds(150));
  const auto elapsed = std::chrono::steady_clock::now() - t0;

  EXPECT_LT(elapsed, std::chrono::milliseconds(100));
  rclcpp::shutdown();
}
The token mirrors SuiteName_TestName so the stitcher can join macro entries to gtest’s JUnit <testcase name="..." classname="..."> rows.

The ConfirmsRegistry

ROBOTICKS_CONFIRMS calls ConfirmsRegistry::instance().add(token, req_ids). The registry:
  • Lives in a single translation unit (defined in the INTERFACE library’s one .cpp companion).
  • Is thread-safe — guarded by a std::mutex so parallel test executors don’t race.
  • Exposes add(token, ids), dump(path) to write JSON, and clear() for in-process re-use.
A typical gtest main dumps the registry on exit:
int main(int argc, char ** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  const int rc = RUN_ALL_TESTS();
  roboticks_cpp::ConfirmsRegistry::instance().dump("test_results/confirms.json");
  return rc;
}
ament_cmake_gtest provides this main for you when you don’t override it; the registry hook lives in the bundled roboticks_cpp_gtest_main target if you’d rather link that instead:
target_link_libraries(test_estop roboticks_cpp::gtest_main)
See the C++ reference for the full API.

Stitching confirms into JUnit

ament_cmake_gtest writes test_results/<package>/<test>.gtest.xml. The stitcher (shipped as roboticks-stitch-cpp-junit, installed by pip install roboticks) merges confirms.json into the XML:
roboticks-stitch-cpp-junit \
  --gtest-xml test_results/my_pkg/test_estop.gtest.xml \
  --confirms test_results/confirms.json \
  --out test_results/my_pkg/test_estop.junit.xml
The output matches the wire contract. When you upload via rbtk test cloud or the GitHub App, the platform reads the stitched XML directly.
The stitcher is wired into colcon test --event-handlers roboticks+ automatically when both roboticks and roboticks_cpp are on PATH / AMENT_PREFIX_PATH. You almost never run it by hand.

Gtest assertion helpers

roboticks_cpp ships rclcpp-aware helpers that mirror the Python ones:
#include <roboticks_cpp/assertions.hpp>

TEST(NavStack, PublishesCmdVel) {
  ROBOTICKS_CONFIRMS(NavStack_PublishesCmdVel, "REQ-007");
  auto node = rclcpp::Node::make_shared("test_observer");
  auto msg = roboticks_cpp::assert_topic_published<geometry_msgs::msg::Twist>(
    node, "/cmd_vel", std::chrono::seconds(3));
  EXPECT_GT(msg.linear.x, 0.1);
}
Helpers raise gtest FAIL() macros on timeout so failures slot into the standard gtest summary.

A full example

// test/test_collision_check.cpp
#include <gtest/gtest.h>
#include <rclcpp/rclcpp.hpp>
#include <roboticks_cpp/confirms.hpp>
#include <roboticks_cpp/assertions.hpp>
#include <my_pkg/collision_checker.hpp>

class CollisionCheckerFixture : public ::testing::Test {
 protected:
  void SetUp() override {
    rclcpp::init(0, nullptr);
    node_ = std::make_shared<my_pkg::CollisionChecker>();
  }
  void TearDown() override {
    node_.reset();
    rclcpp::shutdown();
  }
  std::shared_ptr<my_pkg::CollisionChecker> node_;
};

TEST_F(CollisionCheckerFixture, DetectsObstacleWithinRange) {
  ROBOTICKS_CONFIRMS(CollisionCheckerFixture_DetectsObstacleWithinRange,
                     "REQ-051", "REQ-052");
  node_->inject_point_cloud(make_obstacle_at(1.5));
  auto verdict = roboticks_cpp::assert_topic_published<std_msgs::msg::Bool>(
    node_, "/obstacle_detected", std::chrono::seconds(2));
  EXPECT_TRUE(verdict.data);
}

Next

C++ reference

Full ConfirmsRegistry API, macro semantics, threading guarantees.

Wire contract

The JUnit-with-confirms schema both stitchers target.

Pytest tests

The Python-side equivalent, for mixed-language packages.

Coverage

Wire gcov/lcov so coverage shows up next to the matrix.