Skip to content

Commit cddcb89

Browse files
add tests for magnetometer_broadcaster/MagnetometerBroadcaster
1 parent 758536a commit cddcb89

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

magnetometer_broadcaster/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,33 @@ target_link_libraries(magnetometer_broadcaster PUBLIC
4343
pluginlib_export_plugin_description_file(
4444
controller_interface magnetometer_broadcaster.xml)
4545

46+
if(BUILD_TESTING)
47+
find_package(ament_cmake_gmock REQUIRED)
48+
find_package(controller_manager REQUIRED)
49+
find_package(hardware_interface REQUIRED)
50+
find_package(ros2_control_test_assets REQUIRED)
51+
52+
add_definitions(-DTEST_FILES_DIRECTORY="${CMAKE_CURRENT_SOURCE_DIR}/test")
53+
ament_add_gmock(test_load_magnetometer_broadcaster
54+
test/test_load_magnetometer_broadcaster.cpp
55+
${CMAKE_CURRENT_SOURCE_DIR}/test/magnetometer_broadcaster_params.yaml)
56+
target_include_directories(test_load_magnetometer_broadcaster PRIVATE include)
57+
target_link_libraries(test_load_magnetometer_broadcaster
58+
magnetometer_broadcaster
59+
controller_manager::controller_manager
60+
ros2_control_test_assets::ros2_control_test_assets
61+
)
62+
63+
ament_add_gmock(test_magnetometer_broadcaster
64+
test/test_magnetometer_broadcaster.cpp
65+
${CMAKE_CURRENT_SOURCE_DIR}/test/magnetometer_broadcaster_params.yaml)
66+
target_link_libraries(test_magnetometer_broadcaster
67+
magnetometer_broadcaster
68+
ros2_control_test_assets::ros2_control_test_assets
69+
magnetometer_broadcaster
70+
)
71+
endif()
72+
4673
install(
4774
TARGETS
4875
magnetometer_broadcaster
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
test_magnetometer_broadcaster:
2+
ros__parameters:
3+
sensor_name: magnetometer
4+
frame_id: magnetometer_frame
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2026 ros2_control development team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/*
16+
* Authors: Christian Rauch, Wiktor Bajor, Jakub Delicat
17+
*/
18+
19+
#include <gmock/gmock.h>
20+
#include <memory>
21+
22+
#include <controller_manager/controller_manager.hpp>
23+
#include <hardware_interface/resource_manager.hpp>
24+
#include <rclcpp/executor.hpp>
25+
#include <rclcpp/executors/single_threaded_executor.hpp>
26+
#include <rclcpp/utilities.hpp>
27+
#include <ros2_control_test_assets/descriptions.hpp>
28+
29+
TEST(TestLoadMagnetometerBroadcaster, load_controller)
30+
{
31+
rclcpp::init(0, nullptr);
32+
std::shared_ptr<rclcpp::Executor> executor =
33+
std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
34+
35+
controller_manager::ControllerManager cm(
36+
executor, ros2_control_test_assets::minimal_robot_urdf, true, "test_controller_manager");
37+
const std::string test_file_path =
38+
std::string(TEST_FILES_DIRECTORY) + "/magnetometer_broadcaster_params.yaml";
39+
40+
cm.set_parameter({"test_magnetometer_broadcaster.params_file", test_file_path});
41+
cm.set_parameter(
42+
{"test_magnetometer_broadcaster.type", "magnetometer_broadcaster/MagnetometerBroadcaster"});
43+
44+
ASSERT_NE(cm.load_controller("test_magnetometer_broadcaster"), nullptr);
45+
rclcpp::shutdown();
46+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright 2026 ros2_control development team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/*
16+
* Authors: Christian Rauch, Wiktor Bajor, Jakub Delicat
17+
*/
18+
19+
#include <gmock/gmock.h>
20+
#include <class_loader/class_loader.hpp>
21+
#include <controller_interface/controller_interface.hpp>
22+
#include <hardware_interface/loaned_state_interface.hpp>
23+
#include <hardware_interface/types/hardware_interface_return_values.hpp>
24+
#include <hardware_interface/types/hardware_interface_type_values.hpp>
25+
#include <magnetometer_broadcaster/magnetometer_broadcaster_parameters.hpp>
26+
#include <rclcpp/node.hpp>
27+
#include <rclcpp/wait_result_kind.hpp>
28+
#include <rclcpp/wait_set.hpp>
29+
#include <rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp>
30+
#include <ros2_control_test_assets/descriptions.hpp>
31+
#include <sensor_msgs/msg/magnetic_field.hpp>
32+
#include <utility>
33+
34+
using callback_return_type =
35+
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
36+
37+
namespace
38+
{
39+
rclcpp::WaitResultKind wait_for(std::shared_ptr<rclcpp::SubscriptionBase> subscription)
40+
{
41+
rclcpp::WaitSet wait_set;
42+
wait_set.add_subscription(subscription);
43+
return wait_set.wait().kind();
44+
}
45+
46+
rclcpp::NodeOptions create_node_options_with_overriden_parameters(
47+
std::vector<rclcpp::Parameter> parameters)
48+
{
49+
auto node_options = rclcpp::NodeOptions();
50+
node_options.parameter_overrides(parameters);
51+
return node_options;
52+
}
53+
} // namespace
54+
55+
class MagnetometerBroadcasterTest : public ::testing::Test
56+
{
57+
public:
58+
MagnetometerBroadcasterTest() { rclcpp::init(0, nullptr); }
59+
60+
~MagnetometerBroadcasterTest() { rclcpp::shutdown(); }
61+
62+
void SetUp()
63+
{
64+
broadcaster = loader->createInstance<controller_interface::ControllerInterface>(
65+
"magnetometer_broadcaster::MagnetometerBroadcaster");
66+
}
67+
68+
void TearDown() { broadcaster.reset(); }
69+
70+
void setup_broadcaster()
71+
{
72+
std::vector<hardware_interface::LoanedStateInterface> state_ifs;
73+
state_ifs.emplace_back(magnetic_field_x, nullptr);
74+
state_ifs.emplace_back(magnetic_field_y, nullptr);
75+
state_ifs.emplace_back(magnetic_field_z, nullptr);
76+
77+
broadcaster->assign_interfaces({}, std::move(state_ifs));
78+
}
79+
80+
sensor_msgs::msg::MagneticField subscribe_and_get_message()
81+
{
82+
rclcpp::Node test_subscription_node("test_subscription_node");
83+
auto subscription = test_subscription_node.create_subscription<sensor_msgs::msg::MagneticField>(
84+
"/test_magnetometer_broadcaster/magnetic_field", 10,
85+
[](const sensor_msgs::msg::MagneticField::SharedPtr) {});
86+
broadcaster->update(rclcpp::Time{}, rclcpp::Duration::from_seconds(0));
87+
wait_for(subscription);
88+
89+
rclcpp::MessageInfo msg_info;
90+
sensor_msgs::msg::MagneticField msg;
91+
subscription->take(msg, msg_info);
92+
return msg;
93+
}
94+
95+
controller_interface::ControllerInterfaceParams create_ctrl_params(
96+
const rclcpp::NodeOptions & node_options, const std::string & robot_description = "")
97+
{
98+
controller_interface::ControllerInterfaceParams params;
99+
params.controller_name = "test_magnetometer_broadcaster";
100+
params.robot_description = robot_description;
101+
params.update_rate = 0;
102+
params.node_namespace = "";
103+
params.node_options = node_options;
104+
return params;
105+
}
106+
107+
protected:
108+
const std::string sensor_name_ = "magnetometer";
109+
const rclcpp::Parameter sensor_name_param_ = rclcpp::Parameter("sensor_name", sensor_name_);
110+
const std::string frame_id_ = "magnetometer_frame";
111+
const rclcpp::Parameter frame_id_param_ = rclcpp::Parameter("frame_id", frame_id_);
112+
113+
std::array<double, 3> sensor_values_ = {{20e-6, 30e-6, 40e-6}};
114+
hardware_interface::StateInterface::SharedPtr magnetic_field_x =
115+
std::make_shared<hardware_interface::StateInterface>(
116+
sensor_name_, "magnetic_field.x", &sensor_values_[0]);
117+
hardware_interface::StateInterface::SharedPtr magnetic_field_y =
118+
std::make_shared<hardware_interface::StateInterface>(
119+
sensor_name_, "magnetic_field.y", &sensor_values_[1]);
120+
hardware_interface::StateInterface::SharedPtr magnetic_field_z =
121+
std::make_shared<hardware_interface::StateInterface>(
122+
sensor_name_, "magnetic_field.z", &sensor_values_[2]);
123+
124+
std::unique_ptr<class_loader::ClassLoader> loader =
125+
std::make_unique<class_loader::ClassLoader>(std::string{});
126+
127+
controller_interface::ControllerInterfaceSharedPtr broadcaster = nullptr;
128+
};
129+
130+
TEST_F(MagnetometerBroadcasterTest, whenNoParamsAreSetThenInitShouldFail)
131+
{
132+
const auto result = broadcaster->init(create_ctrl_params(
133+
broadcaster->define_custom_node_options(), ros2_control_test_assets::minimal_robot_urdf));
134+
ASSERT_EQ(result, controller_interface::return_type::ERROR);
135+
}
136+
137+
TEST_F(MagnetometerBroadcasterTest, whenOnlySensorNameIsSetThenInitShouldFail)
138+
{
139+
const auto node_options = create_node_options_with_overriden_parameters({sensor_name_param_});
140+
const auto result = broadcaster->init(
141+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
142+
ASSERT_EQ(result, controller_interface::return_type::ERROR);
143+
}
144+
145+
TEST_F(
146+
MagnetometerBroadcasterTest,
147+
whenAllRequiredArgumentsAreSetThenInitConfigureAndActivationShouldSucceed)
148+
{
149+
const auto node_options =
150+
create_node_options_with_overriden_parameters({sensor_name_param_, frame_id_param_});
151+
const auto result = broadcaster->init(
152+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
153+
ASSERT_EQ(result, controller_interface::return_type::OK);
154+
ASSERT_EQ(broadcaster->on_configure(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
155+
ASSERT_EQ(broadcaster->on_activate(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
156+
}
157+
158+
TEST_F(MagnetometerBroadcasterTest, whenBroadcasterIsActiveShouldPublishWithCovarianceSetToZero)
159+
{
160+
const auto node_options =
161+
create_node_options_with_overriden_parameters({sensor_name_param_, frame_id_param_});
162+
const auto result = broadcaster->init(
163+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
164+
ASSERT_EQ(result, controller_interface::return_type::OK);
165+
ASSERT_EQ(broadcaster->on_configure(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
166+
setup_broadcaster();
167+
ASSERT_EQ(broadcaster->on_activate(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
168+
169+
const auto msg = subscribe_and_get_message();
170+
EXPECT_EQ(msg.header.frame_id, frame_id_);
171+
EXPECT_EQ(msg.magnetic_field.x, sensor_values_[0]);
172+
EXPECT_EQ(msg.magnetic_field.y, sensor_values_[1]);
173+
EXPECT_EQ(msg.magnetic_field.z, sensor_values_[2]);
174+
175+
const std::array<double, 9> expected_covariance = {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
176+
ASSERT_THAT(msg.magnetic_field_covariance, ::testing::ElementsAreArray(expected_covariance));
177+
}
178+
179+
TEST_F(
180+
MagnetometerBroadcasterTest,
181+
whenBroadcasterIsActiveAndStaticCovarianceShouldPublishWithStaticCovariance)
182+
{
183+
const std::array<double, 9> static_covariance = {{1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}};
184+
const auto node_options = create_node_options_with_overriden_parameters(
185+
{sensor_name_param_,
186+
frame_id_param_,
187+
{"static_covariance",
188+
std::vector<double>{static_covariance.begin(), static_covariance.end()}}});
189+
const auto result = broadcaster->init(
190+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
191+
ASSERT_EQ(result, controller_interface::return_type::OK);
192+
ASSERT_EQ(broadcaster->on_configure(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
193+
setup_broadcaster();
194+
ASSERT_EQ(broadcaster->on_activate(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
195+
196+
const auto msg = subscribe_and_get_message();
197+
EXPECT_EQ(msg.header.frame_id, frame_id_);
198+
EXPECT_EQ(msg.magnetic_field.x, sensor_values_[0]);
199+
EXPECT_EQ(msg.magnetic_field.y, sensor_values_[1]);
200+
EXPECT_EQ(msg.magnetic_field.z, sensor_values_[2]);
201+
202+
ASSERT_THAT(msg.magnetic_field_covariance, ::testing::ElementsAreArray(static_covariance));
203+
}

0 commit comments

Comments
 (0)