Skip to content

Commit 21ff57b

Browse files
authored
Add function for getting clients by node (#459)
* Add graph test for service clients There were tests for publishers, subscriptions, and services, but not clients. Signed-off-by: Jacob Perron <[email protected]> * Add function for getting clients by node Signed-off-by: Jacob Perron <[email protected]> * Update service client graph test Signed-off-by: Jacob Perron <[email protected]> * Fix doc sentence Signed-off-by: Jacob Perron <[email protected]> * Update docs Signed-off-by: Jacob Perron <[email protected]>
1 parent ad1c4f1 commit 21ff57b

File tree

3 files changed

+193
-10
lines changed

3 files changed

+193
-10
lines changed

rcl/include/rcl/graph.h

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ rcl_get_subscriber_names_and_types_by_node(
135135
const char * node_namespace,
136136
rcl_names_and_types_t * topic_names_and_types);
137137

138-
/// Return a list of service names and types for associated with a node.
138+
/// Return a list of service names and types associated with a node.
139139
/**
140140
* The `node` parameter must point to a valid node.
141141
*
@@ -148,7 +148,7 @@ rcl_get_subscriber_names_and_types_by_node(
148148
* \see rcl_get_publisher_names_and_types_by_node for details on the `no_demangle` parameter.
149149
*
150150
* The returned names are not automatically remapped by this function.
151-
* Attempting to create clients or services using names returned by this function may not
151+
* Attempting to create service clients using names returned by this function may not
152152
* result in the desired service name being used depending on the remap rules in use.
153153
*
154154
* <hr>
@@ -180,6 +180,51 @@ rcl_get_service_names_and_types_by_node(
180180
const char * node_namespace,
181181
rcl_names_and_types_t * service_names_and_types);
182182

183+
/// Return a list of service client names and types associated with a node.
184+
/**
185+
* The `node` parameter must point to a valid node.
186+
*
187+
* The `service_names_and_types` parameter must be allocated and zero initialized.
188+
* This function allocates memory for the returned list of names and types and so it is the
189+
* callers responsibility to pass `service_names_and_types` to rcl_names_and_types_fini()
190+
* when it is no longer needed.
191+
* Failing to do so will result in leaked memory.
192+
*
193+
* \see rcl_get_publisher_names_and_types_by_node for details on the `no_demangle` parameter.
194+
*
195+
* The returned names are not automatically remapped by this function.
196+
* Attempting to create service servers using names returned by this function may not
197+
* result in the desired service name being used depending on the remap rules in use.
198+
*
199+
* <hr>
200+
* Attribute | Adherence
201+
* ------------------ | -------------
202+
* Allocates Memory | Yes
203+
* Thread-Safe | No
204+
* Uses Atomics | No
205+
* Lock-Free | Maybe [1]
206+
* <i>[1] implementation may need to protect the data structure with a lock</i>
207+
*
208+
* \param[in] node the handle to the node being used to query the ROS graph
209+
* \param[in] allocator allocator to be used when allocating space for strings
210+
* \param[in] node_name the node name of the services to return
211+
* \param[in] node_namespace the node namespace of the services to return
212+
* \param[out] service_names_and_types list of service client names and their types
213+
* \return `RCL_RET_OK` if the query was successful, or
214+
* \return `RCL_RET_NODE_INVALID` if the node is invalid, or
215+
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
216+
* \return `RCL_RET_ERROR` if an unspecified error occurs.
217+
*/
218+
RCL_PUBLIC
219+
RCL_WARN_UNUSED
220+
rcl_ret_t
221+
rcl_get_client_names_and_types_by_node(
222+
const rcl_node_t * node,
223+
rcl_allocator_t * allocator,
224+
const char * node_name,
225+
const char * node_namespace,
226+
rcl_names_and_types_t * service_names_and_types);
227+
183228
/// Return a list of topic names and their types.
184229
/**
185230
* The `node` parameter must point to a valid node.

rcl/src/rcl/graph.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,42 @@ rcl_get_service_names_and_types_by_node(
142142
return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
143143
}
144144

145+
rcl_ret_t
146+
rcl_get_client_names_and_types_by_node(
147+
const rcl_node_t * node,
148+
rcl_allocator_t * allocator,
149+
const char * node_name,
150+
const char * node_namespace,
151+
rcl_names_and_types_t * service_names_and_types)
152+
{
153+
if (!rcl_node_is_valid(node)) {
154+
return RCL_RET_NODE_INVALID;
155+
}
156+
RCL_CHECK_ALLOCATOR_WITH_MSG(allocator, "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
157+
RCL_CHECK_ARGUMENT_FOR_NULL(node_name, RCL_RET_INVALID_ARGUMENT);
158+
RCL_CHECK_ARGUMENT_FOR_NULL(node_namespace, RCL_RET_INVALID_ARGUMENT);
159+
RCL_CHECK_ARGUMENT_FOR_NULL(service_names_and_types, RCL_RET_INVALID_ARGUMENT);
160+
161+
const char * valid_namespace = "/";
162+
if (strlen(node_namespace) > 0) {
163+
valid_namespace = node_namespace;
164+
}
165+
rmw_ret_t rmw_ret;
166+
rmw_ret = rmw_names_and_types_check_zero(service_names_and_types);
167+
if (rmw_ret != RMW_RET_OK) {
168+
return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
169+
}
170+
rcutils_allocator_t rcutils_allocator = *allocator;
171+
rmw_ret = rmw_get_client_names_and_types_by_node(
172+
rcl_node_get_rmw_handle(node),
173+
&rcutils_allocator,
174+
node_name,
175+
valid_namespace,
176+
service_names_and_types
177+
);
178+
return rcl_convert_rmw_ret_to_rcl_ret(rmw_ret);
179+
}
180+
145181
rcl_ret_t
146182
rcl_get_topic_names_and_types(
147183
const rcl_node_t * node,

rcl/test/rcl/test_graph.cpp

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,78 @@ TEST_F(
516516
rcl_reset_error();
517517
}
518518

519+
/* Test the rcl_get_client_names_and_types_by_node function.
520+
*
521+
* This does not test content of the response.
522+
*/
523+
TEST_F(
524+
CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION),
525+
test_rcl_get_client_names_and_types_by_node
526+
) {
527+
rcl_ret_t ret;
528+
rcl_allocator_t allocator = rcl_get_default_allocator();
529+
rcl_allocator_t zero_allocator = static_cast<rcl_allocator_t>(
530+
rcutils_get_zero_initialized_allocator());
531+
rcl_node_t zero_node = rcl_get_zero_initialized_node();
532+
const char * unknown_node_name = "/test_rcl_get_client_names_and_types_by_node";
533+
rcl_names_and_types_t nat = rcl_get_zero_initialized_names_and_types();
534+
// invalid node
535+
ret = rcl_get_client_names_and_types_by_node(
536+
nullptr, &allocator, this->test_graph_node_name, "", &nat);
537+
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string().str;
538+
rcl_reset_error();
539+
ret = rcl_get_client_names_and_types_by_node(
540+
&zero_node, &allocator, this->test_graph_node_name, "", &nat);
541+
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string().str;
542+
rcl_reset_error();
543+
ret = rcl_get_client_names_and_types_by_node(
544+
this->old_node_ptr, &allocator, this->test_graph_node_name, "", &nat);
545+
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string().str;
546+
rcl_reset_error();
547+
// invalid allocator
548+
ret = rcl_get_client_names_and_types_by_node(
549+
this->node_ptr, nullptr, this->test_graph_node_name, "", &nat);
550+
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
551+
rcl_reset_error();
552+
ret = rcl_get_client_names_and_types_by_node(
553+
this->node_ptr, &zero_allocator, this->test_graph_node_name, "", &nat);
554+
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
555+
rcl_reset_error();
556+
// invalid names
557+
ret = rcl_get_client_names_and_types_by_node(
558+
this->node_ptr, &allocator, nullptr, "", &nat);
559+
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
560+
rcl_reset_error();
561+
ret = rcl_get_client_names_and_types_by_node(
562+
this->node_ptr, &allocator, this->test_graph_node_name, nullptr, &nat);
563+
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
564+
rcl_reset_error();
565+
// test valid strings with invalid node names
566+
ret = rcl_get_client_names_and_types_by_node(
567+
this->node_ptr, &allocator, "", "", &nat);
568+
EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
569+
rcl_reset_error();
570+
ret = rcl_get_client_names_and_types_by_node(
571+
this->node_ptr, &allocator, "_InvalidNodeName", "", &nat);
572+
EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
573+
rcl_reset_error();
574+
// invalid names and types
575+
ret = rcl_get_client_names_and_types_by_node(
576+
this->node_ptr, &allocator, this->test_graph_node_name, "", nullptr);
577+
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
578+
rcl_reset_error();
579+
// unknown node name
580+
ret = rcl_get_client_names_and_types_by_node(
581+
this->node_ptr, &allocator, unknown_node_name, "", &nat);
582+
EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
583+
rcl_reset_error();
584+
// valid call
585+
ret = rcl_get_client_names_and_types_by_node(
586+
this->node_ptr, &allocator, this->test_graph_node_name, "", &nat);
587+
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
588+
rcl_reset_error();
589+
}
590+
519591
/* Test the rcl_count_publishers function.
520592
*
521593
* This does not test content of the response.
@@ -728,6 +800,7 @@ struct expected_node_state
728800
size_t publishers;
729801
size_t subscribers;
730802
size_t services;
803+
size_t clients;
731804
};
732805

733806
/**
@@ -740,7 +813,7 @@ class NodeGraphMultiNodeFixture : public CLASSNAME(TestGraphFixture, RMW_IMPLEME
740813
std::string topic_name = "/test_node_info_functions__";
741814
rcl_node_t * remote_node_ptr;
742815
rcl_allocator_t allocator = rcl_get_default_allocator();
743-
GetTopicsFunc sub_func, pub_func, service_func;
816+
GetTopicsFunc sub_func, pub_func, service_func, client_func;
744817
rcl_context_t * remote_context_ptr;
745818

746819
void SetUp() override
@@ -787,6 +860,12 @@ class NodeGraphMultiNodeFixture : public CLASSNAME(TestGraphFixture, RMW_IMPLEME
787860
std::placeholders::_2,
788861
"/",
789862
std::placeholders::_3);
863+
client_func = std::bind(rcl_get_client_names_and_types_by_node,
864+
std::placeholders::_1,
865+
&this->allocator,
866+
std::placeholders::_2,
867+
"/",
868+
std::placeholders::_3);
790869
WaitForAllNodesAlive();
791870
}
792871

@@ -858,6 +937,9 @@ class NodeGraphMultiNodeFixture : public CLASSNAME(TestGraphFixture, RMW_IMPLEME
858937
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Checking services from node");
859938
expect_topics_types(node, service_func, node_state.services,
860939
test_graph_node_name, is_expect, is_success);
940+
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Checking clients from node");
941+
expect_topics_types(node, client_func, node_state.clients,
942+
test_graph_node_name, is_expect, is_success);
861943
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Checking publishers from node");
862944
expect_topics_types(node, pub_func, node_state.publishers,
863945
test_graph_node_name, is_expect, is_success);
@@ -871,6 +953,9 @@ class NodeGraphMultiNodeFixture : public CLASSNAME(TestGraphFixture, RMW_IMPLEME
871953
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Checking services from remote node");
872954
expect_topics_types(node, service_func, remote_node_state.services,
873955
this->remote_node_name, is_expect, is_success);
956+
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Checking clients from remote node");
957+
expect_topics_types(node, client_func, remote_node_state.clients,
958+
this->remote_node_name, is_expect, is_success);
874959
if (!is_success) {
875960
ret = rcl_wait_set_clear(wait_set_ptr);
876961
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
@@ -917,19 +1002,19 @@ TEST_F(NodeGraphMultiNodeFixture, test_node_info_subscriptions)
9171002
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
9181003
rcl_reset_error();
9191004

920-
VerifySubsystemCount(expected_node_state{1, 1, 0}, expected_node_state{1, 1, 0});
1005+
VerifySubsystemCount(expected_node_state{1, 1, 0, 0}, expected_node_state{1, 1, 0, 0});
9211006

9221007
// Destroy the node's subscriber
9231008
ret = rcl_subscription_fini(&sub, this->node_ptr);
9241009
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
9251010
rcl_reset_error();
926-
VerifySubsystemCount(expected_node_state{1, 0, 0}, expected_node_state{1, 1, 0});
1011+
VerifySubsystemCount(expected_node_state{1, 0, 0, 0}, expected_node_state{1, 1, 0, 0});
9271012

9281013
// Destroy the remote node's subdscriber
9291014
ret = rcl_subscription_fini(&sub2, this->remote_node_ptr);
9301015
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
9311016
rcl_reset_error();
932-
VerifySubsystemCount(expected_node_state{1, 0, 0}, expected_node_state{1, 0, 0});
1017+
VerifySubsystemCount(expected_node_state{1, 0, 0, 0}, expected_node_state{1, 0, 0, 0});
9331018
}
9341019

9351020
TEST_F(NodeGraphMultiNodeFixture, test_node_info_publishers)
@@ -942,14 +1027,14 @@ TEST_F(NodeGraphMultiNodeFixture, test_node_info_publishers)
9421027
ret = rcl_publisher_init(&pub, this->node_ptr, ts, this->topic_name.c_str(), &pub_ops);
9431028
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
9441029
rcl_reset_error();
945-
VerifySubsystemCount(expected_node_state{2, 0, 0}, expected_node_state{1, 0, 0});
1030+
VerifySubsystemCount(expected_node_state{2, 0, 0, 0}, expected_node_state{1, 0, 0, 0});
9461031

9471032
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Destroyed publisher");
9481033
// Destroy the publisher.
9491034
ret = rcl_publisher_fini(&pub, this->node_ptr);
9501035
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
9511036
rcl_reset_error();
952-
VerifySubsystemCount(expected_node_state{1, 0, 0}, expected_node_state{1, 0, 0});
1037+
VerifySubsystemCount(expected_node_state{1, 0, 0, 0}, expected_node_state{1, 0, 0, 0});
9531038
}
9541039

9551040
TEST_F(NodeGraphMultiNodeFixture, test_node_info_services)
@@ -961,12 +1046,29 @@ TEST_F(NodeGraphMultiNodeFixture, test_node_info_services)
9611046
auto ts1 = ROSIDL_GET_SRV_TYPE_SUPPORT(test_msgs, srv, BasicTypes);
9621047
ret = rcl_service_init(&service, this->node_ptr, ts1, service_name, &service_options);
9631048
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
964-
VerifySubsystemCount(expected_node_state{1, 0, 1}, expected_node_state{1, 0, 0});
1049+
VerifySubsystemCount(expected_node_state{1, 0, 1, 0}, expected_node_state{1, 0, 0, 0});
9651050

9661051
// Destroy service.
9671052
ret = rcl_service_fini(&service, this->node_ptr);
9681053
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
969-
VerifySubsystemCount(expected_node_state{1, 0, 0}, expected_node_state{1, 0, 0});
1054+
VerifySubsystemCount(expected_node_state{1, 0, 0, 0}, expected_node_state{1, 0, 0, 0});
1055+
}
1056+
1057+
TEST_F(NodeGraphMultiNodeFixture, test_node_info_clients)
1058+
{
1059+
rcl_ret_t ret;
1060+
const char * service_name = "test_service";
1061+
rcl_client_t client = rcl_get_zero_initialized_client();
1062+
rcl_client_options_t client_options = rcl_client_get_default_options();
1063+
auto ts = ROSIDL_GET_SRV_TYPE_SUPPORT(test_msgs, srv, BasicTypes);
1064+
ret = rcl_client_init(&client, this->node_ptr, ts, service_name, &client_options);
1065+
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
1066+
VerifySubsystemCount(expected_node_state{1, 0, 0, 1}, expected_node_state{1, 0, 0, 0});
1067+
1068+
// Destroy client
1069+
ret = rcl_client_fini(&client, this->node_ptr);
1070+
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
1071+
VerifySubsystemCount(expected_node_state{1, 0, 0, 0}, expected_node_state{1, 0, 0, 0});
9701072
}
9711073

9721074
/*

0 commit comments

Comments
 (0)