Skip to content

Commit 414ff43

Browse files
add tests for magnetometer_broadcaster/MagnetometerBroadcaster
1 parent 017795c commit 414ff43

File tree

5 files changed

+285
-0
lines changed

5 files changed

+285
-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

magnetometer_broadcaster/package.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
<depend>realtime_tools</depend>
3636
<depend>sensor_msgs</depend>
3737

38+
<test_depend>ament_cmake_gmock</test_depend>
39+
<test_depend>controller_manager</test_depend>
40+
<test_depend>ros2_control_test_assets</test_depend>
41+
3842
<export>
3943
<build_type>ament_cmake</build_type>
4044
</export>
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: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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 <utility>
21+
22+
#include <class_loader/class_loader.hpp>
23+
#include <controller_interface/controller_interface.hpp>
24+
#include <hardware_interface/loaned_state_interface.hpp>
25+
#include <hardware_interface/types/hardware_interface_return_values.hpp>
26+
#include <hardware_interface/types/hardware_interface_type_values.hpp>
27+
#include <magnetometer_broadcaster/magnetometer_broadcaster_parameters.hpp>
28+
#include <rclcpp/node.hpp>
29+
#include <rclcpp/wait_result_kind.hpp>
30+
#include <rclcpp/wait_set.hpp>
31+
#include <rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp>
32+
#include <ros2_control_test_assets/descriptions.hpp>
33+
#include <sensor_msgs/msg/magnetic_field.hpp>
34+
35+
using callback_return_type =
36+
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
37+
38+
namespace
39+
{
40+
rclcpp::WaitResultKind wait_for(std::shared_ptr<rclcpp::SubscriptionBase> subscription)
41+
{
42+
rclcpp::WaitSet wait_set;
43+
wait_set.add_subscription(subscription);
44+
return wait_set.wait().kind();
45+
}
46+
47+
rclcpp::NodeOptions create_node_options_with_overriden_parameters(
48+
std::vector<rclcpp::Parameter> parameters)
49+
{
50+
auto node_options = rclcpp::NodeOptions();
51+
node_options.parameter_overrides(parameters);
52+
return node_options;
53+
}
54+
} // namespace
55+
56+
class MagnetometerBroadcasterTest : public ::testing::Test
57+
{
58+
public:
59+
MagnetometerBroadcasterTest() { rclcpp::init(0, nullptr); }
60+
61+
~MagnetometerBroadcasterTest() { rclcpp::shutdown(); }
62+
63+
void SetUp()
64+
{
65+
broadcaster = loader->createInstance<controller_interface::ControllerInterface>(
66+
"magnetometer_broadcaster::MagnetometerBroadcaster");
67+
}
68+
69+
void TearDown() { broadcaster.reset(); }
70+
71+
void setup_broadcaster()
72+
{
73+
std::vector<hardware_interface::LoanedStateInterface> state_ifs;
74+
state_ifs.emplace_back(magnetic_field_x, nullptr);
75+
state_ifs.emplace_back(magnetic_field_y, nullptr);
76+
state_ifs.emplace_back(magnetic_field_z, nullptr);
77+
78+
broadcaster->assign_interfaces({}, std::move(state_ifs));
79+
}
80+
81+
sensor_msgs::msg::MagneticField subscribe_and_get_message()
82+
{
83+
rclcpp::Node test_subscription_node("test_subscription_node");
84+
auto subscription = test_subscription_node.create_subscription<sensor_msgs::msg::MagneticField>(
85+
"/test_magnetometer_broadcaster/magnetic_field", 10,
86+
[](const sensor_msgs::msg::MagneticField::SharedPtr) {});
87+
broadcaster->update(rclcpp::Time{}, rclcpp::Duration::from_seconds(0));
88+
wait_for(subscription);
89+
90+
rclcpp::MessageInfo msg_info;
91+
sensor_msgs::msg::MagneticField msg;
92+
subscription->take(msg, msg_info);
93+
return msg;
94+
}
95+
96+
controller_interface::ControllerInterfaceParams create_ctrl_params(
97+
const rclcpp::NodeOptions & node_options, const std::string & robot_description = "")
98+
{
99+
controller_interface::ControllerInterfaceParams params;
100+
params.controller_name = "test_magnetometer_broadcaster";
101+
params.robot_description = robot_description;
102+
params.update_rate = 0;
103+
params.node_namespace = "";
104+
params.node_options = node_options;
105+
return params;
106+
}
107+
108+
protected:
109+
const std::string sensor_name_ = "magnetometer";
110+
const rclcpp::Parameter sensor_name_param_ = rclcpp::Parameter("sensor_name", sensor_name_);
111+
const std::string frame_id_ = "magnetometer_frame";
112+
const rclcpp::Parameter frame_id_param_ = rclcpp::Parameter("frame_id", frame_id_);
113+
114+
std::array<double, 3> sensor_values_ = {{20e-6, 30e-6, 40e-6}};
115+
hardware_interface::StateInterface::SharedPtr magnetic_field_x =
116+
std::make_shared<hardware_interface::StateInterface>(
117+
sensor_name_, "magnetic_field.x", &sensor_values_[0]);
118+
hardware_interface::StateInterface::SharedPtr magnetic_field_y =
119+
std::make_shared<hardware_interface::StateInterface>(
120+
sensor_name_, "magnetic_field.y", &sensor_values_[1]);
121+
hardware_interface::StateInterface::SharedPtr magnetic_field_z =
122+
std::make_shared<hardware_interface::StateInterface>(
123+
sensor_name_, "magnetic_field.z", &sensor_values_[2]);
124+
125+
std::unique_ptr<class_loader::ClassLoader> loader =
126+
std::make_unique<class_loader::ClassLoader>(std::string{});
127+
128+
controller_interface::ControllerInterfaceSharedPtr broadcaster = nullptr;
129+
};
130+
131+
TEST_F(MagnetometerBroadcasterTest, whenNoParamsAreSetThenInitShouldFail)
132+
{
133+
const auto result = broadcaster->init(create_ctrl_params(
134+
broadcaster->define_custom_node_options(), ros2_control_test_assets::minimal_robot_urdf));
135+
ASSERT_EQ(result, controller_interface::return_type::ERROR);
136+
}
137+
138+
TEST_F(MagnetometerBroadcasterTest, whenOnlySensorNameIsSetThenInitShouldFail)
139+
{
140+
const auto node_options = create_node_options_with_overriden_parameters({sensor_name_param_});
141+
const auto result = broadcaster->init(
142+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
143+
ASSERT_EQ(result, controller_interface::return_type::ERROR);
144+
}
145+
146+
TEST_F(
147+
MagnetometerBroadcasterTest,
148+
whenAllRequiredArgumentsAreSetThenInitConfigureAndActivationShouldSucceed)
149+
{
150+
const auto node_options =
151+
create_node_options_with_overriden_parameters({sensor_name_param_, frame_id_param_});
152+
const auto result = broadcaster->init(
153+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
154+
ASSERT_EQ(result, controller_interface::return_type::OK);
155+
ASSERT_EQ(broadcaster->on_configure(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
156+
ASSERT_EQ(broadcaster->on_activate(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
157+
}
158+
159+
TEST_F(MagnetometerBroadcasterTest, whenBroadcasterIsActiveShouldPublishWithCovarianceSetToZero)
160+
{
161+
const auto node_options =
162+
create_node_options_with_overriden_parameters({sensor_name_param_, frame_id_param_});
163+
const auto result = broadcaster->init(
164+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
165+
ASSERT_EQ(result, controller_interface::return_type::OK);
166+
ASSERT_EQ(broadcaster->on_configure(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
167+
setup_broadcaster();
168+
ASSERT_EQ(broadcaster->on_activate(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
169+
170+
const auto msg = subscribe_and_get_message();
171+
EXPECT_EQ(msg.header.frame_id, frame_id_);
172+
EXPECT_EQ(msg.magnetic_field.x, sensor_values_[0]);
173+
EXPECT_EQ(msg.magnetic_field.y, sensor_values_[1]);
174+
EXPECT_EQ(msg.magnetic_field.z, sensor_values_[2]);
175+
176+
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}};
177+
ASSERT_THAT(msg.magnetic_field_covariance, ::testing::ElementsAreArray(expected_covariance));
178+
}
179+
180+
TEST_F(
181+
MagnetometerBroadcasterTest,
182+
whenBroadcasterIsActiveAndStaticCovarianceShouldPublishWithStaticCovariance)
183+
{
184+
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}};
185+
const auto node_options = create_node_options_with_overriden_parameters(
186+
{sensor_name_param_,
187+
frame_id_param_,
188+
{"static_covariance",
189+
std::vector<double>{static_covariance.begin(), static_covariance.end()}}});
190+
const auto result = broadcaster->init(
191+
create_ctrl_params(node_options, ros2_control_test_assets::minimal_robot_urdf));
192+
ASSERT_EQ(result, controller_interface::return_type::OK);
193+
ASSERT_EQ(broadcaster->on_configure(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
194+
setup_broadcaster();
195+
ASSERT_EQ(broadcaster->on_activate(rclcpp_lifecycle::State()), callback_return_type::SUCCESS);
196+
197+
const auto msg = subscribe_and_get_message();
198+
EXPECT_EQ(msg.header.frame_id, frame_id_);
199+
EXPECT_EQ(msg.magnetic_field.x, sensor_values_[0]);
200+
EXPECT_EQ(msg.magnetic_field.y, sensor_values_[1]);
201+
EXPECT_EQ(msg.magnetic_field.z, sensor_values_[2]);
202+
203+
ASSERT_THAT(msg.magnetic_field_covariance, ::testing::ElementsAreArray(static_covariance));
204+
}

0 commit comments

Comments
 (0)