diff --git a/.github/workflows/install_and_build.yaml b/.github/workflows/install_and_build.yaml index 6697601..db29407 100644 --- a/.github/workflows/install_and_build.yaml +++ b/.github/workflows/install_and_build.yaml @@ -1,37 +1,76 @@ name: Run setup and colcon build for Dev on: push + jobs: + setup_and_build_check: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - name: enviroment setupg - run: | - export ROVERFLAKE_ROOT="${GITHUB_WORKSPACE}" - yes | ./setup_scripts/setup_everything_common.sh - - name: build - run: | - cd $ROVERFLAKE_ROOT - echo $ROS_DISTRO - source /opt/ros/humble/setup.bash - echo $ROS_DISTRO - colcon build --symlink-install --packages-skip sweeppy - - name: Notify Discord (success) - if: ${{ success() }} - uses: appleboy/discord-action@v1.0.0 - with: - webhook_id: ${{ secrets.DISCORD_BOT_WEBHOOK_ID }} # it also accepts full URL - webhook_token: ${{ secrets.DISCORD_BOT_WEBHOOK_TOKEN }} # it also accepts full URL - message: | - *I'm happy now and I'll let you merge ${{ github.actor }}. _good job!_* || ${{ github.repository }} on `${{ github.ref_name }}` - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - - name: Notify Discord (failure) - if: ${{ failure() }} - uses: appleboy/discord-action@v1.0.0 - with: - webhook_id: ${{ secrets.DISCORD_BOT_WEBHOOK_ID }} # it also accepts full URL - webhook_token: ${{ secrets.DISCORD_BOT_WEBHOOK_TOKEN }} # it also accepts full URL - message: | - _you have saddened me ${{ github.actor }}_ - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - + - uses: actions/checkout@v4 + + - name: enviroment setupg + run: | + export ROVERFLAKE_ROOT="${GITHUB_WORKSPACE}" + yes | ./setup_scripts/setup_everything_common.sh + + - name: build + run: | + cd $ROVERFLAKE_ROOT + echo $ROS_DISTRO + source /opt/ros/humble/setup.bash + echo $ROS_DISTRO + colcon build --symlink-install --packages-skip sweeppy + + - uses: actions/upload-artifact@v4 + with: + name: rover_ws + path: ${GITHUB_WORKSPACE}/install/ + + - name: Notify Discord (success) + if: ${{ success() }} + uses: appleboy/discord-action@v1.0.0 + with: + webhook_id: ${{ secrets.DISCORD_BOT_WEBHOOK_ID }} + webhook_token: ${{ secrets.DISCORD_BOT_WEBHOOK_TOKEN }} + message: | + *I'm happy now and I'll let you merge ${{ github.actor }}. _good job!_* || ${{ github.repository }} on `${{ github.ref_name }}` + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Notify Discord (failure) + if: ${{ failure() }} + uses: appleboy/discord-action@v1.0.0 + with: + webhook_id: ${{ secrets.DISCORD_BOT_WEBHOOK_ID }} + webhook_token: ${{ secrets.DISCORD_BOT_WEBHOOK_TOKEN }} + message: | + _you have saddened me ${{ github.actor }}_ + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + integration_tests: + runs-on: ubuntu-22.04 + needs: setup_and_build_check + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: rover_ws + path: ${GITHUB_WORKSPACE}/install/ + + - name: run integration tests + run: | + cd ${GITHUB_WORKSPACE} + source install/setup.bash + colcon test --packages-select integration_tests + colcon test-result --verbose + + - name: Notify Discord (failure) + if: ${{ failure() }} + uses: appleboy/discord-action@v1.0.0 + with: + webhook_id: ${{ secrets.DISCORD_BOT_WEBHOOK_ID }} + webhook_token: ${{ secrets.DISCORD_BOT_WEBHOOK_TOKEN }} + message: | + _Unit tests have failed ${{ github.actor }}_ + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + @rowan Check for integration misshaps diff --git a/src/arm_control/CMakeLists.txt b/src/arm_control/CMakeLists.txt index 3522498..9218698 100644 --- a/src/arm_control/CMakeLists.txt +++ b/src/arm_control/CMakeLists.txt @@ -29,6 +29,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS trajectory_msgs rover_msgs moteus_msgs + arm_hardware_interface ) find_package(ament_cmake REQUIRED) diff --git a/src/arm_control/include/armControlParams.h b/src/arm_control/include/armControlParams.h index 7143cf5..6e84a28 100644 --- a/src/arm_control/include/armControlParams.h +++ b/src/arm_control/include/armControlParams.h @@ -16,6 +16,7 @@ #define EE_SPEED_SCALE 1 #define PI 3.14 +//TODO get away from this style of conditional compiling. // Uncomment the one you want, comment the one you dont #define SELECT_MOTEUS_ARM // #define SELECT_OLD_ARM diff --git a/src/arm_control/include/cbs_interface.h b/src/arm_control/include/cbs_interface.h index f52b5da..4e7a320 100644 --- a/src/arm_control/include/cbs_interface.h +++ b/src/arm_control/include/cbs_interface.h @@ -4,8 +4,7 @@ #include "armControlParams.h" #include "rover_msgs/msg/generic_panel.hpp" #include "geometry_msgs/msg/twist_stamped.hpp" - - +#include class CBSArmInterface : public rclcpp::Node { @@ -14,19 +13,17 @@ class CBSArmInterface : public rclcpp::Node auto qos = rclcpp::QoS(rclcpp::KeepLast(1)).transient_local(); arm_cmd_publisher = this->create_publisher("/arm/command", qos); - arm_panel_subscriber = this->create_subscription( + arm_panel_subscriber = this->create_subscription( "/cbs/arm_panel", 10, std::bind(&CBSArmInterface::arm_panel_callback, this, std::placeholders::_1)); left_panel_subscriber = this->create_subscription( "/cbs/left_panel_a", 10, std::bind(&CBSArmInterface::left_panel_callback, this, std::placeholders::_1)); - - arm_ik_pub = this->create_publisher( - "/arm_moveit_control/delta_twist_cmds", qos); + + arm_ik_pub = this->create_publisher( + "/arm_moveit_control/delta_twist_cmds", qos); // arm_panel_timer = this->create_wall_timer( //Timer setup if we need it // std::chrono::milliseconds(10), // Timer interval // std::bind(&CBSManagerNode::armPanelPoll, this) // Callback function // ); - - } ~CBSArmInterface(){ diff --git a/src/arm_control/package.xml b/src/arm_control/package.xml index 24b921d..a4e8ae9 100644 --- a/src/arm_control/package.xml +++ b/src/arm_control/package.xml @@ -25,6 +25,7 @@ rover_msgs trajectory_msgs moveit_servo + arm_hardware_interface diff --git a/src/arm_control/src/cbs_interface.cpp b/src/arm_control/src/cbs_interface.cpp index 9da7f3c..fbd6f6e 100644 --- a/src/arm_control/src/cbs_interface.cpp +++ b/src/arm_control/src/cbs_interface.cpp @@ -45,7 +45,7 @@ void CBSArmInterface::arm_panel_callback(const rover_msgs::msg::ArmPanel::Shared rover_msgs::msg::ArmCommand cmd_msg; - cmd_msg.cmd_type = 'V'; //!SHOULD BE FROM ArmSerialProtocol.h + cmd_msg.cmd_type = ABS_VEL_CMD; //!SHOULD BE FROM ArmSerialProtocol.h cmd_msg.velocities.resize(NUM_JOINTS); cmd_msg.velocities[0] = (static_cast(msg->left.x) - 50)/100 * max_joysticks_output_speed_deg[0]*2; cmd_msg.velocities[1] = (static_cast(msg->left.y) - 50)/100 * max_joysticks_output_speed_deg[1]*2 *-1; diff --git a/src/integration_test/CMakeLists.txt b/src/integration_test/CMakeLists.txt new file mode 100644 index 0000000..9f8b8d5 --- /dev/null +++ b/src/integration_test/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.10) +project(integration_test) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# Catch2 did not work very well. We tried, its just difficult to integrate in a scalable way. +# leaving this stale code just commented to keep record that we tried it. +# add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../external_pkgs/Catch2 ${CMAKE_CURRENT_BINARY_DIR}/external_pkgs/Catch2) # Include Catch2 for unit tests + +include_directories(include/) + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rover_utils REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(example_interfaces REQUIRED) + +add_executable(integration_tester src/assert_node.cpp) +add_executable(example_multiply_by_two_node src/example_multiply_by_two_node.cpp) +ament_target_dependencies(integration_tester rclcpp sensor_msgs rover_utils example_interfaces) +ament_target_dependencies(example_multiply_by_two_node rclcpp sensor_msgs rover_utils example_interfaces) + +if(BUILD_TESTING) + # Integration tests + find_package(ament_cmake_ros REQUIRED) + find_package(launch_testing_ament_cmake REQUIRED) + function(add_ros_isolated_launch_test path) + set(RUNNER "${ament_cmake_ros_DIR}/run_test_isolated.py") + add_launch_test("${path}" RUNNER "${RUNNER}" ${ARGN}) + endfunction() + add_ros_isolated_launch_test(test/test_launch_test.py) + add_ros_isolated_launch_test(test/test_arm_pipeline.py) + # Add more tests here +endif() + +# target_link_libraries(integration_tester +# Catch2::Catch2) + +install( + TARGETS + integration_tester + example_multiply_by_two_node + DESTINATION lib/${PROJECT_NAME} +) + +ament_package() diff --git a/src/integration_test/brainstorm.txt b/src/integration_test/brainstorm.txt new file mode 100644 index 0000000..efbd616 --- /dev/null +++ b/src/integration_test/brainstorm.txt @@ -0,0 +1,4 @@ +How to integration tests? + +scopeout: +1. can catch2 be used? or should not bother? \ No newline at end of file diff --git a/src/integration_test/include/integration_test/integration_helper_macros.h b/src/integration_test/include/integration_test/integration_helper_macros.h new file mode 100644 index 0000000..ad67e1e --- /dev/null +++ b/src/integration_test/include/integration_test/integration_helper_macros.h @@ -0,0 +1,4 @@ +#pragma once + + +// #define APPROX() \ No newline at end of file diff --git a/src/integration_test/package.xml b/src/integration_test/package.xml new file mode 100644 index 0000000..c36172c --- /dev/null +++ b/src/integration_test/package.xml @@ -0,0 +1,29 @@ + + + + integration_test + 0.0.0 + TODO: Package description + rowan + TODO: License declaration + + ament_cmake + + rclcpp + rover_utils + sensor_msgs + + ament_cmake_ros + launch + launch_ros + launch_testing + launch_testing_ament_cmake + rclpy + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/integration_test/src/assert_node.cpp b/src/integration_test/src/assert_node.cpp new file mode 100644 index 0000000..1e394ff --- /dev/null +++ b/src/integration_test/src/assert_node.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +// #include + +// This node is a work in progress, playing around with different ways to setup integration tests. +// We are aiming for a standard, easy way to add tests, and an easy way to get test results. + +#define TEST_INT_VALUE 50 + +// Class definition +class IntegrationTestNode : public rclcpp::Node { +public: + IntegrationTestNode(); // Constructor is defined in .cpp. + +private: + // SUBS AND PUBS + rclcpp::Publisher::SharedPtr joy_publisher; + + rclcpp::Publisher::SharedPtr checker_pub; + rclcpp::Subscription::SharedPtr checker_sub; + + // Callbacks + void test_callback(example_interfaces::msg::Int32 msg); + +}; + +// Constructor +IntegrationTestNode::IntegrationTestNode() : Node("integration_test_node") +{ // We create a class using rclcpp::Node as a base class. You can still use another base class if you need, albeit sometimes with difficulties in passing args.. + + // Quality of service example + auto qos = rclcpp::QoS(rclcpp::KeepLast(1)).transient_local(); + + joy_publisher = this->create_publisher( + "/joy", qos); + + checker_pub = this->create_publisher( + "/integration/test_int/output", qos); + + checker_sub = this->create_subscription( + "/integration/test_int/input", qos, std::bind(&IntegrationTestNode::test_callback, this, std::placeholders::_1)); + + // example_interfaces::msg::Int32 test_msg; + // test_msg.data = TEST_INT_VALUE; + // checker_pub->publish(test_msg); +} + +// Main function (entry point of node) +int main(int argc, char *argv[]) +{ + // Init ros2 with args. + rclcpp::init(argc, argv); + + // Instatiate your node + auto node = std::make_shared(); + // Spin it! (this is what runs pubs and subs. This is a blocking function) + rclcpp::spin(node); + // There is also rclcpp::spinSome(node) if you need more control over when the node spins / need to not block while spinning. + + // Code only reaches here if we crash or shutdown the node + rclcpp::shutdown(); + return 0; +} + +void IntegrationTestNode::test_callback(example_interfaces::msg::Int32 msg) +{ + example_interfaces::msg::Int32 output; + output.data = msg.data *2; + checker_pub->publish(output); + // TEST_CASE("integration test check") + // { + // REQUIRE(true); + // REQUIRE(msg.data == TEST_INT_VALUE); + // } +} \ No newline at end of file diff --git a/src/integration_test/src/example_multiply_by_two_node.cpp b/src/integration_test/src/example_multiply_by_two_node.cpp new file mode 100644 index 0000000..08227b6 --- /dev/null +++ b/src/integration_test/src/example_multiply_by_two_node.cpp @@ -0,0 +1,52 @@ +#include +#include + +// This node is just an example node to show integration tests +// Its just one publisher and one subscriber, give it an integer and it just doubles it + +// Class definition +class MultiplyBy2Node : public rclcpp::Node { +public: + MultiplyBy2Node(); // Constructor is defined in .cpp. + +private: + // SUBS AND PUBS + rclcpp::Publisher::SharedPtr output_pub; + rclcpp::Subscription::SharedPtr input_sub; + + // Callbacks + void test_callback(example_interfaces::msg::Int32 msg); + +}; + +// Constructor +MultiplyBy2Node::MultiplyBy2Node() : Node("multiply_by_two_integration_test_example_node") +{ // We create a class using rclcpp::Node as a base class. You can still use another base class if you need, albeit sometimes with difficulties in passing args.. + + // Quality of service example + auto qos = rclcpp::QoS(rclcpp::KeepLast(1)).transient_local(); + + output_pub = this->create_publisher( + "/integration/test_int/output", qos); + + input_sub = this->create_subscription( + "/integration/test_int/input", qos, std::bind(&MultiplyBy2Node::test_callback, this, std::placeholders::_1)); + + RCLCPP_INFO(this->get_logger(), "Example Log message: Node has started"); +} + +int main(int argc, char *argv[]) +{ + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} + +void MultiplyBy2Node::test_callback(example_interfaces::msg::Int32 msg) +{ + example_interfaces::msg::Int32 output; + output.data = msg.data *2; + output_pub->publish(output); +} \ No newline at end of file diff --git a/src/integration_test/src/integration_test_fixtures.cpp b/src/integration_test/src/integration_test_fixtures.cpp new file mode 100644 index 0000000..c3c2098 --- /dev/null +++ b/src/integration_test/src/integration_test_fixtures.cpp @@ -0,0 +1,54 @@ +// This class is meant to be a helper class for integration tests. +#include +#include +#include + +// Work in progress... + +// A single test +class TestRunner { +public: + TestRunner(std::string name) {}; + + void assert(bool assertion) + { + + } + + void assert(float val1, float val2, float thresh = 0.0000001) + { + + } + + +private: + std::string name; +}; + +class IntegrationTestHelper { +public: + IntegrationTestHelper() : next_test_index(0), current_test_index(0) {}; + + TestRunner& add_test(std::string name); + +private: + std::unordered_map tests; + uint32_t next_test_index; + uint32_t current_test_index; +}; + +TestRunner& IntegrationTestHelper::add_test(std::string name) +{ + current_test_index = next_test_index; + tests.emplace(current_test_index, TestRunner(name)); + next_test_index++; + return tests[current_test_index]; +} + + + + + +// Example usage: + +// auto new_test = test.add_test("My new test"); \ No newline at end of file diff --git a/src/integration_test/test/test_arm_pipeline.py b/src/integration_test/test/test_arm_pipeline.py new file mode 100644 index 0000000..087be35 --- /dev/null +++ b/src/integration_test/test/test_arm_pipeline.py @@ -0,0 +1,144 @@ +import os +import sys +import time +import unittest + +import launch +import launch_ros +import launch_testing.actions +import rclpy +from example_interfaces.msg import Int32 +from rclpy.qos import QoSProfile, QoSDurabilityPolicy, QoSReliabilityPolicy +from rover_msgs.msg import GenericPanel +from rover_msgs.msg import ArmPanel +from rover_msgs.msg import ArmCommand +from geometry_msgs.msg import TwistStamped + + + +def generate_test_description(): + return ( + launch.LaunchDescription( + [ + # Nodes under test + launch_ros.actions.Node( + package='arm_control', + namespace='', + executable='cbs_arm_interface', + name='cbs_arm_interface', + ), + # Launch tests 1.5 s later + launch.actions.TimerAction( + period=0.5, actions=[launch_testing.actions.ReadyToTest()]), + ] + ), {}, + ) + + + +# Active tests + +# Control Base Station Input -> arm command output test +# Injects inputs that would come from the CBS +# Asserts on arm command topics +CBS_INTERFACE_TEST_TIME_SECONDS = 1 +CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS = 0.05 # Node under test has a qos depth of 1 (can only process one msg per sub at a time), so rapid firing publishers will cause undef behaviour. +class CBSInterfaceTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + rclpy.init() + + @classmethod + def tearDownClass(cls): + rclpy.shutdown() + + def setUp(self): + self.node = rclpy.create_node('cbs_interface_test') + + def tearDown(self): + self.node.destroy_node() + + def test_publishes_pose(self, proc_output): + """Check whether pose messages published""" + qos = QoSProfile(depth=10) # using a higher qos just to facilitate slower spin times + qos.durability = QoSDurabilityPolicy.TRANSIENT_LOCAL + qos.reliability = QoSReliabilityPolicy.RELIABLE + arm_cmd_msgs = [] # raw arm msgs + ik_twist_msgs = [] # ik messages + + ik_sub = self.node.create_subscription( + TwistStamped, '/arm_moveit_control/delta_twist_cmds', + lambda msg: ik_twist_msgs.append(msg), qos) + arm_sub = self.node.create_subscription( + ArmCommand, '/arm/command', + lambda msg: arm_cmd_msgs.append(msg), qos) + + arm_panel_pub = self.node.create_publisher(ArmPanel, '/cbs/arm_panel', qos) + left_panel_a_pub = self.node.create_publisher(GenericPanel, '/cbs/left_panel_a', qos) + try: + + # Inject an arm panel message + injection = ArmPanel() + injection.left.x = 50 + injection.left.y = 50 + injection.left.z = 50 + injection.right.x = 50 + injection.right.y = 50 + injection.right.z = 50 + injection.right.button = 0 + injection.left.button = 0 + arm_panel_pub.publish(injection) # injection 0, should be all 0s + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) # needed to let the node under test respond + injection.right.button = 1 + arm_panel_pub.publish(injection) # injection 1, end effector should move + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) + injection.right.button = 0 + injection.left.button = 1 + arm_panel_pub.publish(injection) # injection 2, end effector should move in other direction + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) + injection.left.button = 0 + injection.left.x = 80 + arm_panel_pub.publish(injection) # injection 3, axis 1 should move positive + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) + injection.left.x = 23 + arm_panel_pub.publish(injection) # injection 4, axis 1 should move negative + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) + + end_time = time.time() + CBS_INTERFACE_TEST_TIME_SECONDS + while time.time() < end_time: + # spin to get subscriber callback executed + rclpy.spin_once(self.node, timeout_sec=0.1) + # There should have been 1 message received + assert len(arm_cmd_msgs) > 0 + assert len(ik_twist_msgs) == 0 # control pipeline should not be sending any twist messages yet + for value in arm_cmd_msgs[0].velocities: + assert(value == 0) + assert(arm_cmd_msgs[0].end_effector == 0.0) + assert(arm_cmd_msgs[0].cmd_type == ord('V')) + assert(arm_cmd_msgs[1].end_effector > 0.0) + assert(arm_cmd_msgs[2].end_effector < 0.0) + assert(arm_cmd_msgs[3].velocities[0] > 0.0) + assert(arm_cmd_msgs[4].velocities[0] < 0.0) + + num_direct_commands = len(arm_cmd_msgs) + left_panel_injection = GenericPanel() + left_panel_injection.switches.append(False) # Inject IK switch setting + left_panel_injection.switches[0] = True # Inject IK switch setting + left_panel_a_pub.publish(left_panel_injection) + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) + arm_panel_pub.publish(injection) + time.sleep(CBS_INTERFACE_TIME_NEEDED_TO_RESPOND_SECONDS) + rclpy.spin_once(self.node, timeout_sec=0.1) + + assert len(arm_cmd_msgs) == num_direct_commands # No new arm cmd messages should be published + assert len(ik_twist_msgs) == 1 + assert(ik_twist_msgs[0].twist.linear.x != 0) + + + + finally: + self.node.destroy_subscription(arm_sub) + self.node.destroy_subscription(ik_sub) + self.node.destroy_publisher(arm_panel_pub) + self.node.destroy_publisher(left_panel_a_pub) + diff --git a/src/integration_test/test/test_launch_test.py b/src/integration_test/test/test_launch_test.py new file mode 100644 index 0000000..f77bf0e --- /dev/null +++ b/src/integration_test/test/test_launch_test.py @@ -0,0 +1,77 @@ +import os +import sys +import time +import unittest + +import launch +import launch_ros +import launch_testing.actions +import rclpy +from example_interfaces.msg import Int32 +from rclpy.qos import QoSProfile, QoSDurabilityPolicy + + +def generate_test_description(): + return ( + launch.LaunchDescription( + [ + # Nodes under test + launch_ros.actions.Node( + package='integration_test', + namespace='', + executable='example_multiply_by_two_node', + name='multiply_by_two_node', + ), + # Launch tests 0.5 s later + launch.actions.TimerAction( + period=0.5, actions=[launch_testing.actions.ReadyToTest()]), + ] + ), {}, + ) + +# Active tests +class TestExample(unittest.TestCase): + @classmethod + def setUpClass(cls): + rclpy.init() + + @classmethod + def tearDownClass(cls): + rclpy.shutdown() + + def setUp(self): + self.node = rclpy.create_node('test_example') + + def tearDown(self): + self.node.destroy_node() + + def test_publishes_pose(self, proc_output): + """Check whether pose messages published""" + qos = QoSProfile(depth=10) + qos.durability = QoSDurabilityPolicy.TRANSIENT_LOCAL + + msgs_rx = [] + sub = self.node.create_subscription( + Int32, '/integration/test_int/output', + lambda msg: msgs_rx.append(msg), qos) + pub = self.node.create_publisher(Int32, '/integration/test_int/input', qos) + try: + # Listen to the pose topic for 10 s + injection = Int32() + injection.data = 5 + pub.publish(injection) + end_time = time.time() + 1 + while time.time() < end_time: + # spin to get subscriber callback executed + rclpy.spin_once(self.node, timeout_sec=1) + # There should have been 1 message received + assert len(msgs_rx) > 0 + assert msgs_rx[0].data == 10 + finally: + self.node.destroy_subscription(sub) + + def test_logs_spawning(self, proc_output): + """Check whether logging properly""" + proc_output.assertWaitFor( + "Example Log message: Node has started", + timeout=5, stream='stderr') diff --git a/src/rover_samples/rv_sample_package/CMakeLists.txt b/src/rover_samples/rv_sample_package/CMakeLists.txt index b6197ef..9ee6351 100644 --- a/src/rover_samples/rv_sample_package/CMakeLists.txt +++ b/src/rover_samples/rv_sample_package/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.15) project(rv_sample_package) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")