Files
rclcpp/rclcpp/test/rclcpp/test_node.cpp
William Woodall 1037822a63 refactor AnySubscriptionCallback and add/deprecate callback signatures (#1598)
* refactor AnySubscriptionCallback to...

use std::variant and make the dispatch functions constexpr,
avoiding runtime dispatching.

Also, deprecate the std::function<void (std::shared_ptr<MessageT>)> signature,
as it is unsafe to share one shared_ptr when multiple subscriptions take it,
because they could mutate MessageT while others are using it. So you'd have to
make a copy for each subscription, which is no different than the
std::unique_ptr<MessageT> signature or the user making their own copy in a
shared_ptr from the const MessageT & signature or the
std::shared_ptr<const MessageT> signature.

Added a std::function<void (const std::shared_ptr<const MessageT> &)> signature
to go along side the existing
std::function<void (std::shared_ptr<const MessageT>)> signature.

Removed redundant 'const' before pass-by-value signatures, e.g.
std::function<void (const shared_ptr<const MessageT>)> became
std::function<void (shared_ptr<const MessageT>)>.
This will not affect API or any users using the old style.

Signed-off-by: William Woodall <william@osrfoundation.org>

* fix use of std::bind, free functions, etc. using new function_traits::as_std_function<>

Signed-off-by: William Woodall <william@osrfoundation.org>

* fix use of const MessageT & callbacks by fixing subscriptin_traits

Signed-off-by: William Woodall <william@osrfoundation.org>

* fix deprecation warnings

Signed-off-by: William Woodall <william@osrfoundation.org>

* use target_compile_features to require c++17 for downstream users of rclcpp

Signed-off-by: William Woodall <william@osrfoundation.org>

* uncrustify

Signed-off-by: William Woodall <william@osrfoundation.org>

* cpplint

Signed-off-by: William Woodall <william@osrfoundation.org>

* use target_compile_features(... cxx_std_17)

Signed-off-by: William Woodall <william@osrfoundation.org>

* Keep both std::shared_ptr<const MessageT> and const std::shared_ptr<const MessageT> & signatures.

The const std::shared_ptr<const MessageT> & signature is being kept because it
can be more flexible and efficient than std::shared_ptr<const MessageT>, but
costs realtively little to support.

The std::shared_ptr<const MessageT> signature is being kept because we want to
avoid deprecating it and causing disruption, and because it is convenient to
write, and in most cases will not materially impact the performance.

Signed-off-by: William Woodall <william@osrfoundation.org>

* defer deprecation of the shared_ptr<MessageT> sub callbacks

Signed-off-by: William Woodall <william@osrfoundation.org>

* fix unused variable warning

Signed-off-by: William Woodall <william@osrfoundation.org>

* small fixups to AnySubscriptionCallback

Signed-off-by: William Woodall <william@osrfoundation.org>

* add check for unset AnySubscriptionCallback in dispatch methods

Signed-off-by: William Woodall <william@osrfoundation.org>

* update dispatch methods to handle all scenarios

Signed-off-by: William Woodall <william@osrfoundation.org>

* updated tests for AnySubscriptionCallback, include full parameterized i/o matrix

Signed-off-by: William Woodall <william@osrfoundation.org>

* fixup test with changed assumption

Signed-off-by: William Woodall <william@osrfoundation.org>

* remove use of std::unary_function, which was removed in c++17

Signed-off-by: William Woodall <william@osrfoundation.org>

* silence c++17 warnings on windows for now

Signed-off-by: William Woodall <william@osrfoundation.org>
2021-04-02 17:30:29 -07:00

2796 lines
103 KiB
C++

// Copyright 2017 Open Source Robotics Foundation, Inc.
//
// 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 <gtest/gtest.h>
#include <map>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include "rclcpp/exceptions.hpp"
#include "rclcpp/node.hpp"
#include "rclcpp/scope_exit.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rcpputils/filesystem_helper.hpp"
#include "rmw/validate_namespace.h"
#include "test_msgs/msg/basic_types.hpp"
#include "test_msgs/msg/empty.hpp"
#include "test_msgs/srv/empty.hpp"
#include "../mocking_utils/patch.hpp"
class TestNode : public ::testing::Test
{
protected:
static void SetUpTestCase()
{
rclcpp::init(0, nullptr);
}
static void TearDownTestCase()
{
rclcpp::shutdown();
}
void SetUp() override
{
test_resources_path /= "test_node";
}
rcpputils::fs::path test_resources_path{TEST_RESOURCES_DIRECTORY};
};
/*
Testing node construction and destruction.
*/
TEST_F(TestNode, construction_and_destruction) {
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
EXPECT_NE(nullptr, node->get_node_base_interface());
EXPECT_NE(nullptr, node->get_node_clock_interface());
EXPECT_NE(nullptr, node->get_node_graph_interface());
EXPECT_NE(nullptr, node->get_node_logging_interface());
EXPECT_NE(nullptr, node->get_node_time_source_interface());
EXPECT_NE(nullptr, node->get_node_timers_interface());
EXPECT_NE(nullptr, node->get_node_topics_interface());
EXPECT_NE(nullptr, node->get_node_services_interface());
EXPECT_NE(nullptr, node->get_node_parameters_interface());
EXPECT_NE(nullptr, node->get_node_waitables_interface());
EXPECT_NE(nullptr, node->get_node_options().get_rcl_node_options());
EXPECT_NE(nullptr, node->get_graph_event());
EXPECT_NE(nullptr, node->get_clock());
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("invalid_node?", "/ns");
(void)node;
}, rclcpp::exceptions::InvalidNodeNameError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/invalid_ns?");
(void)node;
}, rclcpp::exceptions::InvalidNamespaceError);
}
}
TEST_F(TestNode, get_name_and_namespace) {
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/ns", node->get_namespace());
EXPECT_STREQ("/ns/my_node", node->get_fully_qualified_name());
}
{
auto options = rclcpp::NodeOptions()
.arguments({"--ros-args", "-r", "__ns:=/another_ns"});
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns", options);
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/another_ns", node->get_namespace());
EXPECT_STREQ("/another_ns/my_node", node->get_fully_qualified_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/ns", node->get_namespace());
EXPECT_STREQ("/ns/my_node", node->get_fully_qualified_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node");
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/", node->get_namespace());
EXPECT_STREQ("/my_node", node->get_fully_qualified_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "");
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/", node->get_namespace());
EXPECT_STREQ("/my_node", node->get_fully_qualified_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/my/ns");
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/my/ns", node->get_namespace());
EXPECT_STREQ("/my/ns/my_node", node->get_fully_qualified_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "my/ns");
EXPECT_STREQ("my_node", node->get_name());
EXPECT_STREQ("/my/ns", node->get_namespace());
EXPECT_STREQ("/my/ns/my_node", node->get_fully_qualified_name());
}
{
auto node1 = std::make_shared<rclcpp::Node>("my_node1", "my/ns");
auto node2 = std::make_shared<rclcpp::Node>("my_node2", "my/ns");
auto node3 = std::make_shared<rclcpp::Node>("my_node3", "/ns2");
auto node4 = std::make_shared<rclcpp::Node>("my_node4", "my/ns3");
auto names_and_namespaces = node1->get_node_names();
auto name_namespace_set = std::unordered_set<std::string>(
names_and_namespaces.begin(),
names_and_namespaces.end());
std::function<bool(std::string)> Set_Contains = [&](std::string string_key)
{
return name_namespace_set.find(string_key) != name_namespace_set.end();
};
EXPECT_TRUE(Set_Contains("/my/ns/my_node1"));
EXPECT_TRUE(Set_Contains("/my/ns/my_node2"));
EXPECT_TRUE(Set_Contains("/ns2/my_node3"));
EXPECT_TRUE(Set_Contains("/my/ns3/my_node4"));
}
}
TEST_F(TestNode, subnode_get_name_and_namespace) {
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
auto subnode = node->create_sub_node("sub_ns");
EXPECT_STREQ("my_node", subnode->get_name());
EXPECT_STREQ("/ns", subnode->get_namespace());
EXPECT_STREQ("sub_ns", subnode->get_sub_namespace().c_str());
EXPECT_STREQ("/ns/sub_ns", subnode->get_effective_namespace().c_str());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto subnode = node->create_sub_node("sub_ns");
EXPECT_STREQ("my_node", subnode->get_name());
EXPECT_STREQ("/ns", subnode->get_namespace());
EXPECT_STREQ("sub_ns", subnode->get_sub_namespace().c_str());
EXPECT_STREQ("/ns/sub_ns", subnode->get_effective_namespace().c_str());
auto subnode2 = subnode->create_sub_node("sub_ns2");
EXPECT_STREQ("my_node", subnode2->get_name());
EXPECT_STREQ("/ns", subnode2->get_namespace());
EXPECT_STREQ("sub_ns/sub_ns2", subnode2->get_sub_namespace().c_str());
EXPECT_STREQ("/ns/sub_ns/sub_ns2", subnode2->get_effective_namespace().c_str());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node");
auto subnode = node->create_sub_node("sub_ns");
EXPECT_STREQ("my_node", subnode->get_name());
EXPECT_STREQ("/", subnode->get_namespace());
EXPECT_STREQ("sub_ns", subnode->get_sub_namespace().c_str());
EXPECT_STREQ("/sub_ns", subnode->get_effective_namespace().c_str());
auto subnode2 = subnode->create_sub_node("sub_ns2");
EXPECT_STREQ("my_node", subnode2->get_name());
EXPECT_STREQ("/", subnode2->get_namespace());
EXPECT_STREQ("sub_ns/sub_ns2", subnode2->get_sub_namespace().c_str());
EXPECT_STREQ("/sub_ns/sub_ns2", subnode2->get_effective_namespace().c_str());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node");
ASSERT_THROW(
{
auto subnode = node->create_sub_node("/sub_ns");
}, rclcpp::exceptions::NameValidationError);
}
}
/*
Testing node construction and destruction.
*/
TEST_F(TestNode, subnode_construction_and_destruction) {
{
ASSERT_NO_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
auto subnode = node->create_sub_node("sub_ns");
});
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
auto subnode = node->create_sub_node("invalid_ns?");
}, rclcpp::exceptions::InvalidNamespaceError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns/");
}, rclcpp::exceptions::InvalidNamespaceError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns/");
auto subnode = node->create_sub_node("/sub_ns");
}, rclcpp::exceptions::InvalidNamespaceError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
auto subnode = node->create_sub_node("/sub_ns");
}, rclcpp::exceptions::NameValidationError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
auto subnode = node->create_sub_node("~sub_ns");
}, rclcpp::exceptions::InvalidNamespaceError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto subnode = node->create_sub_node("invalid_ns?");
}, rclcpp::exceptions::InvalidNamespaceError);
}
{
ASSERT_NO_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto subnode = node->create_sub_node("sub_ns");
});
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto subnode = node->create_sub_node("/sub_ns");
}, rclcpp::exceptions::NameValidationError);
}
{
ASSERT_THROW(
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto subnode = node->create_sub_node("~sub_ns");
}, rclcpp::exceptions::InvalidNamespaceError);
}
}
TEST_F(TestNode, get_logger) {
{
auto node = std::make_shared<rclcpp::Node>("my_node");
EXPECT_STREQ("my_node", node->get_logger().get_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
EXPECT_STREQ("ns.my_node", node->get_logger().get_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "ns");
EXPECT_STREQ("ns.my_node", node->get_logger().get_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "/my/ns");
EXPECT_STREQ("my.ns.my_node", node->get_logger().get_name());
}
{
auto node = std::make_shared<rclcpp::Node>("my_node", "my/ns");
EXPECT_STREQ("my.ns.my_node", node->get_logger().get_name());
}
}
TEST_F(TestNode, get_clock) {
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto ros_clock = node->get_clock();
EXPECT_NE(nullptr, ros_clock);
EXPECT_EQ(ros_clock->get_clock_type(), RCL_ROS_TIME);
const rclcpp::Node & const_node = *node.get();
EXPECT_NE(nullptr, const_node.get_clock());
}
TEST_F(TestNode, now) {
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
auto clock = node->get_clock();
auto now_builtin = node->now().nanoseconds();
auto now_external = clock->now().nanoseconds();
EXPECT_GE(now_external, now_builtin);
EXPECT_LT(now_external - now_builtin, 5000000L);
}
std::string
operator"" _unq(const char * prefix, size_t prefix_length)
{
static uint64_t count = 0;
return std::string(prefix, prefix_length) + "_" + std::to_string(++count);
}
TEST_F(TestNode, declare_parameter_with_no_initial_values) {
// test cases without initial values
auto node = std::make_shared<rclcpp::Node>("test_declare_parameter_node"_unq);
{
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
// no default, no initial
rclcpp::ParameterValue value = node->declare_parameter(
"parameter"_unq, rclcpp::ParameterValue{}, descriptor);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
}
{
// int default, no initial
rclcpp::ParameterValue default_value(42);
rclcpp::ParameterValue value = node->declare_parameter("parameter"_unq, default_value);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), default_value.get<int>());
}
{
// int default, no initial, custom parameter descriptor
auto name = "parameter"_unq;
rclcpp::ParameterValue default_value(42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
rclcpp::ParameterValue value =
node->declare_parameter(name, default_value, descriptor);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), default_value.get<int>());
rcl_interfaces::msg::ParameterDescriptor actual_descriptor =
node->describe_parameter(name);
EXPECT_EQ(actual_descriptor.read_only, descriptor.read_only);
}
{
// int default, no initial, implicit template specialization
int default_value = 42;
EXPECT_EQ(node->declare_parameter("parameter"_unq, default_value), default_value);
}
{
// parameter already declared throws
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_THROW(
{
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
},
rclcpp::exceptions::ParameterAlreadyDeclaredException);
}
{
// parameter name invalid throws
EXPECT_THROW(
{node->declare_parameter("", 5);},
rclcpp::exceptions::InvalidParametersException);
}
{
// parameter rejected throws
auto name = "parameter"_unq;
auto on_set_parameters =
[&name](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto & parameter : parameters) {
if (
parameter.get_name() == name &&
parameter.get_type() != rclcpp::PARAMETER_INTEGER)
{
result.successful = false;
result.reason = "'" + name + "' must be an integer";
}
}
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
EXPECT_THROW(
{node->declare_parameter<std::string>(name, "not an int");},
rclcpp::exceptions::InvalidParameterValueException);
}
}
TEST_F(TestNode, declare_parameter_with_allow_undeclared_parameters) {
// test cases without initial values
auto node = std::make_shared<rclcpp::Node>(
"test_declare_parameter_node"_unq, "/",
rclcpp::NodeOptions{}.allow_undeclared_parameters(true));
{
// declared parameters static typing is still enforced
auto param_name = "parameter"_unq;
auto value = node->declare_parameter(param_name, 5);
EXPECT_EQ(value, 5);
EXPECT_FALSE(node->set_parameter({param_name, "asd"}).successful);
auto param = node->get_parameter(param_name);
EXPECT_EQ(param.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(param.get_value<int64_t>(), 5);
}
{
// not for automatically declared parameters
auto param_name = "parameter"_unq;
EXPECT_TRUE(node->set_parameter({param_name, 5}).successful);
auto param = node->get_parameter(param_name);
EXPECT_EQ(param.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(param.get_value<int64_t>(), 5);
EXPECT_TRUE(node->set_parameter({param_name, "asd"}).successful);
param = node->get_parameter(param_name);
EXPECT_EQ(param.get_type(), rclcpp::PARAMETER_STRING);
EXPECT_EQ(param.get_value<std::string>(), "asd");
}
{
// declare after set is invalid
auto param_name = "parameter"_unq;
EXPECT_TRUE(node->set_parameter({param_name, 5}).successful);
auto param = node->get_parameter(param_name);
EXPECT_EQ(param.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(param.get_value<int64_t>(), 5);
EXPECT_THROW(
node->declare_parameter(param_name, 5),
rclcpp::exceptions::ParameterAlreadyDeclaredException);
}
}
auto get_fixed_on_parameter_set_callback(const std::string & name, bool successful)
{
return
[name, successful](const std::vector<rclcpp::Parameter> & parameters) {
(void)parameters;
rcl_interfaces::msg::SetParametersResult result;
result.successful = successful;
return result;
};
}
TEST_F(TestNode, test_registering_multiple_callbacks_api) {
auto node = std::make_shared<rclcpp::Node>("test_declare_parameter_node"_unq);
{
int64_t default_value{42};
auto name1 = "parameter"_unq;
auto scoped_callback1 = node->add_on_set_parameters_callback(
get_fixed_on_parameter_set_callback(name1, true));
EXPECT_NE(scoped_callback1, nullptr);
int64_t value{node->declare_parameter(name1, default_value)};
EXPECT_EQ(value, default_value);
auto name2 = "parameter"_unq;
auto scoped_callback2 = node->add_on_set_parameters_callback(
get_fixed_on_parameter_set_callback(name2, false));
EXPECT_NE(scoped_callback2, nullptr);
EXPECT_THROW(
{node->declare_parameter(name2, default_value);},
rclcpp::exceptions::InvalidParameterValueException);
auto name3 = "parameter"_unq;
scoped_callback2.reset();
value = node->declare_parameter(name3, default_value);
EXPECT_EQ(value, default_value);
}
{
int64_t default_value{42};
auto name1 = "parameter"_unq;
auto scoped_callback1 = node->add_on_set_parameters_callback(
get_fixed_on_parameter_set_callback(name1, true));
EXPECT_NE(scoped_callback1, nullptr);
int64_t value{node->declare_parameter(name1, default_value)};
EXPECT_EQ(value, default_value);
auto name2 = "parameter"_unq;
auto scoped_callback2 = node->add_on_set_parameters_callback(
get_fixed_on_parameter_set_callback(name2, false));
EXPECT_NE(scoped_callback2, nullptr);
EXPECT_THROW(
{node->declare_parameter(name2, default_value);},
rclcpp::exceptions::InvalidParameterValueException);
auto name3 = "parameter"_unq;
node->remove_on_set_parameters_callback(scoped_callback2.get());
value = node->declare_parameter(name3, default_value);
EXPECT_EQ(value, default_value);
}
{
int64_t default_value{42};
auto name1 = "parameter"_unq;
rclcpp::Node::OnSetParametersCallbackHandle::SharedPtr scoped_callback(
node->add_on_set_parameters_callback(
get_fixed_on_parameter_set_callback(name1, false)));
rclcpp::Node::OnSetParametersCallbackHandle::SharedPtr scoped_callback_copy(scoped_callback);
scoped_callback.reset();
EXPECT_THROW(
{node->declare_parameter("parameter"_unq, default_value);},
rclcpp::exceptions::InvalidParameterValueException);
scoped_callback_copy.reset();
// All the shared_ptr has been reset
int64_t value = node->declare_parameter("parameter"_unq, default_value);
EXPECT_EQ(value, default_value);
}
}
TEST_F(TestNode, declare_parameter_with_overrides) {
// test cases with overrides
rclcpp::NodeOptions no;
no.parameter_overrides(
{
{"parameter_no_default", 42},
{"parameter_no_default_set", 42},
{"parameter_no_default_set_cvref", 42},
{"parameter_and_default", 42},
{"parameter_and_default_ignore_override", 42},
{"parameter_custom", 42},
{"parameter_template", 42},
{"parameter_already_declared", 42},
{"parameter_rejected", 42},
{"parameter_type_mismatch", "not an int"},
});
auto node = std::make_shared<rclcpp::Node>("test_declare_parameter_node"_unq, no);
{
// no default, with override
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_no_default", rclcpp::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), 42);
}
{
// no default, with override, and set after
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_no_default_set", rclcpp::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), 42);
// check that the value is changed after a set
node->set_parameter({"parameter_no_default_set", 44});
EXPECT_EQ(node->get_parameter("parameter_no_default_set").get_value<int>(), 44);
}
{
// no default, with override
const rclcpp::ParameterValue & value = node->declare_parameter(
"parameter_no_default_set_cvref", rclcpp::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), 42);
// check that the value is changed after a set
node->set_parameter({"parameter_no_default_set_cvref", 44});
EXPECT_EQ(value.get<int>(), 44);
}
{
// int default, with override
rclcpp::ParameterValue default_value(43);
rclcpp::ParameterValue value = node->declare_parameter("parameter_and_default", default_value);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), 42); // and not 43 which is the default value
}
{
// int default, with override and ignoring it
rclcpp::ParameterValue default_value(43);
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_and_default_ignore_override",
default_value,
rcl_interfaces::msg::ParameterDescriptor(),
true);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), 43); // and not 42, the parameter override is ignored.
}
{
// int default, with initial, custom parameter descriptor
rclcpp::ParameterValue default_value(43);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
rclcpp::ParameterValue value =
node->declare_parameter("parameter_custom", default_value, descriptor);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int>(), 42); // and not 43 which is the default value
rcl_interfaces::msg::ParameterDescriptor actual_descriptor =
node->describe_parameter("parameter_custom");
EXPECT_EQ(actual_descriptor.read_only, descriptor.read_only);
}
{
// int default, with initial, implicit template specialization
int default_value = 43;
// is equal to 42, not 43 which is the default value
EXPECT_EQ(node->declare_parameter("parameter_template", default_value), 42);
}
{
// parameter already declared throws
auto name = "parameter_already_declared";
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_THROW(
{
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
},
rclcpp::exceptions::ParameterAlreadyDeclaredException);
}
{
// parameter name invalid throws
EXPECT_THROW(
{node->declare_parameter("", 5);},
rclcpp::exceptions::InvalidParametersException);
}
{
// parameter rejected throws, with initial value
auto name = std::string("parameter_rejected");
auto on_set_parameters =
[&name](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto & parameter : parameters) {
if (
parameter.get_name() == name &&
parameter.get_type() == rclcpp::PARAMETER_INTEGER)
{
if (parameter.get_value<int>() < 43) {
result.successful = false;
result.reason = "'" + name + "' must be an integer and less than 43";
}
}
}
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
EXPECT_THROW(
{node->declare_parameter<int>(name, 43);},
rclcpp::exceptions::InvalidParameterValueException);
}
{
// default type and initial value type do not match
EXPECT_THROW(
{node->declare_parameter("parameter_type_mismatch", 42);},
rclcpp::exceptions::InvalidParameterTypeException);
}
{
// default type and expected type do not match
EXPECT_THROW(
{node->declare_parameter(
"parameter_type_mismatch", rclcpp::ParameterType::PARAMETER_INTEGER);},
rclcpp::exceptions::InvalidParameterTypeException);
}
{
// cannot pass an expected type and a descriptor with dynamic_typing=True
rcl_interfaces::msg::ParameterDescriptor descriptor{};
descriptor.dynamic_typing = true;
EXPECT_THROW(
{node->declare_parameter(
"invalid_argument", rclcpp::ParameterType::PARAMETER_INTEGER, descriptor);},
std::invalid_argument);
}
}
TEST_F(TestNode, declare_parameters_with_no_initial_values) {
// test cases without initial values
auto node = std::make_shared<rclcpp::Node>("test_declare_parameters_node"_unq);
{
// with namespace, defaults, no custom descriptors, no initial
int64_t bigger_than_int = INT64_MAX - 42;
auto values = node->declare_parameters<int64_t>(
"namespace1", {
{"parameter_a", 42},
{"parameter_b", 256},
{"parameter_c", bigger_than_int},
});
std::vector<int64_t> expected = {42, 256, bigger_than_int};
EXPECT_EQ(values, expected);
EXPECT_TRUE(node->has_parameter("namespace1.parameter_a"));
EXPECT_TRUE(node->has_parameter("namespace1.parameter_b"));
EXPECT_FALSE(node->has_parameter("namespace1"));
}
{
// without namespace, defaults, no custom descriptors, no initial
auto values = node->declare_parameters<int64_t>(
"", {
{"parameter_without_ns_a", 42},
{"parameter_without_ns_b", 256},
});
std::vector<int64_t> expected = {42, 256};
EXPECT_EQ(values, expected);
EXPECT_TRUE(node->has_parameter("parameter_without_ns_a"));
EXPECT_TRUE(node->has_parameter("parameter_without_ns_b"));
}
{
// with namespace, defaults, custom descriptors, no initial
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
auto values = node->declare_parameters<int64_t>(
"namespace2", {
{"parameter_a", {42, descriptor}},
{"parameter_b", {256, descriptor}},
});
std::vector<int64_t> expected = {42, 256};
EXPECT_EQ(values, expected);
EXPECT_TRUE(node->has_parameter("namespace2.parameter_a"));
EXPECT_TRUE(node->has_parameter("namespace2.parameter_b"));
EXPECT_FALSE(node->has_parameter("namespace2"));
}
{
// without namespace, defaults, custom descriptors, no initial
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
auto values = node->declare_parameters<int64_t>(
"", {
{"parameter_without_ns_c", {42, descriptor}},
{"parameter_without_ns_d", {256, descriptor}},
});
std::vector<int64_t> expected = {42, 256};
EXPECT_EQ(values, expected);
EXPECT_TRUE(node->has_parameter("parameter_without_ns_c"));
EXPECT_TRUE(node->has_parameter("parameter_without_ns_d"));
}
{
// empty parameters
auto values = node->declare_parameters<int64_t>("", {});
std::vector<int64_t> expected {};
EXPECT_EQ(values, expected);
}
{
// parameter already declared throws, even with not_set type
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_THROW(
{node->declare_parameters<int64_t>("", {{name, 42}});},
rclcpp::exceptions::ParameterAlreadyDeclaredException);
}
{
// parameter name invalid throws
EXPECT_THROW(
{node->declare_parameters<int64_t>("", {{"", 42}});},
rclcpp::exceptions::InvalidParametersException);
}
{
// parameter rejected throws
auto name = "parameter"_unq;
auto on_set_parameters =
[&name](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto & parameter : parameters) {
if (
parameter.get_name() == name &&
parameter.get_type() != rclcpp::PARAMETER_INTEGER)
{
result.successful = false;
result.reason = "'" + name + "' must be an integer";
}
}
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
EXPECT_THROW(
{node->declare_parameters<std::string>("", {{name, "not an int"}});},
rclcpp::exceptions::InvalidParameterValueException);
}
}
TEST_F(TestNode, declare_parameter_with_cli_overrides) {
const std::string parameters_filepath = (
test_resources_path / "test_parameters.yaml").string();
// test cases with overrides
rclcpp::NodeOptions no;
no.arguments(
{
"--ros-args",
"-p", "parameter_bool:=false",
"-p", "parameter_int:=42",
"-p", "parameter_double:=0.42",
"-p", "parameter_string:=foo",
"--params-file", parameters_filepath.c_str(),
"-p", "parameter_bool_array:=[false, true]",
"-p", "parameter_int_array:=[-21, 42]",
"-p", "parameter_double_array:=[-1.0, .42]",
"-p", "parameter_string_array:=[foo, bar]"
});
// To match parameters YAML file content, use a well-known node name for this test only.
auto node = std::make_shared<rclcpp::Node>("test_declare_parameter_node", no);
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_bool", rclcpp::ParameterType::PARAMETER_BOOL);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_BOOL);
EXPECT_EQ(value.get<bool>(), true);
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_int", rclcpp::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get<int64_t>(), 21); // set to 42 in CLI, overriden by file
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_double", rclcpp::ParameterType::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get<double>(), 0.42);
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_string", rclcpp::ParameterType::PARAMETER_STRING);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_STRING);
EXPECT_EQ(value.get<std::string>(), "foo");
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_bool_array", rclcpp::ParameterType::PARAMETER_BOOL_ARRAY);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_BOOL_ARRAY);
std::vector<bool> expected_value{false, true};
EXPECT_EQ(value.get<std::vector<bool>>(), expected_value);
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_int_array", rclcpp::ParameterType::PARAMETER_INTEGER_ARRAY);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER_ARRAY);
std::vector<int64_t> expected_value{-21, 42};
EXPECT_EQ(value.get<std::vector<int64_t>>(), expected_value);
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_double_array", rclcpp::ParameterType::PARAMETER_DOUBLE_ARRAY);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE_ARRAY);
std::vector<double> expected_value{-1.0, 0.42};
EXPECT_EQ(value.get<std::vector<double>>(), expected_value);
}
{
rclcpp::ParameterValue value = node->declare_parameter(
"parameter_string_array", rclcpp::ParameterType::PARAMETER_STRING_ARRAY);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_STRING_ARRAY);
std::vector<std::string> expected_value{"foo", "bar"};
// set to [baz, baz, baz] in file, overriden by CLI
EXPECT_EQ(value.get<std::vector<std::string>>(), expected_value);
}
}
TEST_F(TestNode, undeclare_parameter) {
auto node = std::make_shared<rclcpp::Node>("test_undeclare_parameter_node"_unq);
{
// normal use
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_TRUE(node->has_parameter(name));
node->undeclare_parameter(name);
EXPECT_FALSE(node->has_parameter(name));
}
{
// not declared throws
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
EXPECT_THROW(
{node->undeclare_parameter(name);},
rclcpp::exceptions::ParameterNotDeclaredException);
}
{
// statically typed parameter throws
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_THROW(
{node->undeclare_parameter(name);},
rclcpp::exceptions::InvalidParameterTypeException);
EXPECT_TRUE(node->has_parameter(name));
}
{
// read only parameter throws
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter(name, 42, descriptor);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_THROW(
{node->undeclare_parameter(name);},
rclcpp::exceptions::ParameterImmutableException);
EXPECT_TRUE(node->has_parameter(name));
}
}
TEST_F(TestNode, has_parameter) {
auto node = std::make_shared<rclcpp::Node>("test_has_parameter_node"_unq);
// normal use
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_TRUE(node->has_parameter(name));
node->undeclare_parameter(name);
EXPECT_FALSE(node->has_parameter(name));
}
TEST_F(TestNode, list_parameters) {
auto node = std::make_shared<rclcpp::Node>("test_list_parameter_node"_unq);
// normal use
auto name = "parameter"_unq;
const size_t before_size = node->list_parameters({}, 1u).names.size();
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_EQ(1u + before_size, node->list_parameters({}, 1u).names.size());
node->undeclare_parameter(name);
EXPECT_EQ(before_size, node->list_parameters({}, 1u).names.size());
}
TEST_F(TestNode, set_parameter_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_set_parameter_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 42);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 43)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 43);
}
{
// normal use, change type not allowed
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 42);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, "not an integer")).successful);
}
{
// normal use, change type
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, 42, descriptor);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 42);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, "not an integer")).successful);
EXPECT_EQ(node->get_parameter(name).get_value<std::string>(), std::string("not an integer"));
}
{
// normal use, multiple parameters
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 42);
EXPECT_TRUE(node->has_parameter(name1));
node->declare_parameter(name2, true);
EXPECT_TRUE(node->has_parameter(name2));
node->declare_parameter<std::string>(name3, "something");
EXPECT_TRUE(node->has_parameter(name3));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
EXPECT_EQ(node->get_parameter(name2).get_value<bool>(), true);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name2, false)).successful);
EXPECT_EQ(node->get_parameter(name2).get_value<bool>(), false);
}
{
// setting an undeclared parameter throws
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
EXPECT_THROW(
{node->set_parameter(rclcpp::Parameter(name, 42));},
rclcpp::exceptions::ParameterNotDeclaredException);
EXPECT_FALSE(node->has_parameter(name));
}
{
// rejecting parameter does not throw, but fails
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
auto on_set_parameters =
[](const std::vector<rclcpp::Parameter> &) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = false;
result.reason = "no parameter may not be set right now";
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 43)).successful);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET, when already not set, does not undeclare
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
auto value = node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name)).successful);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET, when already to another type, will fail
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name)).successful);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET,
// when dynamic typing is allowing and already set to another type, will undeclare
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, 42, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name)).successful);
EXPECT_FALSE(node->has_parameter(name));
}
{
// setting a parameter with integer range descriptor
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 10;
integer_range.to_value = 18;
integer_range.step = 2;
node->declare_parameter(name, 10, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get_value<int64_t>(), 10);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 14)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 14);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 18)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 15)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 20)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 8)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
}
{
// setting a parameter with integer range descriptor, from_value > to_value
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 20;
integer_range.to_value = 18;
integer_range.step = 1;
node->declare_parameter(name, 20, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get_value<int64_t>(), 20);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 18)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 19)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 10)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 25)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
}
{
// setting a parameter with integer range descriptor, from_value = to_value
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 18;
integer_range.to_value = 18;
integer_range.step = 1;
node->declare_parameter(name, 18, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 17)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 19)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
}
{
// setting a parameter with integer range descriptor, step > distance(from_value, to_value)
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 18;
integer_range.to_value = 25;
integer_range.step = 10;
node->declare_parameter(name, 18, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get_value<int64_t>(), 18);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 25)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 17)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 19)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 26)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
}
{
// setting a parameter with integer range descriptor, distance not multiple of the step.
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 18;
integer_range.to_value = 28;
integer_range.step = 7;
node->declare_parameter(name, 18, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get_value<int64_t>(), 18);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 28)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 28);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 25)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 17)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 19)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 32)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 25);
}
{
// setting a parameter with integer range descriptor, step=0
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 10;
integer_range.to_value = 18;
integer_range.step = 0;
node->declare_parameter(name, 10, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(value.get_value<int64_t>(), 10);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 11);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 15)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 15);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 18)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 19)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int64_t>(), 18);
}
{
// setting a parameter with integer range descriptor and wrong default value will throw
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.integer_range.resize(1);
auto & integer_range = descriptor.integer_range.at(0);
integer_range.from_value = 10;
integer_range.to_value = 18;
integer_range.step = 2;
ASSERT_THROW(
node->declare_parameter(name, 42, descriptor),
rclcpp::exceptions::InvalidParameterValueException);
}
{
// setting a parameter with floating point range descriptor
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 10.0;
floating_point_range.to_value = 11.0;
floating_point_range.step = 0.2;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 10.2)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.2);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 11.3)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 12.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9.4)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
}
{
// setting a parameter with floating point range descriptor, negative step
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 10.0;
floating_point_range.to_value = 11.0;
floating_point_range.step = -0.2;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 10.2)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.2);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 11.3)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 12.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9.4)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
}
{
// setting a parameter with floating point range descriptor, from_value > to_value
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 11.0;
floating_point_range.to_value = 10.0;
floating_point_range.step = 0.2;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 11.2)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 12.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9.4)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
}
{
// setting a parameter with floating point range descriptor, from_value = to_value
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 10.0;
floating_point_range.to_value = 10.0;
floating_point_range.step = 0.2;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 11.2)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 12.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9.4)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.0);
}
{
// setting a parameter with floating point range descriptor, step > distance
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 10.0;
floating_point_range.to_value = 11.0;
floating_point_range.step = 2.2;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 12.2)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 7.8)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
}
{
// setting a parameter with floating point range descriptor, distance not multiple of the step.
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 10.0;
floating_point_range.to_value = 11.0;
floating_point_range.step = 0.7;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 10.7)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.7);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 11.4)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.7);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9.3)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.7);
}
{
// setting a parameter with floating point range descriptor, step=0
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.floating_point_range.resize(1);
auto & floating_point_range = descriptor.floating_point_range.at(0);
floating_point_range.from_value = 10.0;
floating_point_range.to_value = 11.0;
floating_point_range.step = 0.0;
node->declare_parameter(name, 10.0, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_DOUBLE);
EXPECT_EQ(value.get_value<double>(), 10.0);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 10.0001)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.0001);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 10.5479051)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 10.5479051);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 11.0)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 11.001)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
EXPECT_FALSE(node->set_parameter(rclcpp::Parameter(name, 9.999)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<double>(), 11.0);
}
{
// setting a parameter with a different type is still possible
// when having a descriptor specifying a type (type is a status, not a constraint).
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.type = rclcpp::PARAMETER_INTEGER;
node->declare_parameter(name, "asd", descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_STRING);
EXPECT_EQ(value.get_value<std::string>(), "asd");
}
}
TEST_F(TestNode, set_parameter_undeclared_parameters_allowed) {
rclcpp::NodeOptions no;
no.parameter_overrides(
{
{"parameter_with_override", 30},
});
no.allow_undeclared_parameters(true);
auto node = std::make_shared<rclcpp::Node>("test_set_parameter_node"_unq, no);
{
// overrides are ignored when not declaring a parameter
auto name = "parameter_with_override";
EXPECT_FALSE(node->has_parameter(name));
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 43)).successful);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 43);
}
{
// normal use (declare first) still works with this true
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 42);
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 43)).successful);
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 43);
}
{
// setting a parameter that is not declared implicitly declares it
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
EXPECT_TRUE(node->set_parameter(rclcpp::Parameter(name, 43)).successful);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 43);
}
}
TEST_F(TestNode, set_parameters_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_set_parameters_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 1);
node->declare_parameter(name2, true);
node->declare_parameter<std::string>(name3, "blue");
auto rets = node->set_parameters(
{
{name1, 2},
{name2, false},
{name3, "red"},
});
EXPECT_TRUE(std::all_of(rets.begin(), rets.end(), [](auto & r) {return r.successful;}));
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_TRUE(node->has_parameter(name2));
EXPECT_TRUE(node->has_parameter(name3));
}
{
// overwrite and order of setting
auto name = "parameter"_unq;
node->declare_parameter(name, 1);
auto rets = node->set_parameters(
{
{name, 42},
{name, 2},
});
EXPECT_TRUE(std::all_of(rets.begin(), rets.end(), [](auto & r) {return r.successful;}));
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 2);
}
{
// undeclared parameter throws,
// and preceding parameters are still set, but proceeding values are not
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 1);
node->declare_parameter(name3, 100);
EXPECT_THROW(
{
node->set_parameters(
{
{name1, 2},
{name2, "not declared :("},
{name3, 101},
});
},
rclcpp::exceptions::ParameterNotDeclaredException);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
EXPECT_TRUE(node->has_parameter(name3));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 2);
EXPECT_EQ(node->get_parameter(name3).get_value<int>(), 100);
}
{
// rejecting parameter does not throw, but fails
// all parameters to be set are attempted to be set
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 1);
node->declare_parameter(name2, true);
node->declare_parameter<std::string>(name3, "blue");
auto on_set_parameters =
[&name2](const std::vector<rclcpp::Parameter> & ps) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (std::any_of(ps.begin(), ps.end(), [&](auto & p) {return p.get_name() == name2;})) {
result.successful = false;
result.reason = "parameter '" + name2 + "' may not be set right now";
}
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
auto rets = node->set_parameters(
{
{name1, 2},
{name2, false},
{name3, "red"},
});
EXPECT_EQ(rets.size(), 3U);
EXPECT_TRUE(rets[0].successful);
EXPECT_FALSE(rets[1].successful);
EXPECT_NE(rets[1].reason.find("may not be set right now"), std::string::npos);
EXPECT_TRUE(rets[2].successful);
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 2);
EXPECT_EQ(node->get_parameter(name2).get_value<bool>(), true); // old value
EXPECT_EQ(node->get_parameter(name3).get_value<std::string>(), "red");
}
{
// setting type of rclcpp::PARAMETER_NOT_SET, when already not set, does not undeclare
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
auto value = node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
EXPECT_TRUE(node->set_parameters({rclcpp::Parameter(name)})[0].successful);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET, when already to another type, will fail
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_FALSE(node->set_parameters({rclcpp::Parameter(name)})[0].successful);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET,
// when already to another type and dynamic typic allowed, will undeclare
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, 42, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_TRUE(node->set_parameters({rclcpp::Parameter(name)})[0].successful);
EXPECT_FALSE(node->has_parameter(name));
}
}
// test set_parameters with undeclared allowed
TEST_F(TestNode, set_parameters_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_set_parameters_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use (declare first) still works with this true
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
node->declare_parameter(name1, 42);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
node->declare_parameter<std::string>(name2, "test");
EXPECT_TRUE(node->has_parameter(name2));
EXPECT_EQ(node->get_parameter(name2).get_value<std::string>(), "test");
auto rets = node->set_parameters(
{
rclcpp::Parameter(name1, 43),
rclcpp::Parameter(name2, "other"),
});
EXPECT_TRUE(std::all_of(rets.begin(), rets.end(), [](auto & r) {return r.successful;}));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 43);
EXPECT_EQ(node->get_parameter(name2).get_value<std::string>(), "other");
}
{
// setting a parameter that is not declared implicitly declares it
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
auto rets = node->set_parameters(
{
rclcpp::Parameter(name1, 42),
rclcpp::Parameter(name2, "test"),
});
EXPECT_TRUE(std::all_of(rets.begin(), rets.end(), [](auto & r) {return r.successful;}));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
EXPECT_EQ(node->get_parameter(name2).get_value<std::string>(), "test");
}
}
TEST_F(TestNode, set_parameters_atomically_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_set_parameters_atomically_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 1);
node->declare_parameter(name2, true);
node->declare_parameter<std::string>(name3, "blue");
auto ret = node->set_parameters_atomically(
{
{name1, 2},
{name2, false},
{name3, "red"},
});
EXPECT_TRUE(ret.successful);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_TRUE(node->has_parameter(name2));
EXPECT_TRUE(node->has_parameter(name3));
}
{
// overwrite and order of setting
auto name = "parameter"_unq;
node->declare_parameter(name, 1);
auto ret = node->set_parameters_atomically(
{
{name, 42},
{name, 2},
});
EXPECT_TRUE(ret.successful);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 2);
}
{
// undeclared parameter throws,
// and no parameters were changed
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 1);
node->declare_parameter(name3, 100);
EXPECT_THROW(
{
node->set_parameters_atomically(
{
{name1, 2},
{name2, "not declared :("},
{name3, 101},
});
},
rclcpp::exceptions::ParameterNotDeclaredException);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
EXPECT_TRUE(node->has_parameter(name3));
// both have old values
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 1);
EXPECT_EQ(node->get_parameter(name3).get_value<int>(), 100);
}
{
// rejecting parameter does not throw, but fails
// and no parameters are changed
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 1);
node->declare_parameter(name2, true);
node->declare_parameter<std::string>(name3, "blue");
auto on_set_parameters =
[&name2](const std::vector<rclcpp::Parameter> & ps) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (std::any_of(ps.begin(), ps.end(), [&](auto & p) {return p.get_name() == name2;})) {
result.successful = false;
result.reason = "parameter '" + name2 + "' may not be set right now";
}
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
auto ret = node->set_parameters_atomically(
{
{name1, 2},
{name2, false}, // should fail to be set, failing the whole operation
{name3, "red"},
});
EXPECT_FALSE(ret.successful);
// all have old values
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 1);
EXPECT_EQ(node->get_parameter(name2).get_value<bool>(), true);
EXPECT_EQ(node->get_parameter(name3).get_value<std::string>(), "blue");
}
{
// setting type of rclcpp::PARAMETER_NOT_SET, when already not set, does not undeclare
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
auto value = node->declare_parameter(name, rclcpp::ParameterValue{}, descriptor);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
EXPECT_TRUE(node->set_parameters_atomically({rclcpp::Parameter(name)}).successful);
EXPECT_TRUE(node->has_parameter(name));
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_NOT_SET);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET, when already to another type, will fail
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_FALSE(node->set_parameters_atomically({rclcpp::Parameter(name)}).successful);
}
{
// setting type of rclcpp::PARAMETER_NOT_SET,
// when dynamic typing is allowed and already declared to another type, will undeclare
auto name = "parameter"_unq;
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.dynamic_typing = true;
node->declare_parameter(name, 42, descriptor);
EXPECT_TRUE(node->has_parameter(name));
auto value = node->get_parameter(name);
EXPECT_EQ(value.get_type(), rclcpp::PARAMETER_INTEGER);
EXPECT_TRUE(node->set_parameters_atomically({rclcpp::Parameter(name)}).successful);
EXPECT_FALSE(node->has_parameter(name));
}
}
// test set_parameters with undeclared allowed
TEST_F(TestNode, set_parameters_atomically_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_set_parameters_atomically_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use (declare first) still works with this true
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
node->declare_parameter(name1, 42);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
node->declare_parameter<std::string>(name2, "test");
EXPECT_TRUE(node->has_parameter(name2));
EXPECT_EQ(node->get_parameter(name2).get_value<std::string>(), "test");
auto ret = node->set_parameters_atomically(
{
rclcpp::Parameter(name1, 43),
rclcpp::Parameter(name2, "other"),
});
EXPECT_TRUE(ret.successful);
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 43);
EXPECT_EQ(node->get_parameter(name2).get_value<std::string>(), "other");
}
{
// setting a parameter that is not declared implicitly declares it
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
auto ret = node->set_parameters_atomically(
{
rclcpp::Parameter(name1, 42),
rclcpp::Parameter(name2, "test"),
});
EXPECT_TRUE(ret.successful);
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
EXPECT_EQ(node->get_parameter(name2).get_value<std::string>(), "test");
}
{
// if an undeclared parameter is implicitly declared, but a later parameter set fails,
// then the implicitly "to be" declared parameter remains undeclared
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto name3 = "parameter"_unq;
node->declare_parameter(name1, 42);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
node->declare_parameter<std::string>(name3, "test");
EXPECT_TRUE(node->has_parameter(name3));
EXPECT_EQ(node->get_parameter(name3).get_value<std::string>(), "test");
auto on_set_parameters =
[&name3](const std::vector<rclcpp::Parameter> & ps) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (std::any_of(ps.begin(), ps.end(), [&](auto & p) {return p.get_name() == name3;})) {
result.successful = false;
result.reason = "parameter '" + name3 + "' may not be set right now";
}
return result;
};
auto handler = node->add_on_set_parameters_callback(on_set_parameters);
RCLCPP_SCOPE_EXIT({node->remove_on_set_parameters_callback(handler.get());}); // always reset
auto ret = node->set_parameters_atomically(
{
rclcpp::Parameter(name1, 43),
rclcpp::Parameter(name2, true), // this would cause implicit declaration
rclcpp::Parameter(name3, "other"), // this set should fail, and fail the whole operation
});
EXPECT_FALSE(ret.successful);
// name1 and name2 remain with the old values
EXPECT_EQ(node->get_parameter(name1).get_value<int>(), 42);
EXPECT_FALSE(node->has_parameter(name2)); // important! name2 remains undeclared
EXPECT_EQ(node->get_parameter(name3).get_value<std::string>(), "test");
}
}
// test get_parameter with undeclared not allowed
TEST_F(TestNode, get_parameter_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
// version that throws on undeclared
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 42);
// version that returns bool and never throws, and stores in rclcpp::Parameter
{
rclcpp::Parameter parameter;
EXPECT_TRUE(node->get_parameter(name, parameter));
EXPECT_EQ(parameter.get_value<int>(), 42);
}
// version that returns bool and never throws, but is templated to store in a primitive type
{
int value;
EXPECT_TRUE(node->get_parameter(name, value));
EXPECT_EQ(value, 42);
}
}
{
// getting an undeclared parameter throws
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
EXPECT_THROW({node->get_parameter(name);}, rclcpp::exceptions::ParameterNotDeclaredException);
{
rclcpp::Parameter parameter;
EXPECT_FALSE(node->get_parameter(name, parameter));
}
{
int value;
EXPECT_FALSE(node->get_parameter(name, value));
}
}
{
// for templated version, throws if the parameter type doesn't match the requested type
auto name = "parameter"_unq;
node->declare_parameter<std::string>(name, "not an int");
EXPECT_THROW(
{
int value;
node->get_parameter(name, value);
},
rclcpp::exceptions::InvalidParameterTypeException);
}
}
// test get_parameter with undeclared allowed
TEST_F(TestNode, get_parameter_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use (declare first) still works
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
// version that throws on undeclared
EXPECT_EQ(node->get_parameter(name).get_value<int>(), 42);
// version that returns bool and never throws, and stores in rclcpp::Parameter
{
rclcpp::Parameter parameter;
EXPECT_TRUE(node->get_parameter(name, parameter));
EXPECT_EQ(parameter.get_value<int>(), 42);
}
// version that returns bool and never throws, but is templated to store in a primitive type
{
int value;
EXPECT_TRUE(node->get_parameter(name, value));
EXPECT_EQ(value, 42);
}
}
{
// getting an undeclared parameter returns default constructed rclcpp::Parameter or false
auto name = "parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
EXPECT_EQ(node->get_parameter(name).get_type(), rclcpp::PARAMETER_NOT_SET);
{
rclcpp::Parameter parameter;
EXPECT_FALSE(node->get_parameter(name, parameter));
}
{
int value;
EXPECT_FALSE(node->get_parameter(name, value));
}
}
{
// for templated version, return false if the parameter not declared
auto name = "parameter"_unq;
EXPECT_EQ(node->get_parameter(name).get_type(), rclcpp::PARAMETER_NOT_SET);
int value = 42;
EXPECT_FALSE(node->get_parameter<int>(name, value));
EXPECT_EQ(value, 42);
}
}
// test get_parameter_or with undeclared not allowed
TEST_F(TestNode, get_parameter_or_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_or_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use (declare first) still works
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
{
int value;
EXPECT_TRUE(node->get_parameter_or(name, value, 43));
EXPECT_EQ(value, 42);
}
}
{
// normal use, no declare first
auto name = "parameter"_unq;
{
int value;
EXPECT_FALSE(node->get_parameter_or(name, value, 43));
EXPECT_EQ(value, 43);
}
}
}
// test get_parameter_or with undeclared allowed
TEST_F(TestNode, get_parameter_or_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use (declare first) still works
auto name = "parameter"_unq;
node->declare_parameter(name, 42);
EXPECT_TRUE(node->has_parameter(name));
{
int value;
EXPECT_TRUE(node->get_parameter_or(name, value, 43));
EXPECT_EQ(value, 42);
}
}
{
// normal use, no declare first
auto name = "parameter"_unq;
{
int value;
EXPECT_FALSE(node->get_parameter_or(name, value, 43));
EXPECT_EQ(value, 43);
}
}
}
// test get_parameters with undeclared not allowed
TEST_F(TestNode, get_parameters_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameters_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto base_name1 = "parameter"_unq;
auto name1 = "ints." + base_name1;
auto base_name2 = "parameter"_unq;
auto name2 = "strings." + base_name2;
auto base_name3 = "parameter"_unq;
auto name3 = "ints." + base_name3;
node->declare_parameter(name1, 42);
node->declare_parameter<std::string>(name2, "test");
node->declare_parameter(name3, 100);
EXPECT_TRUE(node->has_parameter(name1));
EXPECT_TRUE(node->has_parameter(name2));
EXPECT_TRUE(node->has_parameter(name3));
// non-templated version, get all
{
std::vector<rclcpp::Parameter> expected = {
{name1, 42},
{name2, "test"},
{name3, 100},
};
EXPECT_EQ(node->get_parameters({name1, name2, name3}), expected);
}
// non-templated version, get some
{
std::vector<rclcpp::Parameter> expected = {
{name1, 42},
{name3, 100},
};
EXPECT_EQ(node->get_parameters({name1, name3}), expected);
}
// non-templated version, get some, different types
{
std::vector<rclcpp::Parameter> expected = {
{name1, 42},
{name2, "test"},
};
EXPECT_EQ(node->get_parameters({name1, name2}), expected);
}
// non-templated version, get some, wrong order (request order preserved)
{
std::vector<rclcpp::Parameter> expected = {
{name3, 100},
{name1, 42},
};
EXPECT_EQ(node->get_parameters({name3, name1}), expected);
}
// templated version, get all int's
{
std::map<std::string, int64_t> expected = {
{base_name1, 42},
{base_name3, 100},
};
std::map<std::string, int64_t> actual;
EXPECT_TRUE(node->get_parameters("ints", actual));
EXPECT_EQ(actual, expected);
}
// templated version, get the one string
{
std::map<std::string, std::string> expected = {
{base_name2, "test"},
};
std::map<std::string, std::string> actual;
EXPECT_TRUE(node->get_parameters("strings", actual));
EXPECT_EQ(actual, expected);
}
}
{
// getting an undeclared parameter throws, or in the alternative signature returns false
auto name = "prefix.parameter"_unq;
EXPECT_FALSE(node->has_parameter(name));
EXPECT_THROW(
{node->get_parameters({name});},
rclcpp::exceptions::ParameterNotDeclaredException);
{
std::map<std::string, int64_t> values;
EXPECT_TRUE(values.empty());
EXPECT_FALSE(node->get_parameters("prefix", values));
EXPECT_TRUE(values.empty());
}
}
{
// templated version with empty prefix will get all parameters
auto node_local = std::make_shared<rclcpp::Node>("test_get_parameters_node"_unq);
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node_local->declare_parameter("prefix." + name1, 42);
node_local->declare_parameter("prefix." + name2, 100);
{
std::map<std::string, int64_t> actual;
EXPECT_TRUE(node_local->get_parameters("prefix", actual));
EXPECT_NE(actual.find(name1), actual.end());
EXPECT_NE(actual.find(name2), actual.end());
}
// will throw if set of parameters is non-homogeneous
auto name3 = "prefix1.parameter"_unq;
node_local->declare_parameter<std::string>("prefix." + name3, "not an int");
{
std::map<std::string, int64_t> actual;
EXPECT_THROW(
{
node_local->get_parameters("prefix", actual);
},
rclcpp::exceptions::InvalidParameterTypeException);
}
}
}
// test get_parameters with undeclared allowed
TEST_F(TestNode, get_parameters_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameters_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use
auto base_name1 = "parameter"_unq;
auto name1 = "ints." + base_name1;
auto base_name2 = "parameter"_unq;
auto name2 = "strings." + base_name2;
auto base_name3 = "parameter"_unq;
auto name3 = "ints." + base_name3;
EXPECT_FALSE(node->has_parameter(name1));
EXPECT_FALSE(node->has_parameter(name2));
EXPECT_FALSE(node->has_parameter(name3));
{
// non-templated version, get all, none set, no throw
std::vector<rclcpp::Parameter> expected = {
{name1, {}},
{name2, {}},
{name3, {}},
};
EXPECT_EQ(node->get_parameters({name1, name2, name3}), expected);
}
{
// templated version, get all int's, none set, no throw
std::map<std::string, int64_t> actual;
EXPECT_FALSE(node->get_parameters("ints", actual));
EXPECT_TRUE(actual.empty());
}
{
// templated version, get the one string, none set, no throw
std::map<std::string, std::string> actual;
EXPECT_FALSE(node->get_parameters("strings", actual));
EXPECT_TRUE(actual.empty());
}
}
}
// test describe parameter with undeclared not allowed
TEST_F(TestNode, describe_parameter_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
{
auto result = node->describe_parameter(name1);
EXPECT_EQ(result.name, name1);
EXPECT_EQ(result.type, rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_FALSE(result.read_only);
}
{
auto result = node->describe_parameter(name2);
EXPECT_EQ(result.name, name2);
EXPECT_EQ(result.type, rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_TRUE(result.read_only);
}
}
{
// non-existent parameter throws
auto name = "parameter"_unq;
{
EXPECT_THROW(
{
node->describe_parameter(name);
}, rclcpp::exceptions::ParameterNotDeclaredException);
}
}
}
// test describe parameter with undeclared allowed
TEST_F(TestNode, describe_parameter_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use still works
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
{
auto result = node->describe_parameter(name1);
EXPECT_EQ(result.name, name1);
EXPECT_EQ(result.type, rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_FALSE(result.read_only);
}
{
auto result = node->describe_parameter(name2);
EXPECT_EQ(result.name, name2);
EXPECT_EQ(result.type, rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_TRUE(result.read_only);
}
}
{
// non-existent parameter does not throw, but returns default constructed one
auto name = "parameter"_unq;
{
auto result = node->describe_parameter(name);
rcl_interfaces::msg::ParameterDescriptor expected;
expected.name = name;
EXPECT_EQ(result, expected);
}
}
}
// test describe parameters with undeclared not allowed
TEST_F(TestNode, describe_parameters_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameters_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
auto results = node->describe_parameters({name1, name2});
EXPECT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].name, name1);
EXPECT_EQ(results[0].type, rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_FALSE(results[0].read_only);
EXPECT_EQ(results[1].name, name2);
EXPECT_EQ(results[1].type, rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_TRUE(results[1].read_only);
}
{
// non-existent parameter throws
auto name = "parameter"_unq;
{
EXPECT_THROW(
{
node->describe_parameters({name});
}, rclcpp::exceptions::ParameterNotDeclaredException);
}
}
{
// non-existent parameter throws, even with existing parameters in the list requested
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
{
EXPECT_THROW(
{
node->describe_parameters({name1, name2});
}, rclcpp::exceptions::ParameterNotDeclaredException);
}
}
{
// check that repeated names in input work, and that output is stable (same order as input)
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
auto results = node->describe_parameters({name2, name1, name2});
EXPECT_EQ(results.size(), 3u);
EXPECT_EQ(results[0].name, name2);
EXPECT_EQ(results[0].type, rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_TRUE(results[0].read_only);
EXPECT_EQ(results[1].name, name1);
EXPECT_EQ(results[1].type, rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_FALSE(results[1].read_only);
EXPECT_EQ(results[2].name, name2);
EXPECT_EQ(results[2].type, rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_TRUE(results[2].read_only);
}
}
// test describe parameters with undeclared allowed
TEST_F(TestNode, describe_parameters_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameters_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use still works (declare first)
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
auto results = node->describe_parameters({name1, name2});
EXPECT_EQ(results.size(), 2u);
EXPECT_EQ(results[0].name, name1);
EXPECT_EQ(results[0].type, rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_FALSE(results[0].read_only);
EXPECT_EQ(results[1].name, name2);
EXPECT_EQ(results[1].type, rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_TRUE(results[1].read_only);
}
{
// non-existent parameter does not throw
auto name = "parameter"_unq;
auto results = node->describe_parameters({name});
EXPECT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].name, name);
EXPECT_EQ(results[0].type, rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
EXPECT_FALSE(results[0].read_only);
}
{
// check that repeated names in input work, and that output is stable (same order as input)
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto results = node->describe_parameters({name2, name1, name2});
EXPECT_EQ(results.size(), 3u);
EXPECT_EQ(results[0].name, name2);
EXPECT_EQ(results[0].type, rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
EXPECT_FALSE(results[0].read_only);
EXPECT_EQ(results[1].name, name1);
EXPECT_EQ(results[1].type, rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
EXPECT_FALSE(results[1].read_only);
EXPECT_EQ(results[2].name, name2);
EXPECT_EQ(results[2].type, rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
EXPECT_FALSE(results[2].read_only);
}
}
// test get parameter types with undeclared not allowed
TEST_F(TestNode, get_parameter_types_undeclared_parameters_not_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_types_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(false));
{
// normal use
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
auto results = node->get_parameter_types({name1, name2});
EXPECT_EQ(results.size(), 2u);
EXPECT_EQ(results[0], rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(results[1], rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
}
{
// non-existent parameter throws
auto name = "parameter"_unq;
{
EXPECT_THROW(
{
node->get_parameter_types({name});
}, rclcpp::exceptions::ParameterNotDeclaredException);
}
}
{
// check that repeated names in input work, and that output is stable (same order as input)
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
auto results = node->get_parameter_types({name2, name1, name2});
EXPECT_EQ(results.size(), 3u);
EXPECT_EQ(results[0], rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
EXPECT_EQ(results[1], rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(results[2], rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
}
}
// test get parameter types with undeclared allowed
TEST_F(TestNode, get_parameter_types_undeclared_parameters_allowed) {
auto node = std::make_shared<rclcpp::Node>(
"test_get_parameter_types_node"_unq,
rclcpp::NodeOptions().allow_undeclared_parameters(true));
{
// normal use still works (declare first)
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
node->declare_parameter(name1, 42);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.read_only = true;
node->declare_parameter<std::string>(name2, "test", descriptor);
auto results = node->get_parameter_types({name1, name2});
EXPECT_EQ(results.size(), 2u);
EXPECT_EQ(results[0], rcl_interfaces::msg::ParameterType::PARAMETER_INTEGER);
EXPECT_EQ(results[1], rcl_interfaces::msg::ParameterType::PARAMETER_STRING);
}
{
// non-existent parameter does not throw
auto name = "parameter"_unq;
auto results = node->get_parameter_types({name});
EXPECT_EQ(results.size(), 1u);
EXPECT_EQ(results[0], rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
}
{
// check that repeated names in input work, and that output is stable (same order as input)
auto name1 = "parameter"_unq;
auto name2 = "parameter"_unq;
auto results = node->get_parameter_types({name2, name1, name2});
EXPECT_EQ(results.size(), 3u);
EXPECT_EQ(results[0], rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
EXPECT_EQ(results[1], rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
EXPECT_EQ(results[2], rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
}
}
void expect_qos_profile_eq(
const rmw_qos_profile_t & qos1, const rmw_qos_profile_t & qos2, bool is_publisher)
{
// Depth and history are skipped because they are not retrieved.
EXPECT_EQ(qos1.reliability, qos2.reliability);
EXPECT_EQ(qos1.durability, qos2.durability);
EXPECT_EQ(qos1.deadline.sec, qos2.deadline.sec);
EXPECT_EQ(qos1.deadline.nsec, qos2.deadline.nsec);
if (is_publisher) {
EXPECT_EQ(qos1.lifespan.sec, qos2.lifespan.sec);
EXPECT_EQ(qos1.lifespan.nsec, qos2.lifespan.nsec);
}
EXPECT_EQ(qos1.liveliness, qos2.liveliness);
EXPECT_EQ(qos1.liveliness_lease_duration.sec, qos2.liveliness_lease_duration.sec);
EXPECT_EQ(qos1.liveliness_lease_duration.nsec, qos2.liveliness_lease_duration.nsec);
}
// test that calling get_publishers_info_by_topic and get_subscriptions_info_by_topic
TEST_F(TestNode, get_publishers_subscriptions_info_by_topic) {
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
std::string topic_name = "test_topic_info";
std::string fq_topic_name = rclcpp::expand_topic_or_service_name(
topic_name, node->get_name(), node->get_namespace());
// Lists should be empty
EXPECT_TRUE(node->get_publishers_info_by_topic(fq_topic_name).empty());
EXPECT_TRUE(node->get_subscriptions_info_by_topic(fq_topic_name).empty());
// Add a publisher
rclcpp::QoSInitialization qos_initialization =
{
RMW_QOS_POLICY_HISTORY_KEEP_ALL,
10
};
rmw_qos_profile_t rmw_qos_profile_default =
{
RMW_QOS_POLICY_HISTORY_KEEP_ALL,
10,
RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT,
RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL,
{1, 12345},
{20, 9887665},
RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC,
{5, 23456},
false
};
rclcpp::QoS qos = rclcpp::QoS(qos_initialization, rmw_qos_profile_default);
auto publisher = node->create_publisher<test_msgs::msg::BasicTypes>(topic_name, qos);
// List should have one item
auto publisher_list = node->get_publishers_info_by_topic(fq_topic_name);
ASSERT_EQ(publisher_list.size(), (size_t)1);
// Subscription list should be empty
EXPECT_TRUE(node->get_subscriptions_info_by_topic(fq_topic_name).empty());
// Verify publisher list has the right data.
EXPECT_EQ(node->get_name(), publisher_list[0].node_name());
EXPECT_EQ(node->get_namespace(), publisher_list[0].node_namespace());
EXPECT_EQ("test_msgs/msg/BasicTypes", publisher_list[0].topic_type());
EXPECT_EQ(rclcpp::EndpointType::Publisher, publisher_list[0].endpoint_type());
auto actual_qos_profile = publisher_list[0].qos_profile().get_rmw_qos_profile();
{
SCOPED_TRACE("Publisher QOS 1");
expect_qos_profile_eq(qos.get_rmw_qos_profile(), actual_qos_profile, true);
}
// Add a subscription
rclcpp::QoSInitialization qos_initialization2 =
{
RMW_QOS_POLICY_HISTORY_KEEP_LAST,
0
};
rmw_qos_profile_t rmw_qos_profile_default2 =
{
RMW_QOS_POLICY_HISTORY_KEEP_LAST,
0,
RMW_QOS_POLICY_RELIABILITY_RELIABLE,
RMW_QOS_POLICY_DURABILITY_VOLATILE,
{15, 1678},
{29, 2345},
RMW_QOS_POLICY_LIVELINESS_AUTOMATIC,
{5, 23456},
false
};
rclcpp::QoS qos2 = rclcpp::QoS(qos_initialization2, rmw_qos_profile_default2);
auto callback = [](test_msgs::msg::BasicTypes::ConstSharedPtr msg) {
(void)msg;
};
auto subscriber =
node->create_subscription<test_msgs::msg::BasicTypes>(topic_name, qos2, callback);
// Both lists should have one item
publisher_list = node->get_publishers_info_by_topic(fq_topic_name);
auto subscription_list = node->get_subscriptions_info_by_topic(fq_topic_name);
EXPECT_EQ(publisher_list.size(), (size_t)1);
EXPECT_EQ(subscription_list.size(), (size_t)1);
// Verify publisher and subscription list has the right data.
EXPECT_EQ(node->get_name(), publisher_list[0].node_name());
EXPECT_EQ(node->get_namespace(), publisher_list[0].node_namespace());
EXPECT_EQ("test_msgs/msg/BasicTypes", publisher_list[0].topic_type());
EXPECT_EQ(rclcpp::EndpointType::Publisher, publisher_list[0].endpoint_type());
auto publisher_qos_profile = publisher_list[0].qos_profile().get_rmw_qos_profile();
{
SCOPED_TRACE("Publisher QOS 2");
expect_qos_profile_eq(qos.get_rmw_qos_profile(), publisher_qos_profile, true);
}
EXPECT_EQ(node->get_name(), subscription_list[0].node_name());
EXPECT_EQ(node->get_namespace(), subscription_list[0].node_namespace());
EXPECT_EQ("test_msgs/msg/BasicTypes", subscription_list[0].topic_type());
EXPECT_EQ(rclcpp::EndpointType::Subscription, subscription_list[0].endpoint_type());
auto subscription_qos_profile = subscription_list[0].qos_profile().get_rmw_qos_profile();
{
SCOPED_TRACE("Subscription QOS");
expect_qos_profile_eq(qos2.get_rmw_qos_profile(), subscription_qos_profile, false);
}
// Error cases
EXPECT_THROW(
{
publisher_list = node->get_publishers_info_by_topic("13");
}, rclcpp::exceptions::InvalidTopicNameError);
EXPECT_THROW(
{
subscription_list = node->get_subscriptions_info_by_topic("13");
}, rclcpp::exceptions::InvalidTopicNameError);
}
TEST_F(TestNode, callback_groups) {
auto node = std::make_shared<rclcpp::Node>("node", "ns");
size_t num_callback_groups_in_basic_node = node->get_callback_groups().size();
auto group1 = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
EXPECT_EQ(1u + num_callback_groups_in_basic_node, node->get_callback_groups().size());
auto group2 = node->create_callback_group(rclcpp::CallbackGroupType::Reentrant);
EXPECT_EQ(2u + num_callback_groups_in_basic_node, node->get_callback_groups().size());
}
// This is tested more thoroughly in node_interfaces/test_node_graph
TEST_F(TestNode, get_entity_names) {
auto node = std::make_shared<rclcpp::Node>("node", "ns");
const auto node_names = node->get_node_names();
EXPECT_NE(
node_names.end(),
std::find(node_names.begin(), node_names.end(), node->get_fully_qualified_name()));
const auto topic_names_and_types = node->get_topic_names_and_types();
EXPECT_EQ(topic_names_and_types.end(), topic_names_and_types.find("topic"));
EXPECT_EQ(0u, node->count_publishers("topic"));
EXPECT_EQ(0u, node->count_subscribers("topic"));
const auto service_names_and_types = node->get_service_names_and_types();
EXPECT_EQ(service_names_and_types.end(), service_names_and_types.find("service"));
const auto service_names_and_types_by_node =
node->get_service_names_and_types_by_node("node", "/ns");
EXPECT_EQ(
service_names_and_types_by_node.end(),
service_names_and_types_by_node.find("service"));
}
TEST_F(TestNode, wait_for_graph_event) {
// Even though this node is only used in the std::thread below, it's here to ensure there is no
// race condition in its destruction and modification of the node_graph
auto node = std::make_shared<rclcpp::Node>("node", "ns");
constexpr std::chrono::seconds timeout(10);
auto thread_start = std::chrono::steady_clock::now();
auto thread_completion = thread_start;
// This runs until the graph is updated
std::thread graph_event_wait_thread([&thread_completion, node, timeout]() {
auto event = node->get_graph_event();
EXPECT_NO_THROW(node->wait_for_graph_change(event, timeout));
thread_completion = std::chrono::steady_clock::now();
});
// Start creating nodes until at least one event triggers in graph_event_wait_thread or until 100
// nodes have been created (at which point this is a failure)
std::vector<std::shared_ptr<rclcpp::Node>> nodes;
while (thread_completion == thread_start && nodes.size() < 100) {
nodes.emplace_back(std::make_shared<rclcpp::Node>("node"_unq, "ns"));
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
graph_event_wait_thread.join();
// Nodes will probably only be of size 1
EXPECT_LT(0u, nodes.size());
EXPECT_GT(100u, nodes.size());
EXPECT_NE(thread_start, thread_completion);
EXPECT_GT(timeout, thread_completion - thread_start);
}
TEST_F(TestNode, create_sub_node_rmw_validate_namespace_error) {
auto node = std::make_shared<rclcpp::Node>("node", "ns");
{
auto mock = mocking_utils::patch_and_return(
"lib:rclcpp", rmw_validate_namespace, RMW_RET_INVALID_ARGUMENT);
// reset() is not necessary for this exception, but it handles unused return value warning
EXPECT_THROW(
node->create_sub_node("ns").reset(),
rclcpp::exceptions::RCLInvalidArgument);
}
{
auto mock = mocking_utils::patch_and_return(
"lib:rclcpp", rmw_validate_namespace, RMW_RET_ERROR);
EXPECT_THROW(
node->create_sub_node("ns").reset(),
rclcpp::exceptions::RCLError);
}
}
TEST_F(TestNode, static_and_dynamic_typing) {
rclcpp::NodeOptions no;
no.parameter_overrides(
{
{"integer_parameter_override_ok", 43},
{"string_parameter_override_should_throw", 43},
{"integer_must_provide_override", 43},
{"cool_way_of_declaring_a_string_without_a_default", "hello!"}
});
auto node = std::make_shared<rclcpp::Node>("node", "ns", no);
{
auto param = node->declare_parameter("an_int", 42);
EXPECT_EQ(42, param);
auto result = node->set_parameter({"an_int", "string value"});
EXPECT_FALSE(result.successful);
result = node->set_parameter({"an_int", 43});
EXPECT_TRUE(result.successful);
EXPECT_TRUE(node->get_parameter("an_int", param));
EXPECT_EQ(43, param);
}
{
auto param = node->declare_parameter("integer_parameter_override_ok", 42);
EXPECT_EQ(43, param);
}
{
EXPECT_THROW(
node->declare_parameter("string_parameter_override_should_throw", "a string"),
rclcpp::exceptions::InvalidParameterTypeException);
}
{
auto param = node->declare_parameter(
"integer_must_provide_override", rclcpp::PARAMETER_INTEGER);
EXPECT_EQ(43, param.get<int64_t>());
}
{
auto param = node->declare_parameter<std::string>(
"cool_way_of_declaring_a_string_without_a_default");
EXPECT_EQ("hello!", param);
}
{
EXPECT_THROW(
node->declare_parameter("integer_override_not_given", rclcpp::PARAMETER_INTEGER),
rclcpp::exceptions::NoParameterOverrideProvided);
}
{
EXPECT_THROW(
node->declare_parameter("parameter_not_set_is_not_a_valid_type", rclcpp::PARAMETER_NOT_SET),
std::invalid_argument);
}
{
EXPECT_THROW(
node->declare_parameter(
"uninitialized_not_valid_except_dynamic_typing", rclcpp::ParameterValue{}),
rclcpp::exceptions::InvalidParameterTypeException);
}
{
#ifndef _WIN32
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#else
# pragma warning(push)
# pragma warning(disable: 4996)
#endif
auto param = node->declare_parameter("deprecated_way_dynamic_typing");
EXPECT_EQ(param, rclcpp::ParameterValue{});
#ifndef _WIN32
# pragma GCC diagnostic pop
#else
# pragma warning(pop)
#endif
}
}