From b176c7d0fc79ed5f40e24a8b03b02fae78becf6e Mon Sep 17 00:00:00 2001 From: Jakub Delicat Date: Fri, 4 Oct 2024 11:24:07 +0000 Subject: [PATCH] Added node unit tests Signed-off-by: Jakub Delicat --- .github/workflows/run-unit-tests.yaml | 2 +- wibotic_connector_can/CMakeLists.txt | 12 ++ .../wibotic_can_driver.hpp | 39 +++-- .../wibotic_can_driver_node.hpp | 18 ++- wibotic_connector_can/package.xml | 1 + wibotic_connector_can/src/main.cpp | 4 +- .../src/wibotic_can_driver.cpp | 46 +++--- .../src/wibotic_can_driver_node.cpp | 25 ++- .../unit/test_wibotic_can_driver_node.cpp | 143 ++++++++++++++++++ 9 files changed, 234 insertions(+), 56 deletions(-) create mode 100644 wibotic_connector_can/test/unit/test_wibotic_can_driver_node.cpp diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index ca65b19..7f23a7f 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -1,5 +1,5 @@ --- -name: Run panther unit tests +name: Run unit tests on: workflow_dispatch: diff --git a/wibotic_connector_can/CMakeLists.txt b/wibotic_connector_can/CMakeLists.txt index 383613a..41cab95 100644 --- a/wibotic_connector_can/CMakeLists.txt +++ b/wibotic_connector_can/CMakeLists.txt @@ -56,9 +56,21 @@ install(TARGETS wibotic_connector_can wibotic_can_driver DESTINATION lib/${PROJECT_NAME}) if(BUILD_TESTING) + find_package(ament_cmake_gtest REQUIRED) find_package(ament_cmake_gmock REQUIRED) find_package(ros_testing REQUIRED) + # Unit tests + ament_add_gmock( + ${PROJECT_NAME}_test_wibotic_can_driver_node + test/unit/test_wibotic_can_driver_node.cpp src/wibotic_can_driver_node.cpp) + target_include_directories( + ${PROJECT_NAME}_test_wibotic_can_driver_node + PUBLIC $ + $) + target_link_libraries(${PROJECT_NAME}_test_wibotic_can_driver_node + wibotic_can_driver) + # Integration tests option(TEST_INTEGRATION "Run integration tests" ON) if(TEST_INTEGRATION) diff --git a/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver.hpp b/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver.hpp index da3c8cb..0107635 100644 --- a/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver.hpp +++ b/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver.hpp @@ -18,8 +18,8 @@ #include #include -#include #include +#include #include "wibotic_connector_can/uavcan_types/wibotic/WiBoticInfo.hpp" @@ -37,6 +37,16 @@ class WiboticCanDriverInterface */ virtual ~WiboticCanDriverInterface() = default; + /** * + * @param can_iface_name The name of the CAN interface. + * @param node_id The ID of the node. + * @param node_name The name of the node. + * + * @exception std::runtime_error Thrown if can interface cannot be found. + * */ + virtual void SetUavCanSettings( + const std::string & can_iface_name, std::size_t node_id, const std::string & node_name) = 0; + /** * @brief Creates the UAVCAN node. */ @@ -56,9 +66,9 @@ class WiboticCanDriverInterface /** * @brief Spins the Wibotic CAN driver. * - * @param miliseconds The time to spin in miliseconds. + * @param milliseconds The time to spin in milliseconds. */ - virtual void Spin(std::size_t miliseconds) = 0; + virtual void Spin(std::size_t milliseconds) = 0; /** * @brief Gets the WiboticInfo message. @@ -86,27 +96,27 @@ class WiboticCanDriverInterface */ class WiboticCanDriver : public WiboticCanDriverInterface { - typedef uavcan::MethodBinder - WiBoticInfoCallbackBinder; + typedef uavcan::MethodBinder< + WiboticCanDriver *, void (WiboticCanDriver::*)(const wibotic::WiBoticInfo &)> + WiBoticInfoCallbackBinder; public: /** - * @brief Constructor for the WiboticCanDriver class. - * * @param can_iface_name The name of the CAN interface. * @param node_id The ID of the node. * @param node_name The name of the node. * * @exception std::runtime_error Thrown if can interface cannot be found. - */ - WiboticCanDriver(const std::string& can_iface_name, std::size_t node_id, const std::string& node_name); + * */ + void SetUavCanSettings( + const std::string & can_iface_name, std::size_t node_id, + const std::string & node_name) override; /** * @brief Creates the UAVCAN node. */ void CreateUavCanNode() override; - void CreateWiboticInfoSubscriber() override; /** @@ -114,18 +124,19 @@ class WiboticCanDriver : public WiboticCanDriverInterface * * It starts the UAVCAN node, sets it to operational mode and starts Wibotic subscriber. * - * @exception std::runtime_error Thrown if the node or subscriber does not exist and they does not start properly. + * @exception std::runtime_error Thrown if the node or subscriber does not exist and they does not + * start properly. */ void Activate() override; /** * @brief Spins the Wibotic CAN driver. * - * @param miliseconds The time to spin in miliseconds. + * @param milliseconds The time to spin in milliseconds. * * @exception std::runtime_error Thrown if the node or subscriber does not spin properly. */ - void Spin(std::size_t miliseconds) override; + void Spin(std::size_t milliseconds) override; /** * @brief Gets the WiboticInfo message. @@ -144,7 +155,7 @@ class WiboticCanDriver : public WiboticCanDriverInterface * * @param msg The WiboticInfo message. */ - void WiboticInfoCallback(const wibotic::WiBoticInfo& msg); + void WiboticInfoCallback(const wibotic::WiBoticInfo & msg); std::string can_iface_name_; std::size_t node_id_; diff --git a/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver_node.hpp b/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver_node.hpp index a34ddc4..061ca43 100644 --- a/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver_node.hpp +++ b/wibotic_connector_can/include/wibotic_connector_can/wibotic_can_driver_node.hpp @@ -21,6 +21,8 @@ #include "wibotic_msgs/msg/wibotic_info.hpp" +// RCLCPP is compiling with C++17, so we need to define UAVCAN_CPP_VERSION to UAVCAN_CPP11 +// to avoid compilation errors and silent them. #define UAVCAN_CPP_VERSION UAVCAN_CPP11 #include "wibotic_connector_can/wibotic_can_driver.hpp" @@ -29,28 +31,30 @@ namespace wibotic_connector_can class WiboticCanDriverNode : public rclcpp::Node { public: - WiboticCanDriverNode(const std::string& node_name, - const rclcpp::NodeOptions & options = rclcpp::NodeOptions()); + WiboticCanDriverNode( + const std::string & node_name, WiboticCanDriverInterface::SharedPtr wibotic_can_driver, + const rclcpp::NodeOptions & options = rclcpp::NodeOptions()); protected: void DeclareParameters(); void GetParameters(); void CreateWiboticCanDriver(); + wibotic::WiBoticInfo GetWiboticInfo(); + + void WiboticInfoTimerCallback(); + + wibotic_msgs::msg::WiboticInfo ConvertWiboticInfoToMsg(const wibotic::WiBoticInfo & wibotic_info); std::string can_iface_name_; std::size_t uavcan_node_id_; std::string uavcan_node_name_; float update_time_s_; - std::unique_ptr wibotic_can_driver_; + WiboticCanDriverInterface::SharedPtr wibotic_can_driver_; rclcpp::TimerBase::SharedPtr wibotic_info_timer_; rclcpp::Publisher::SharedPtr wibotic_info_pub_; - - void WiboticInfoTimerCallback(); - - wibotic_msgs::msg::WiboticInfo ConvertWiboticInfoToMsg(const wibotic::WiBoticInfo& wibotic_info); }; } // namespace wibotic_connector_can diff --git a/wibotic_connector_can/package.xml b/wibotic_connector_can/package.xml index b0f88cc..6c6cf57 100644 --- a/wibotic_connector_can/package.xml +++ b/wibotic_connector_can/package.xml @@ -19,6 +19,7 @@ rclcpp wibotic_msgs + ament_cmake_gmock ament_cmake_gtest google-mock ros_testing diff --git a/wibotic_connector_can/src/main.cpp b/wibotic_connector_can/src/main.cpp index 015983a..a09ea0b 100644 --- a/wibotic_connector_can/src/main.cpp +++ b/wibotic_connector_can/src/main.cpp @@ -24,7 +24,9 @@ int main(int argc, char ** argv) { rclcpp::init(argc, argv); - auto wibotic_can_driver_node = std::make_shared("wibotic_can_driver"); + auto wibotic_can_driver = std::make_shared(); + auto wibotic_can_driver_node = std::make_shared( + "wibotic_can_driver", wibotic_can_driver); try { rclcpp::spin(wibotic_can_driver_node); diff --git a/wibotic_connector_can/src/wibotic_can_driver.cpp b/wibotic_connector_can/src/wibotic_can_driver.cpp index 3c7e699..c1cffb4 100644 --- a/wibotic_connector_can/src/wibotic_can_driver.cpp +++ b/wibotic_connector_can/src/wibotic_can_driver.cpp @@ -16,40 +16,41 @@ namespace wibotic_connector_can { -WiboticCanDriver::WiboticCanDriver(const std::string& can_iface_name, std::size_t node_id, const std::string& node_name) - : can_iface_name_(can_iface_name), node_id_(node_id), node_name_(node_name) +void WiboticCanDriver::SetUavCanSettings( + const std::string & can_iface_name, std::size_t node_id, const std::string & node_name) { + can_iface_name_ = can_iface_name; + node_id_ = node_id; + node_name_ = node_name; } void WiboticCanDriver::CreateUavCanNode() { - uavcan_node_ = uavcan_linux::makeNode({ can_iface_name_ }); + uavcan_node_ = uavcan_linux::makeNode({can_iface_name_}); uavcan_node_->setNodeID(node_id_); uavcan_node_->setName(node_name_.c_str()); } void WiboticCanDriver::CreateWiboticInfoSubscriber() { - wibotic_info_uavcan_sub_ = std::make_shared>(*uavcan_node_); + wibotic_info_uavcan_sub_ = + std::make_shared>(*uavcan_node_); } void WiboticCanDriver::Activate() { - if (!uavcan_node_) - { + if (!uavcan_node_) { throw std::runtime_error("Trying to activate nonexisting node."); } - const int sub_res = - wibotic_info_uavcan_sub_->start(WiBoticInfoCallbackBinder(this, &WiboticCanDriver::WiboticInfoCallback)); - if (sub_res < 0) - { + const int sub_res = wibotic_info_uavcan_sub_->start( + WiBoticInfoCallbackBinder(this, &WiboticCanDriver::WiboticInfoCallback)); + if (sub_res < 0) { throw std::runtime_error("Failed to start the subscriber; error: " + std::to_string(sub_res)); } const int node_start_res = uavcan_node_->start(); - if (node_start_res < 0) - { + if (node_start_res < 0) { throw std::runtime_error("Failed to start the node; error: " + std::to_string(node_start_res)); } @@ -58,34 +59,29 @@ void WiboticCanDriver::Activate() activated_ = true; } -void WiboticCanDriver::Spin(std::size_t miliseconds) +void WiboticCanDriver::Spin(std::size_t milliseconds) { - if (!uavcan_node_) - { + if (!uavcan_node_) { throw std::runtime_error("Trying to spin nonexisting node."); } - if (!wibotic_info_uavcan_sub_) - { + if (!wibotic_info_uavcan_sub_) { throw std::runtime_error("Trying to spin nonexisting subscriber."); } - if (!activated_) - { + if (!activated_) { throw std::runtime_error("Trying to spin non-activated driver."); } - const int res = uavcan_node_->spin(uavcan::MonotonicDuration::fromMSec(miliseconds)); - if (res < 0) - { + const int res = uavcan_node_->spin(uavcan::MonotonicDuration::fromMSec(milliseconds)); + if (res < 0) { throw std::runtime_error("Failed to spin UAVCAN node, res: " + std::to_string(res)); } } wibotic::WiBoticInfo WiboticCanDriver::GetWiboticInfo() { - if (wibotic_info_queue_.empty()) - { + if (wibotic_info_queue_.empty()) { throw std::runtime_error("WiBoticInfo queue is empty."); } @@ -94,7 +90,7 @@ wibotic::WiBoticInfo WiboticCanDriver::GetWiboticInfo() return wibotic_info; } -void WiboticCanDriver::WiboticInfoCallback(const wibotic::WiBoticInfo& msg) +void WiboticCanDriver::WiboticInfoCallback(const wibotic::WiBoticInfo & msg) { wibotic_info_queue_.push(msg); } diff --git a/wibotic_connector_can/src/wibotic_can_driver_node.cpp b/wibotic_connector_can/src/wibotic_can_driver_node.cpp index cc67c9b..37df40c 100644 --- a/wibotic_connector_can/src/wibotic_can_driver_node.cpp +++ b/wibotic_connector_can/src/wibotic_can_driver_node.cpp @@ -17,10 +17,16 @@ namespace wibotic_connector_can { WiboticCanDriverNode::WiboticCanDriverNode( - const std::string & node_name, const rclcpp::NodeOptions & options) -: rclcpp::Node(node_name, options) + const std::string & node_name, WiboticCanDriverInterface::SharedPtr wibotic_can_driver, + const rclcpp::NodeOptions & options) +: rclcpp::Node(node_name, options), wibotic_can_driver_(wibotic_can_driver) + { RCLCPP_INFO(this->get_logger(), "Initializing node."); + if (!wibotic_can_driver_) { + throw std::runtime_error("Wibotic CAN driver is not initialized."); + } + DeclareParameters(); GetParameters(); @@ -53,13 +59,19 @@ void WiboticCanDriverNode::GetParameters() void WiboticCanDriverNode::CreateWiboticCanDriver() { - wibotic_can_driver_ = std::make_unique( - can_iface_name_, uavcan_node_id_, uavcan_node_name_); + wibotic_can_driver_->SetUavCanSettings(can_iface_name_, uavcan_node_id_, uavcan_node_name_); wibotic_can_driver_->CreateUavCanNode(); wibotic_can_driver_->CreateWiboticInfoSubscriber(); wibotic_can_driver_->Activate(); } +wibotic::WiBoticInfo WiboticCanDriverNode::GetWiboticInfo() +{ + const auto update_time_ms = static_cast(update_time_s_ * 1000); + wibotic_can_driver_->Spin(update_time_ms); + return wibotic_can_driver_->GetWiboticInfo(); +} + void WiboticCanDriverNode::WiboticInfoTimerCallback() { if (!wibotic_can_driver_) { @@ -67,10 +79,7 @@ void WiboticCanDriverNode::WiboticInfoTimerCallback() } try { - const auto update_time_ms = static_cast(update_time_s_ * 1000); - wibotic_can_driver_->Spin(update_time_ms); - auto wibotic_info = wibotic_can_driver_->GetWiboticInfo(); - + auto wibotic_info = GetWiboticInfo(); wibotic_info_pub_->publish(ConvertWiboticInfoToMsg(wibotic_info)); } catch (const std::runtime_error & e) { RCLCPP_WARN(this->get_logger(), e.what()); diff --git a/wibotic_connector_can/test/unit/test_wibotic_can_driver_node.cpp b/wibotic_connector_can/test/unit/test_wibotic_can_driver_node.cpp new file mode 100644 index 0000000..0fb1cfe --- /dev/null +++ b/wibotic_connector_can/test/unit/test_wibotic_can_driver_node.cpp @@ -0,0 +1,143 @@ +// Copyright 2024 Husarion sp. z o.o. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +// RCLCPP is compiling with C++17, so we need to define UAVCAN_CPP_VERSION to UAVCAN_CPP11 +// to avoid compilation errors and silent them. +#define UAVCAN_CPP_VERSION UAVCAN_CPP11 +#include "wibotic_connector_can/wibotic_can_driver.hpp" +#include "wibotic_connector_can/wibotic_can_driver_node.hpp" + +namespace wibotic +{ + +// This is dummy stream overload because the real one is in the uavcan library what is built with +// C++11/ +std::ostream & operator<<(std::ostream & os, const WiBoticInfo &) { return os; } + +} // namespace wibotic + +class MockWiboticCanDriver : public wibotic_connector_can::WiboticCanDriverInterface +{ +public: + MOCK_METHOD( + void, SetUavCanSettings, (const std::string &, std::size_t, const std::string &), (override)); + MOCK_METHOD(void, CreateUavCanNode, (), (override)); + MOCK_METHOD(void, CreateWiboticInfoSubscriber, (), (override)); + MOCK_METHOD(void, Activate, (), (override)); + MOCK_METHOD(void, Spin, (std::size_t), (override)); + MOCK_METHOD(wibotic::WiBoticInfo, GetWiboticInfo, (), (override)); + + // Nice mock suppresses warnings about uninteresting calls + // using NiceMock = testing::NiceMock; +}; + +class WiboticCanDriverNodeWrapper : public wibotic_connector_can::WiboticCanDriverNode +{ +public: + WiboticCanDriverNodeWrapper( + const std::string & node_name, + wibotic_connector_can::WiboticCanDriverInterface::SharedPtr wibotic_can_driver, + const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) + : wibotic_connector_can::WiboticCanDriverNode(node_name, wibotic_can_driver, options) + { + } + + wibotic::WiBoticInfo GetWiboticInfo() + { + return wibotic_connector_can::WiboticCanDriverNode::GetWiboticInfo(); + } + + wibotic_msgs::msg::WiboticInfo ConvertWiboticInfoToMsg(const wibotic::WiBoticInfo & wibotic_info) + { + return wibotic_connector_can::WiboticCanDriverNode::ConvertWiboticInfoToMsg(wibotic_info); + } +}; + +class TestWiboticCanDriverNode : public ::testing::Test +{ +public: + TestWiboticCanDriverNode(); + +protected: + std::shared_ptr wibotic_can_driver_; + std::unique_ptr wibotic_can_driver_node_; +}; + +TestWiboticCanDriverNode::TestWiboticCanDriverNode() +{ + wibotic_can_driver_ = std::make_shared(); + wibotic_can_driver_node_ = std::make_unique( + "wibotic_can_driver", wibotic_can_driver_); +} + +TEST_F(TestWiboticCanDriverNode, GetWiboticInfoEmptyQueue) +{ + ON_CALL(*wibotic_can_driver_, GetWiboticInfo()) + .WillByDefault(testing::Throw(std::runtime_error("Queue is empty!"))); + + EXPECT_THROW(wibotic_can_driver_node_->GetWiboticInfo(), std::runtime_error); +} + +TEST_F(TestWiboticCanDriverNode, GetWiboticInfo) +{ + wibotic::WiBoticInfo wibotic_info; + wibotic_info.VMonBatt = 1.0; + ON_CALL(*wibotic_can_driver_, GetWiboticInfo()).WillByDefault([wibotic_info]() { + return wibotic_info; + }); + + EXPECT_EQ(wibotic_can_driver_node_->GetWiboticInfo(), wibotic_info); +} + +TEST_F(TestWiboticCanDriverNode, ConvertWiboticInfoToMsg) +{ + wibotic::WiBoticInfo wibotic_info; + wibotic_info.VMonBatt = 1.0; + wibotic_info.IBattery = 2.0; + wibotic_info.VRect = 3.0; + wibotic_info.VMonCharger = 4.0; + wibotic_info.TBoard = 5.0; + wibotic_info.TargetIBatt = 6.0; + wibotic_info.ICharger = 7.0; + wibotic_info.ISingleCharger2 = 8.0; + wibotic_info.ISingleCharger3 = 9.0; + + auto wibotic_info_msg = wibotic_can_driver_node_->ConvertWiboticInfoToMsg(wibotic_info); + + EXPECT_FLOAT_EQ(wibotic_info_msg.v_mon_batt, 1.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.i_battery, 2.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.v_rect, 3.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.v_mon_charger, 4.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.t_board, 5.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.target_i_batt, 6.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.i_charger, 7.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.i_single_charger2, 8.0); + EXPECT_FLOAT_EQ(wibotic_info_msg.i_single_charger3, 9.0); +} + +int main(int argc, char ** argv) +{ + testing::InitGoogleTest(&argc, argv); + rclcpp::init(0, nullptr); + + auto result = RUN_ALL_TESTS(); + + rclcpp::shutdown(); + return result; +}