578 lines
23 KiB
C++
578 lines
23 KiB
C++
// Copyright 2016 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.
|
|
|
|
// Disable -Wmissing-field-initializers, as it is overly strict and will be
|
|
// relaxed in future versions of GCC (it is not a warning for clang).
|
|
// See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36750#c13
|
|
#ifndef _WIN32
|
|
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
|
#endif
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <future>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "rcl/rcl.h"
|
|
#include "rcl/graph.h"
|
|
|
|
#include "std_msgs/msg/string.h"
|
|
#include "example_interfaces/srv/add_two_ints.h"
|
|
|
|
#include "../memory_tools/memory_tools.hpp"
|
|
#include "../scope_exit.hpp"
|
|
#include "rcl/error_handling.h"
|
|
|
|
#ifdef RMW_IMPLEMENTATION
|
|
# define CLASSNAME_(NAME, SUFFIX) NAME ## __ ## SUFFIX
|
|
# define CLASSNAME(NAME, SUFFIX) CLASSNAME_(NAME, SUFFIX)
|
|
#else
|
|
# define CLASSNAME(NAME, SUFFIX) NAME
|
|
#endif
|
|
|
|
bool is_connext =
|
|
std::string(rmw_get_implementation_identifier()).find("rmw_connext") == 0;
|
|
|
|
class CLASSNAME (TestGraphFixture, RMW_IMPLEMENTATION) : public ::testing::Test
|
|
{
|
|
public:
|
|
rcl_node_t * old_node_ptr;
|
|
rcl_node_t * node_ptr;
|
|
rcl_wait_set_t * wait_set_ptr;
|
|
void SetUp()
|
|
{
|
|
stop_memory_checking();
|
|
rcl_ret_t ret;
|
|
ret = rcl_init(0, nullptr, rcl_get_default_allocator());
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
this->old_node_ptr = new rcl_node_t;
|
|
*this->old_node_ptr = rcl_get_zero_initialized_node();
|
|
const char * old_name = "old_node_name";
|
|
rcl_node_options_t node_options = rcl_node_get_default_options();
|
|
ret = rcl_node_init(this->old_node_ptr, old_name, "", &node_options);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
ret = rcl_shutdown(); // after this, the old_node_ptr should be invalid
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
|
|
ret = rcl_init(0, nullptr, rcl_get_default_allocator());
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
this->node_ptr = new rcl_node_t;
|
|
*this->node_ptr = rcl_get_zero_initialized_node();
|
|
const char * name = "test_graph_node";
|
|
ret = rcl_node_init(this->node_ptr, name, "", &node_options);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
|
|
this->wait_set_ptr = new rcl_wait_set_t;
|
|
*this->wait_set_ptr = rcl_get_zero_initialized_wait_set();
|
|
ret = rcl_wait_set_init(this->wait_set_ptr, 0, 1, 0, 0, 0, rcl_get_default_allocator());
|
|
|
|
set_on_unexpected_malloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED MALLOC";});
|
|
set_on_unexpected_realloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED REALLOC";});
|
|
set_on_unexpected_free_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED FREE";});
|
|
start_memory_checking();
|
|
}
|
|
|
|
void TearDown()
|
|
{
|
|
assert_no_malloc_end();
|
|
assert_no_realloc_end();
|
|
assert_no_free_end();
|
|
stop_memory_checking();
|
|
set_on_unexpected_malloc_callback(nullptr);
|
|
set_on_unexpected_realloc_callback(nullptr);
|
|
set_on_unexpected_free_callback(nullptr);
|
|
rcl_ret_t ret;
|
|
ret = rcl_node_fini(this->old_node_ptr);
|
|
delete this->old_node_ptr;
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
|
|
ret = rcl_wait_set_fini(this->wait_set_ptr);
|
|
delete this->wait_set_ptr;
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
|
|
ret = rcl_node_fini(this->node_ptr);
|
|
delete this->node_ptr;
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
|
|
ret = rcl_shutdown();
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
}
|
|
};
|
|
|
|
/* Test the rcl_get_topic_names_and_types and rcl_destroy_topic_names_and_types functions.
|
|
*
|
|
* This does not test content of the rcl_topic_names_and_types_t structure.
|
|
*/
|
|
TEST_F(
|
|
CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION),
|
|
test_rcl_get_and_destroy_topic_names_and_types
|
|
) {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret;
|
|
rcl_topic_names_and_types_t tnat {};
|
|
rcl_node_t zero_node = rcl_get_zero_initialized_node();
|
|
// invalid node
|
|
ret = rcl_get_topic_names_and_types(nullptr, rcl_get_default_allocator(), &tnat);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
ret = rcl_get_topic_names_and_types(&zero_node, rcl_get_default_allocator(), &tnat);
|
|
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
ret = rcl_get_topic_names_and_types(this->old_node_ptr, rcl_get_default_allocator(), &tnat);
|
|
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// invalid topic_names_and_types
|
|
ret = rcl_get_topic_names_and_types(this->node_ptr, rcl_get_default_allocator(), nullptr);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// invalid argument to rcl_destroy_topic_names_and_types
|
|
ret = rcl_destroy_topic_names_and_types(nullptr);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// valid calls
|
|
ret = rcl_get_topic_names_and_types(this->node_ptr, rcl_get_default_allocator(), &tnat);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
ret = rcl_destroy_topic_names_and_types(&tnat);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
}
|
|
|
|
/* Test the rcl_count_publishers function.
|
|
*
|
|
* This does not test content the response.
|
|
*/
|
|
TEST_F(
|
|
CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION),
|
|
test_rcl_count_publishers
|
|
) {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret;
|
|
rcl_node_t zero_node = rcl_get_zero_initialized_node();
|
|
const char * topic_name = "/topic_test_rcl_count_publishers";
|
|
size_t count;
|
|
// invalid node
|
|
ret = rcl_count_publishers(nullptr, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
ret = rcl_count_publishers(&zero_node, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
ret = rcl_count_publishers(this->old_node_ptr, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// invalid topic name
|
|
ret = rcl_count_publishers(this->node_ptr, nullptr, &count);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// TODO(wjwwood): test valid strings with invalid topic names in them
|
|
// invalid count
|
|
ret = rcl_count_publishers(this->node_ptr, topic_name, nullptr);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// valid call
|
|
ret = rcl_count_publishers(this->node_ptr, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
}
|
|
|
|
/* Test the rcl_count_subscribers function.
|
|
*
|
|
* This does not test content the response.
|
|
*/
|
|
TEST_F(
|
|
CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION),
|
|
test_rcl_count_subscribers
|
|
) {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret;
|
|
rcl_node_t zero_node = rcl_get_zero_initialized_node();
|
|
const char * topic_name = "/topic_test_rcl_count_subscribers";
|
|
size_t count;
|
|
// invalid node
|
|
ret = rcl_count_subscribers(nullptr, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
ret = rcl_count_subscribers(&zero_node, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
ret = rcl_count_subscribers(this->old_node_ptr, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// invalid topic name
|
|
ret = rcl_count_subscribers(this->node_ptr, nullptr, &count);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// TODO(wjwwood): test valid strings with invalid topic names in them
|
|
// invalid count
|
|
ret = rcl_count_subscribers(this->node_ptr, topic_name, nullptr);
|
|
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// valid call
|
|
ret = rcl_count_subscribers(this->node_ptr, topic_name, &count);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
}
|
|
|
|
void
|
|
check_graph_state(
|
|
const rcl_node_t * node_ptr,
|
|
rcl_wait_set_t * wait_set_ptr,
|
|
const rcl_guard_condition_t * graph_guard_condition,
|
|
std::string & topic_name,
|
|
size_t expected_publisher_count,
|
|
size_t expected_subscriber_count,
|
|
bool expected_in_tnat,
|
|
size_t number_of_tries)
|
|
{
|
|
printf(
|
|
"Expecting %zu publishers, %zu subscribers, and that the topic is%s in the graph.\n",
|
|
expected_publisher_count,
|
|
expected_subscriber_count,
|
|
expected_in_tnat ? "" : " not"
|
|
);
|
|
size_t publisher_count = 0;
|
|
size_t subscriber_count = 0;
|
|
bool is_in_tnat = false;
|
|
rcl_topic_names_and_types_t tnat {};
|
|
rcl_ret_t ret;
|
|
for (size_t i = 0; i < number_of_tries; ++i) {
|
|
ret = rcl_count_publishers(node_ptr, topic_name.c_str(), &publisher_count);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
|
|
ret = rcl_count_subscribers(node_ptr, topic_name.c_str(), &subscriber_count);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
|
|
tnat = rcl_get_zero_initialized_topic_names_and_types();
|
|
ret = rcl_get_topic_names_and_types(node_ptr, rcl_get_default_allocator(), &tnat);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
is_in_tnat = false;
|
|
for (size_t i = 0; RCL_RET_OK == ret && i < tnat.topic_count; ++i) {
|
|
if (topic_name == tnat.topic_names[i]) {
|
|
ASSERT_FALSE(is_in_tnat) << "duplicates in the tnat"; // Found it more than once!
|
|
is_in_tnat = true;
|
|
}
|
|
}
|
|
if (RCL_RET_OK == ret) {
|
|
ret = rcl_destroy_topic_names_and_types(&tnat);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
}
|
|
|
|
printf(
|
|
" Try %zu: %zu publishers, %zu subscribers, and that the topic is%s in the graph.\n",
|
|
i + 1,
|
|
publisher_count,
|
|
subscriber_count,
|
|
is_in_tnat ? "" : " not"
|
|
);
|
|
if (
|
|
expected_publisher_count == publisher_count &&
|
|
expected_subscriber_count == subscriber_count &&
|
|
expected_in_tnat == is_in_tnat)
|
|
{
|
|
printf(" state correct!\n");
|
|
break;
|
|
} else {
|
|
if (expected_publisher_count != publisher_count) {
|
|
printf(" pub count incorrect!\n");
|
|
}
|
|
if (expected_subscriber_count != subscriber_count) {
|
|
printf(" sub count incorrect!\n");
|
|
}
|
|
if (expected_in_tnat != is_in_tnat) {
|
|
printf(" in tnat incorrect!\n");
|
|
}
|
|
}
|
|
// Wait for graph change before trying again.
|
|
if ((i + 1) == number_of_tries) {
|
|
// Don't wait for the graph to change on the last loop because we won't check again.
|
|
continue;
|
|
}
|
|
ret = rcl_wait_set_clear_guard_conditions(wait_set_ptr);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
ret = rcl_wait_set_add_guard_condition(wait_set_ptr, graph_guard_condition);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
std::chrono::nanoseconds time_to_sleep = std::chrono::milliseconds(200);
|
|
printf(
|
|
" state wrong, waiting up to '%s' nanoseconds for graph changes... ",
|
|
std::to_string(time_to_sleep.count()).c_str());
|
|
ret = rcl_wait(wait_set_ptr, time_to_sleep.count());
|
|
if (ret == RCL_RET_TIMEOUT) {
|
|
printf("timeout\n");
|
|
continue;
|
|
}
|
|
printf("change occurred\n");
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
}
|
|
EXPECT_EQ(expected_publisher_count, publisher_count);
|
|
EXPECT_EQ(expected_subscriber_count, subscriber_count);
|
|
if (expected_in_tnat) {
|
|
EXPECT_TRUE(is_in_tnat);
|
|
} else {
|
|
EXPECT_FALSE(is_in_tnat);
|
|
}
|
|
}
|
|
|
|
/* Test graph queries with a hand crafted graph.
|
|
*/
|
|
TEST_F(CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION), test_graph_query_functions) {
|
|
stop_memory_checking();
|
|
std::string topic_name("/test_graph_query_functions__");
|
|
std::chrono::nanoseconds now = std::chrono::system_clock::now().time_since_epoch();
|
|
topic_name += std::to_string(now.count());
|
|
printf("Using topic name: %s\n", topic_name.c_str());
|
|
rcl_ret_t ret;
|
|
const rcl_guard_condition_t * graph_guard_condition =
|
|
rcl_node_get_graph_guard_condition(this->node_ptr);
|
|
// First assert the "topic_name" is not in use.
|
|
check_graph_state(
|
|
this->node_ptr,
|
|
this->wait_set_ptr,
|
|
graph_guard_condition,
|
|
topic_name,
|
|
0, // expected publishers on topic
|
|
0, // expected subscribers on topic
|
|
false, // topic expected in graph
|
|
9); // number of retries
|
|
// Now create a publisher on "topic_name" and check that it is seen.
|
|
rcl_publisher_t pub = rcl_get_zero_initialized_publisher();
|
|
rcl_publisher_options_t pub_ops = rcl_publisher_get_default_options();
|
|
auto ts = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String);
|
|
ret = rcl_publisher_init(&pub, this->node_ptr, ts, topic_name.c_str(), &pub_ops);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// Check the graph.
|
|
check_graph_state(
|
|
this->node_ptr,
|
|
this->wait_set_ptr,
|
|
graph_guard_condition,
|
|
topic_name,
|
|
1, // expected publishers on topic
|
|
0, // expected subscribers on topic
|
|
true, // topic expected in graph
|
|
9); // number of retries
|
|
// Now create a subscriber.
|
|
rcl_subscription_t sub = rcl_get_zero_initialized_subscription();
|
|
rcl_subscription_options_t sub_ops = rcl_subscription_get_default_options();
|
|
ret = rcl_subscription_init(&sub, this->node_ptr, ts, topic_name.c_str(), &sub_ops);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// Check the graph again.
|
|
check_graph_state(
|
|
this->node_ptr,
|
|
this->wait_set_ptr,
|
|
graph_guard_condition,
|
|
topic_name,
|
|
1, // expected publishers on topic
|
|
1, // expected subscribers on topic
|
|
true, // topic expected in graph
|
|
9); // number of retries
|
|
// Destroy the publisher.
|
|
ret = rcl_publisher_fini(&pub, this->node_ptr);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// Check the graph again.
|
|
check_graph_state(
|
|
this->node_ptr,
|
|
this->wait_set_ptr,
|
|
graph_guard_condition,
|
|
topic_name,
|
|
0, // expected publishers on topic
|
|
1, // expected subscribers on topic
|
|
true, // topic expected in graph
|
|
9); // number of retries
|
|
// Destroy the subscriber.
|
|
ret = rcl_subscription_fini(&sub, this->node_ptr);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
rcl_reset_error();
|
|
// Check the graph again.
|
|
check_graph_state(
|
|
this->node_ptr,
|
|
this->wait_set_ptr,
|
|
graph_guard_condition,
|
|
topic_name,
|
|
0, // expected publishers on topic
|
|
0, // expected subscribers on topic
|
|
false, // topic expected in graph
|
|
9); // number of retries
|
|
}
|
|
|
|
/* Test the graph guard condition notices topic changes.
|
|
*
|
|
* Note: this test could be impacted by other communications on the same ROS Domain.
|
|
*/
|
|
TEST_F(CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION), test_graph_guard_condition_topics) {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret;
|
|
// Create a thread to sleep for a time, then create a publisher, sleep more, then a subscriber,
|
|
// sleep more, destroy the subscriber, sleep more, and then destroy the publisher.
|
|
std::promise<bool> topic_changes_promise;
|
|
std::thread topic_thread([this, &topic_changes_promise]() {
|
|
// sleep
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
// create the publisher
|
|
rcl_publisher_t pub = rcl_get_zero_initialized_publisher();
|
|
rcl_publisher_options_t pub_ops = rcl_publisher_get_default_options();
|
|
rcl_ret_t ret = rcl_publisher_init(
|
|
&pub, this->node_ptr, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String),
|
|
"/chatter_test_graph_guard_condition_topics", &pub_ops);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
// sleep
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
// create the subscription
|
|
rcl_subscription_t sub = rcl_get_zero_initialized_subscription();
|
|
rcl_subscription_options_t sub_ops = rcl_subscription_get_default_options();
|
|
ret = rcl_subscription_init(
|
|
&sub, this->node_ptr, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String),
|
|
"/chatter_test_graph_guard_condition_topics", &sub_ops);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
// sleep
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
// destroy the subscription
|
|
ret = rcl_subscription_fini(&sub, this->node_ptr);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
// sleep
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
// destroy the publication
|
|
ret = rcl_publisher_fini(&pub, this->node_ptr);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
// notify that the thread is done
|
|
topic_changes_promise.set_value(true);
|
|
});
|
|
// Wait for the graph state to change, expecting it to do so at least 4 times,
|
|
// once for each change in the topics thread.
|
|
const rcl_guard_condition_t * graph_guard_condition =
|
|
rcl_node_get_graph_guard_condition(this->node_ptr);
|
|
ASSERT_NE(nullptr, graph_guard_condition) << rcl_get_error_string_safe();
|
|
std::shared_future<bool> future = topic_changes_promise.get_future();
|
|
size_t graph_changes_count = 0;
|
|
// while the topic thread is not done, wait and count the graph changes
|
|
while (future.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
|
|
ret = rcl_wait_set_clear_guard_conditions(this->wait_set_ptr);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
ret = rcl_wait_set_add_guard_condition(this->wait_set_ptr, graph_guard_condition);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
std::chrono::nanoseconds time_to_sleep = std::chrono::milliseconds(200);
|
|
printf(
|
|
"waiting up to '%s' nanoseconds for graph changes\n",
|
|
std::to_string(time_to_sleep.count()).c_str());
|
|
ret = rcl_wait(this->wait_set_ptr, time_to_sleep.count());
|
|
if (ret == RCL_RET_TIMEOUT) {
|
|
continue;
|
|
}
|
|
graph_changes_count++;
|
|
}
|
|
topic_thread.join();
|
|
// expect at least 4 changes
|
|
ASSERT_GE(graph_changes_count, 4ul);
|
|
}
|
|
|
|
/* Test the rcl_service_server_is_available function.
|
|
*/
|
|
TEST_F(CLASSNAME(TestGraphFixture, RMW_IMPLEMENTATION), test_rcl_service_server_is_available) {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret;
|
|
// First create a client which will be used to call the function.
|
|
rcl_client_t client = rcl_get_zero_initialized_client();
|
|
auto ts = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, AddTwoInts);
|
|
const char * service_name = "/service_test_rcl_service_server_is_available";
|
|
rcl_client_options_t client_options = rcl_client_get_default_options();
|
|
ret = rcl_client_init(&client, this->node_ptr, ts, service_name, &client_options);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
auto client_exit = make_scope_exit([&client, this]() {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret = rcl_client_fini(&client, this->node_ptr);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
});
|
|
// Check, knowing there is no service server (created by us at least).
|
|
bool is_available;
|
|
ret = rcl_service_server_is_available(this->node_ptr, &client, &is_available);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
ASSERT_FALSE(is_available);
|
|
// Setup function to wait for service state to change using graph guard condition.
|
|
const rcl_guard_condition_t * graph_guard_condition =
|
|
rcl_node_get_graph_guard_condition(this->node_ptr);
|
|
ASSERT_NE(nullptr, graph_guard_condition) << rcl_get_error_string_safe();
|
|
auto wait_for_service_state_to_change = [this, &graph_guard_condition, &client](
|
|
bool expected_state,
|
|
bool & is_available)
|
|
{
|
|
is_available = false;
|
|
auto start = std::chrono::steady_clock::now();
|
|
auto end = start + std::chrono::seconds(10);
|
|
while (std::chrono::steady_clock::now() < end) {
|
|
// We wait multiple times in case other graph changes are occurring simultaneously.
|
|
std::chrono::nanoseconds time_left = end - start;
|
|
std::chrono::nanoseconds time_to_sleep = time_left;
|
|
std::chrono::seconds min_sleep(1);
|
|
if (time_to_sleep > min_sleep) {
|
|
time_to_sleep = min_sleep;
|
|
}
|
|
rcl_ret_t ret = rcl_wait_set_clear_guard_conditions(this->wait_set_ptr);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
ret = rcl_wait_set_add_guard_condition(this->wait_set_ptr, graph_guard_condition);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
printf(
|
|
"waiting up to '%s' nanoseconds for graph changes\n",
|
|
std::to_string(time_to_sleep.count()).c_str());
|
|
ret = rcl_wait(this->wait_set_ptr, time_to_sleep.count());
|
|
if (ret == RCL_RET_TIMEOUT) {
|
|
if (!is_connext) {
|
|
// TODO(wjwwood):
|
|
// Connext has a race condition which can cause the graph guard
|
|
// condition to wake up due to the necessary topics going away,
|
|
// but afterwards rcl_service_server_is_available() still does
|
|
// not reflect that the service is "no longer available".
|
|
// The result is that some tests are flaky unless you not only
|
|
// check right after a graph change but again in the future where
|
|
// rcl_service_server_is_available() eventually reports the
|
|
// service is no longer there. This condition can be removed and
|
|
// we can always continue when we get RCL_RET_TIMEOUT once that
|
|
// is fixed.
|
|
continue;
|
|
}
|
|
} else {
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
}
|
|
ret = rcl_service_server_is_available(this->node_ptr, &client, &is_available);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
if (is_available == expected_state) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
{
|
|
// Create the service server.
|
|
rcl_service_t service = rcl_get_zero_initialized_service();
|
|
rcl_service_options_t service_options = rcl_service_get_default_options();
|
|
ret = rcl_service_init(&service, this->node_ptr, ts, service_name, &service_options);
|
|
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
auto service_exit = make_scope_exit([&service, this]() {
|
|
stop_memory_checking();
|
|
rcl_ret_t ret = rcl_service_fini(&service, this->node_ptr);
|
|
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string_safe();
|
|
});
|
|
// Wait for and then assert that it is available.
|
|
wait_for_service_state_to_change(true, is_available);
|
|
ASSERT_TRUE(is_available);
|
|
}
|
|
// Assert the state goes back to "not available" after the service is removed.
|
|
wait_for_service_state_to_change(false, is_available);
|
|
ASSERT_FALSE(is_available);
|
|
}
|