New interfaces and their implementations for QoS features (#408)
* implement rcl_wait and rcl_take for rcl_event_t Signed-off-by: Miaofei <miaofei@amazon.com> * address feedback regarding formatting issues Signed-off-by: Miaofei <miaofei@amazon.com> * Remove dependency on sleep in test_events Signed-off-by: Ross Desmond <44277324+ross-desmond@users.noreply.github.com> * update total max wait time for setting up publisher and subscriber to 10 seconds Signed-off-by: Miaofei <miaofei@amazon.com> * Fix test_events for rmw_connext Signed-off-by: Ross Desmond <44277324+ross-desmond@users.noreply.github.com> * Refactor out timed loop from tests to function Signed-off-by: Miaofei <miaofei@amazon.com> * address additional feedback from pull request Signed-off-by: Miaofei <miaofei@amazon.com> * update comment regarding difference between connext and opensplice Signed-off-by: Miaofei <miaofei@amazon.com> * fix uncrustify issues Signed-off-by: Miaofei <miaofei@amazon.com> * update test_events for compatibility with API changes Signed-off-by: Miaofei <miaofei@amazon.com> * temporarily disable test_events for macOS Signed-off-by: Miaofei <miaofei@amazon.com>
This commit is contained in:
parent
7660956a96
commit
461ad9cd71
26 changed files with 1269 additions and 55 deletions
|
@ -35,6 +35,7 @@ set(${PROJECT_NAME}_sources
|
|||
src/rcl/client.c
|
||||
src/rcl/common.c
|
||||
src/rcl/context.c
|
||||
src/rcl/event.c
|
||||
src/rcl/expand_topic_name.c
|
||||
src/rcl/graph.c
|
||||
src/rcl/guard_condition.c
|
||||
|
|
169
rcl/include/rcl/event.h
Normal file
169
rcl/include/rcl/event.h
Normal file
|
@ -0,0 +1,169 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
#ifndef RCL__EVENT_H_
|
||||
#define RCL__EVENT_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include "rcl/client.h"
|
||||
#include "rcl/macros.h"
|
||||
#include "rcl/publisher.h"
|
||||
#include "rcl/service.h"
|
||||
#include "rcl/subscription.h"
|
||||
#include "rcl/visibility_control.h"
|
||||
|
||||
typedef enum rcl_publisher_event_type_t
|
||||
{
|
||||
RCL_PUBLISHER_OFFERED_DEADLINE_MISSED,
|
||||
RCL_PUBLISHER_LIVELINESS_LOST
|
||||
} rcl_publisher_event_type_t;
|
||||
|
||||
typedef enum rcl_subscription_event_type_t
|
||||
{
|
||||
RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED,
|
||||
RCL_SUBSCRIPTION_LIVELINESS_CHANGED
|
||||
} rcl_subscription_event_type_t;
|
||||
|
||||
/// rmw struct.
|
||||
typedef struct rmw_event_t rmw_event_t;
|
||||
|
||||
/// Internal rcl implementation struct.
|
||||
struct rcl_event_impl_t;
|
||||
|
||||
/// Structure which encapsulates a ROS QoS event handle.
|
||||
typedef struct rcl_event_t
|
||||
{
|
||||
struct rcl_event_impl_t * impl;
|
||||
} rcl_event_t;
|
||||
|
||||
/// Return a rcl_event_t struct with members set to `NULL`.
|
||||
/**
|
||||
* Should be called to get a null rcl_event_t before passing to
|
||||
* rcl_event_init().
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_event_t
|
||||
rcl_get_zero_initialized_event(void);
|
||||
|
||||
/// Initialize an rcl_event_t with a publisher.
|
||||
/**
|
||||
* Fill the rcl_event_t with the publisher and desired event_type.
|
||||
*
|
||||
* \param[in,out] event pointer to fill
|
||||
* \param[in] publisher to get events from
|
||||
* \param[in] event_type to listen for
|
||||
* \return `RCL_RET_OK` if the rcl_event_t is filled, or
|
||||
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
|
||||
* \return `RCL_RET_ERROR` if an unspecified error occurs.
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_publisher_event_init(
|
||||
rcl_event_t * event,
|
||||
const rcl_publisher_t * publisher,
|
||||
const rcl_publisher_event_type_t event_type);
|
||||
|
||||
/// Initialize an rcl_event_t with a subscription.
|
||||
/**
|
||||
* Fill the rcl_event_t with the subscription and desired event_type.
|
||||
*
|
||||
* \param[in,out] event pointer to fill
|
||||
* \param[in] subscription to get events from
|
||||
* \param[in] event_type to listen for
|
||||
* \return `RCL_RET_OK` if the rcl_event_t is filled, or
|
||||
* \return `RCL_RET_INVALID_ARGUMENT` if any arguments are invalid, or
|
||||
* \return `RCL_RET_ERROR` if an unspecified error occurs.
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_subscription_event_init(
|
||||
rcl_event_t * event,
|
||||
const rcl_subscription_t * subscription,
|
||||
const rcl_subscription_event_type_t event_type);
|
||||
|
||||
// Take event using the event handle.
|
||||
/**
|
||||
* Take an event from the event handle.
|
||||
*
|
||||
* \param[in] event_handle event object to take from
|
||||
* \param[in, out] event_info event info object to write taken data into
|
||||
* \param[in, out] taken boolean flag indicating if an event was taken or not
|
||||
* \return `RCL_RET_OK` if successful, or
|
||||
* \return `RCL_RET_BAD_ALLOC` if memory allocation failed, or
|
||||
* \return `RCL_RET_ERROR` if an unexpected error occurs.
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_take_event(
|
||||
const rcl_event_t * event,
|
||||
void * event_info);
|
||||
|
||||
// Finalize an event.
|
||||
/**
|
||||
* Finalize an event.
|
||||
*
|
||||
* \param[in] event to finalize
|
||||
* \return `RCL_RET_OK` if successful, or
|
||||
* \return `RCL_RET_EVENT_INVALID` if event is null, or
|
||||
* \return `RCL_RET_ERROR` if an unexpected error occurs.
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_event_fini(rcl_event_t * event);
|
||||
|
||||
/// Return the rmw event handle.
|
||||
/**
|
||||
* The handle returned is a pointer to the internally held rmw handle.
|
||||
* This function can fail, and therefore return `NULL`, if the:
|
||||
* - event is `NULL`
|
||||
* - event is invalid (never called init, called fini, or invalid node)
|
||||
*
|
||||
* The returned handle is made invalid if the event is finalized or if
|
||||
* rcl_shutdown() is called.
|
||||
* The returned handle is not guaranteed to be valid for the life time of the
|
||||
* event as it may be finalized and recreated itself.
|
||||
* Therefore it is recommended to get the handle from the event using
|
||||
* this function each time it is needed and avoid use of the handle
|
||||
* concurrently with functions that might change it.
|
||||
*
|
||||
* <hr>
|
||||
* Attribute | Adherence
|
||||
* ------------------ | -------------
|
||||
* Allocates Memory | Yes
|
||||
* Thread-Safe | No
|
||||
* Uses Atomics | No
|
||||
* Lock-Free | Yes
|
||||
*
|
||||
* \param[in] event pointer to the rcl event
|
||||
* \return rmw event handle if successful, otherwise `NULL`
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rmw_event_t *
|
||||
rcl_event_get_rmw_handle(const rcl_event_t * event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RCL__EVENT_H_
|
|
@ -361,6 +361,31 @@ RCL_WARN_UNUSED
|
|||
rcl_ret_t
|
||||
rcl_node_get_domain_id(const rcl_node_t * node, size_t * domain_id);
|
||||
|
||||
/// Manually assert that this node is alive (for RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_NODE)
|
||||
/**
|
||||
* If the rmw Liveliness policy is set to RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_NODE, the creator of
|
||||
* this node may manually call `assert_liveliness` at some point in time to signal to the rest
|
||||
* of the system that this Node is still alive.
|
||||
* This function must be called at least as often as the qos_profile's liveliness_lease_duration
|
||||
*
|
||||
* <hr>
|
||||
* Attribute | Adherence
|
||||
* ------------------ | -------------
|
||||
* Allocates Memory | No
|
||||
* Thread-Safe | Yes
|
||||
* Uses Atomics | No
|
||||
* Lock-Free | Yes
|
||||
*
|
||||
* \param[in] node handle to the node that needs liveliness to be asserted
|
||||
* \return `RCL_RET_OK` if the liveliness assertion was completed successfully, or
|
||||
* \return `RCL_RET_NODE_INVALID` if the node is invalid, or
|
||||
* \return `RCL_RET_ERROR` if an unspecified error occurs.
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_node_assert_liveliness(const rcl_node_t * node);
|
||||
|
||||
/// Return the rmw node handle.
|
||||
/**
|
||||
* The handle returned is a pointer to the internally held rmw handle.
|
||||
|
|
|
@ -300,6 +300,31 @@ rcl_publish_serialized_message(
|
|||
rmw_publisher_allocation_t * allocation
|
||||
);
|
||||
|
||||
/// Manually assert that this Publisher is alive (for RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC)
|
||||
/**
|
||||
* If the rmw Liveliness policy is set to RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC, the creator of
|
||||
* this publisher may manually call `assert_liveliness` at some point in time to signal to the rest
|
||||
* of the system that this Node is still alive.
|
||||
* This function must be called at least as often as the qos_profile's liveliness_lease_duration
|
||||
*
|
||||
* <hr>
|
||||
* Attribute | Adherence
|
||||
* ------------------ | -------------
|
||||
* Allocates Memory | No
|
||||
* Thread-Safe | Yes
|
||||
* Uses Atomics | No
|
||||
* Lock-Free | Yes
|
||||
*
|
||||
* \param[in] publisher handle to the publisher that needs liveliness to be asserted
|
||||
* \return `RCL_RET_OK` if the liveliness assertion was completed successfully, or
|
||||
* \return `RCL_RET_PUBLISHER_INVALID` if the publisher is invalid, or
|
||||
* \return `RCL_RET_ERROR` if an unspecified error occurs.
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_publisher_assert_liveliness(const rcl_publisher_t * publisher);
|
||||
|
||||
/// Get the topic name for the publisher.
|
||||
/**
|
||||
* This function returns the publisher's internal topic name string.
|
||||
|
|
|
@ -99,6 +99,12 @@ typedef rmw_ret_t rcl_ret_t;
|
|||
/// Argument is not a valid log level rule
|
||||
#define RCL_RET_INVALID_LOG_LEVEL_RULE 1020
|
||||
|
||||
// rcl event specific ret codes in 20XX
|
||||
/// Invalid rcl_event_t given return code.
|
||||
#define RCL_RET_EVENT_INVALID 2000
|
||||
/// Failed to take an event from the event handle
|
||||
#define RCL_RET_EVENT_TAKE_FAILED 2001
|
||||
|
||||
/// typedef for rmw_serialized_message_t;
|
||||
typedef rmw_serialized_message_t rcl_serialized_message_t;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ extern "C"
|
|||
#include "rcl/service.h"
|
||||
#include "rcl/subscription.h"
|
||||
#include "rcl/timer.h"
|
||||
#include "rcl/event.h"
|
||||
#include "rcl/types.h"
|
||||
#include "rcl/visibility_control.h"
|
||||
|
||||
|
@ -52,6 +53,9 @@ typedef struct rcl_wait_set_t
|
|||
/// Storage for service pointers.
|
||||
const rcl_service_t ** services;
|
||||
size_t size_of_services;
|
||||
/// Storage for event pointers.
|
||||
const rcl_event_t ** events;
|
||||
size_t size_of_events;
|
||||
/// Implementation specific storage.
|
||||
struct rcl_wait_set_impl_t * impl;
|
||||
} rcl_wait_set_t;
|
||||
|
@ -124,6 +128,7 @@ rcl_wait_set_init(
|
|||
size_t number_of_timers,
|
||||
size_t number_of_clients,
|
||||
size_t number_of_services,
|
||||
size_t number_of_events,
|
||||
rcl_context_t * context,
|
||||
rcl_allocator_t allocator);
|
||||
|
||||
|
@ -289,7 +294,8 @@ rcl_wait_set_resize(
|
|||
size_t guard_conditions_size,
|
||||
size_t timers_size,
|
||||
size_t clients_size,
|
||||
size_t services_size);
|
||||
size_t services_size,
|
||||
size_t events_size);
|
||||
|
||||
/// Store a pointer to the guard condition in the next empty spot in the set.
|
||||
/**
|
||||
|
@ -343,6 +349,19 @@ rcl_wait_set_add_service(
|
|||
const rcl_service_t * service,
|
||||
size_t * index);
|
||||
|
||||
/// Store a pointer to the event in the next empty spot in the set.
|
||||
/**
|
||||
* This function behaves exactly the same as for subscriptions.
|
||||
* \see rcl_wait_set_add_subscription
|
||||
*/
|
||||
RCL_PUBLIC
|
||||
RCL_WARN_UNUSED
|
||||
rcl_ret_t
|
||||
rcl_wait_set_add_event(
|
||||
rcl_wait_set_t * wait_set,
|
||||
const rcl_event_t * event,
|
||||
size_t * index);
|
||||
|
||||
/// Block until the wait set is ready or until the timeout has been exceeded.
|
||||
/**
|
||||
* This function will collect the items in the rcl_wait_set_t and pass them
|
||||
|
|
186
rcl/src/rcl/event.c
Normal file
186
rcl/src/rcl/event.c
Normal file
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include "rcl/event.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/expand_topic_name.h"
|
||||
#include "rcl/remap.h"
|
||||
#include "rcutils/logging_macros.h"
|
||||
#include "rmw/error_handling.h"
|
||||
#include "rmw/validate_full_topic_name.h"
|
||||
#include "rmw/event.h"
|
||||
|
||||
#include "./common.h"
|
||||
#include "./publisher_impl.h"
|
||||
#include "./subscription_impl.h"
|
||||
|
||||
typedef struct rcl_event_impl_t
|
||||
{
|
||||
rmw_event_t rmw_handle;
|
||||
rcl_allocator_t allocator;
|
||||
} rcl_event_impl_t;
|
||||
|
||||
rcl_event_t
|
||||
rcl_get_zero_initialized_event()
|
||||
{
|
||||
static rcl_event_t null_event = {0};
|
||||
return null_event;
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_publisher_event_init(
|
||||
rcl_event_t * event,
|
||||
const rcl_publisher_t * publisher,
|
||||
const rcl_publisher_event_type_t event_type)
|
||||
{
|
||||
rcl_ret_t ret = RCL_RET_OK;
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(event, RCL_RET_EVENT_INVALID);
|
||||
// Check publisher and allocator first, so allocator can be used with errors.
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(publisher, RCL_RET_INVALID_ARGUMENT);
|
||||
rcl_allocator_t * allocator = &publisher->impl->options.allocator;
|
||||
RCL_CHECK_ALLOCATOR_WITH_MSG(allocator, "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
|
||||
|
||||
// Allocate space for the implementation struct.
|
||||
event->impl = (rcl_event_impl_t *) allocator->allocate(
|
||||
sizeof(rcl_event_impl_t), allocator->state);
|
||||
RCL_CHECK_FOR_NULL_WITH_MSG(
|
||||
event->impl, "allocating memory failed", ret = RCL_RET_BAD_ALLOC; return ret);
|
||||
|
||||
event->impl->rmw_handle = rmw_get_zero_initialized_event();
|
||||
event->impl->allocator = *allocator;
|
||||
|
||||
rmw_event_type_t rmw_event_type = RMW_EVENT_INVALID;
|
||||
switch (event_type) {
|
||||
case RCL_PUBLISHER_OFFERED_DEADLINE_MISSED:
|
||||
rmw_event_type = RMW_EVENT_OFFERED_DEADLINE_MISSED;
|
||||
break;
|
||||
case RCL_PUBLISHER_LIVELINESS_LOST:
|
||||
rmw_event_type = RMW_EVENT_LIVELINESS_LOST;
|
||||
break;
|
||||
default:
|
||||
RCL_SET_ERROR_MSG("Event type for publisher not supported");
|
||||
return RCL_RET_INVALID_ARGUMENT;
|
||||
}
|
||||
return rmw_publisher_event_init(
|
||||
&event->impl->rmw_handle,
|
||||
publisher->impl->rmw_handle,
|
||||
rmw_event_type);
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_subscription_event_init(
|
||||
rcl_event_t * event,
|
||||
const rcl_subscription_t * subscription,
|
||||
const rcl_subscription_event_type_t event_type)
|
||||
{
|
||||
rcl_ret_t ret = RCL_RET_OK;
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(event, RCL_RET_EVENT_INVALID);
|
||||
// Check subscription and allocator first, so allocator can be used with errors.
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(subscription, RCL_RET_INVALID_ARGUMENT);
|
||||
rcl_allocator_t * allocator = &subscription->impl->options.allocator;
|
||||
RCL_CHECK_ALLOCATOR_WITH_MSG(allocator, "invalid allocator", return RCL_RET_INVALID_ARGUMENT);
|
||||
|
||||
// Allocate space for the implementation struct.
|
||||
event->impl = (rcl_event_impl_t *) allocator->allocate(
|
||||
sizeof(rcl_event_impl_t), allocator->state);
|
||||
RCL_CHECK_FOR_NULL_WITH_MSG(
|
||||
event->impl, "allocating memory failed", ret = RCL_RET_BAD_ALLOC; return ret);
|
||||
|
||||
event->impl->rmw_handle = rmw_get_zero_initialized_event();
|
||||
event->impl->allocator = *allocator;
|
||||
|
||||
rmw_event_type_t rmw_event_type = RMW_EVENT_INVALID;
|
||||
switch (event_type) {
|
||||
case RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED:
|
||||
rmw_event_type = RMW_EVENT_REQUESTED_DEADLINE_MISSED;
|
||||
break;
|
||||
case RCL_SUBSCRIPTION_LIVELINESS_CHANGED:
|
||||
rmw_event_type = RMW_EVENT_LIVELINESS_CHANGED;
|
||||
break;
|
||||
default:
|
||||
RCL_SET_ERROR_MSG("Event type for subscription not supported");
|
||||
return RCL_RET_INVALID_ARGUMENT;
|
||||
}
|
||||
return rmw_subscription_event_init(
|
||||
&event->impl->rmw_handle,
|
||||
subscription->impl->rmw_handle,
|
||||
rmw_event_type);
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_take_event(
|
||||
const rcl_event_t * event,
|
||||
void * event_info)
|
||||
{
|
||||
bool taken = false;
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(event, RCL_RET_EVENT_INVALID);
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(event_info, RCL_RET_INVALID_ARGUMENT);
|
||||
rmw_ret_t ret = rmw_take_event(&event->impl->rmw_handle, event_info, &taken);
|
||||
if (RMW_RET_OK != ret) {
|
||||
RCL_SET_ERROR_MSG(rmw_get_error_string().str);
|
||||
return rcl_convert_rmw_ret_to_rcl_ret(ret);
|
||||
}
|
||||
if (!taken) {
|
||||
RCUTILS_LOG_DEBUG_NAMED(
|
||||
ROS_PACKAGE_NAME, "take_event request complete, unable to take event");
|
||||
return RCL_RET_EVENT_TAKE_FAILED;
|
||||
}
|
||||
RCUTILS_LOG_DEBUG_NAMED(
|
||||
ROS_PACKAGE_NAME, "take_event request success");
|
||||
return rcl_convert_rmw_ret_to_rcl_ret(ret);
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_event_fini(rcl_event_t * event)
|
||||
{
|
||||
rcl_ret_t result = RCL_RET_OK;
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(event, RCL_RET_EVENT_INVALID);
|
||||
|
||||
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Finalizing event");
|
||||
if (NULL != event->impl) {
|
||||
rcl_allocator_t allocator = event->impl->allocator;
|
||||
rmw_ret_t ret = rmw_event_fini(&event->impl->rmw_handle);
|
||||
if (ret != RMW_RET_OK) {
|
||||
RCL_SET_ERROR_MSG(rmw_get_error_string().str);
|
||||
result = rcl_convert_rmw_ret_to_rcl_ret(ret);
|
||||
}
|
||||
allocator.deallocate(event->impl, allocator.state);
|
||||
event->impl = NULL;
|
||||
}
|
||||
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Event finalized");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
rmw_event_t *
|
||||
rcl_event_get_rmw_handle(const rcl_event_t * event)
|
||||
{
|
||||
if (NULL == event) {
|
||||
return NULL; // error already set
|
||||
} else {
|
||||
return &event->impl->rmw_handle;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -525,6 +525,19 @@ rcl_node_get_domain_id(const rcl_node_t * node, size_t * domain_id)
|
|||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_node_assert_liveliness(const rcl_node_t * node)
|
||||
{
|
||||
if (!rcl_node_is_valid(node)) {
|
||||
return RCL_RET_NODE_INVALID; // error already set
|
||||
}
|
||||
if (rmw_node_assert_liveliness(node->impl->rmw_node_handle) != RMW_RET_OK) {
|
||||
RCL_SET_ERROR_MSG(rmw_get_error_string().str);
|
||||
return RCL_RET_ERROR;
|
||||
}
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
rmw_node_t *
|
||||
rcl_node_get_rmw_handle(const rcl_node_t * node)
|
||||
{
|
||||
|
|
|
@ -22,23 +22,16 @@ extern "C"
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "./common.h"
|
||||
#include "rcl/allocator.h"
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/expand_topic_name.h"
|
||||
#include "rcl/remap.h"
|
||||
#include "rcutils/logging_macros.h"
|
||||
#include "rmw/error_handling.h"
|
||||
#include "rmw/rmw.h"
|
||||
#include "rmw/validate_full_topic_name.h"
|
||||
|
||||
typedef struct rcl_publisher_impl_t
|
||||
{
|
||||
rcl_publisher_options_t options;
|
||||
rmw_qos_profile_t actual_qos;
|
||||
rcl_context_t * context;
|
||||
rmw_publisher_t * rmw_handle;
|
||||
} rcl_publisher_impl_t;
|
||||
#include "./common.h"
|
||||
#include "./publisher_impl.h"
|
||||
|
||||
rcl_publisher_t
|
||||
rcl_get_zero_initialized_publisher()
|
||||
|
@ -289,6 +282,19 @@ rcl_publish_serialized_message(
|
|||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_publisher_assert_liveliness(const rcl_publisher_t * publisher)
|
||||
{
|
||||
if (!rcl_publisher_is_valid(publisher)) {
|
||||
return RCL_RET_PUBLISHER_INVALID; // error already set
|
||||
}
|
||||
if (rmw_publisher_assert_liveliness(publisher->impl->rmw_handle) != RMW_RET_OK) {
|
||||
RCL_SET_ERROR_MSG(rmw_get_error_string().str);
|
||||
return RCL_RET_ERROR;
|
||||
}
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
const char *
|
||||
rcl_publisher_get_topic_name(const rcl_publisher_t * publisher)
|
||||
{
|
||||
|
|
30
rcl/src/rcl/publisher_impl.h
Normal file
30
rcl/src/rcl/publisher_impl.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
#ifndef RCL__PUBLISHER_IMPL_H_
|
||||
#define RCL__PUBLISHER_IMPL_H_
|
||||
|
||||
#include "rmw/rmw.h"
|
||||
|
||||
#include "rcl/publisher.h"
|
||||
|
||||
typedef struct rcl_publisher_impl_t
|
||||
{
|
||||
rcl_publisher_options_t options;
|
||||
rmw_qos_profile_t actual_qos;
|
||||
rcl_context_t * context;
|
||||
rmw_publisher_t * rmw_handle;
|
||||
} rcl_publisher_impl_t;
|
||||
|
||||
#endif // RCL__PUBLISHER_IMPL_H_
|
|
@ -21,20 +21,16 @@ extern "C"
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "./common.h"
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/expand_topic_name.h"
|
||||
#include "rcl/remap.h"
|
||||
#include "rcutils/logging_macros.h"
|
||||
#include "rmw/error_handling.h"
|
||||
#include "rmw/rmw.h"
|
||||
#include "rmw/validate_full_topic_name.h"
|
||||
|
||||
typedef struct rcl_subscription_impl_t
|
||||
{
|
||||
rcl_subscription_options_t options;
|
||||
rmw_subscription_t * rmw_handle;
|
||||
} rcl_subscription_impl_t;
|
||||
#include "./common.h"
|
||||
#include "./subscription_impl.h"
|
||||
|
||||
|
||||
rcl_subscription_t
|
||||
rcl_get_zero_initialized_subscription()
|
||||
|
|
28
rcl/src/rcl/subscription_impl.h
Normal file
28
rcl/src/rcl/subscription_impl.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2015 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.
|
||||
|
||||
#ifndef RCL__SUBSCRIPTION_IMPL_H_
|
||||
#define RCL__SUBSCRIPTION_IMPL_H_
|
||||
|
||||
#include "rmw/rmw.h"
|
||||
|
||||
#include "rcl/subscription.h"
|
||||
|
||||
typedef struct rcl_subscription_impl_t
|
||||
{
|
||||
rcl_subscription_options_t options;
|
||||
rmw_subscription_t * rmw_handle;
|
||||
} rcl_subscription_impl_t;
|
||||
|
||||
#endif // RCL__SUBSCRIPTION_IMPL_H_
|
|
@ -29,6 +29,7 @@ extern "C"
|
|||
#include "rcutils/logging_macros.h"
|
||||
#include "rmw/error_handling.h"
|
||||
#include "rmw/rmw.h"
|
||||
#include "rmw/event.h"
|
||||
|
||||
#include "./context_impl.h"
|
||||
|
||||
|
@ -46,6 +47,10 @@ typedef struct rcl_wait_set_impl_t
|
|||
// number of services that have been added to the wait set
|
||||
size_t service_index;
|
||||
rmw_services_t rmw_services;
|
||||
// number of events that have been added to the wait set
|
||||
size_t event_index;
|
||||
rmw_events_t rmw_events;
|
||||
|
||||
rmw_wait_set_t * rmw_wait_set;
|
||||
// number of timers that have been added to the wait set
|
||||
size_t timer_index;
|
||||
|
@ -83,7 +88,7 @@ __wait_set_is_valid(const rcl_wait_set_t * wait_set)
|
|||
static void
|
||||
__wait_set_clean_up(rcl_wait_set_t * wait_set, rcl_allocator_t allocator)
|
||||
{
|
||||
rcl_ret_t ret = rcl_wait_set_resize(wait_set, 0, 0, 0, 0, 0);
|
||||
rcl_ret_t ret = rcl_wait_set_resize(wait_set, 0, 0, 0, 0, 0, 0);
|
||||
(void)ret; // NO LINT
|
||||
assert(RCL_RET_OK == ret); // Defensive, shouldn't fail with size 0.
|
||||
if (wait_set->impl) {
|
||||
|
@ -100,6 +105,7 @@ rcl_wait_set_init(
|
|||
size_t number_of_timers,
|
||||
size_t number_of_clients,
|
||||
size_t number_of_services,
|
||||
size_t number_of_events,
|
||||
rcl_context_t * context,
|
||||
rcl_allocator_t allocator)
|
||||
{
|
||||
|
@ -138,12 +144,15 @@ rcl_wait_set_init(
|
|||
wait_set->impl->rmw_clients.client_count = 0;
|
||||
wait_set->impl->rmw_services.services = NULL;
|
||||
wait_set->impl->rmw_services.service_count = 0;
|
||||
wait_set->impl->rmw_events.events = NULL;
|
||||
wait_set->impl->rmw_events.event_count = 0;
|
||||
|
||||
size_t num_conditions =
|
||||
(2 * number_of_subscriptions) +
|
||||
number_of_guard_conditions +
|
||||
number_of_clients +
|
||||
number_of_services;
|
||||
number_of_services +
|
||||
number_of_events;
|
||||
|
||||
wait_set->impl->rmw_wait_set = rmw_create_wait_set(&(context->impl->rmw_context), num_conditions);
|
||||
if (!wait_set->impl->rmw_wait_set) {
|
||||
|
@ -157,7 +166,7 @@ rcl_wait_set_init(
|
|||
// Initialize subscription space.
|
||||
rcl_ret_t ret = rcl_wait_set_resize(
|
||||
wait_set, number_of_subscriptions, number_of_guard_conditions, number_of_timers,
|
||||
number_of_clients, number_of_services);
|
||||
number_of_clients, number_of_services, number_of_events);
|
||||
if (RCL_RET_OK != ret) {
|
||||
fail_ret = ret;
|
||||
goto fail;
|
||||
|
@ -328,6 +337,7 @@ rcl_wait_set_clear(rcl_wait_set_t * wait_set)
|
|||
SET_CLEAR(guard_condition);
|
||||
SET_CLEAR(client);
|
||||
SET_CLEAR(service);
|
||||
SET_CLEAR(event);
|
||||
SET_CLEAR(timer);
|
||||
|
||||
SET_CLEAR_RMW(
|
||||
|
@ -346,6 +356,10 @@ rcl_wait_set_clear(rcl_wait_set_t * wait_set)
|
|||
services,
|
||||
rmw_services.services,
|
||||
rmw_services.service_count);
|
||||
SET_CLEAR_RMW(
|
||||
events,
|
||||
rmw_events.events,
|
||||
rmw_events.event_count);
|
||||
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
@ -362,7 +376,8 @@ rcl_wait_set_resize(
|
|||
size_t guard_conditions_size,
|
||||
size_t timers_size,
|
||||
size_t clients_size,
|
||||
size_t services_size)
|
||||
size_t services_size,
|
||||
size_t events_size)
|
||||
{
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(wait_set, RCL_RET_INVALID_ARGUMENT);
|
||||
RCL_CHECK_ARGUMENT_FOR_NULL(wait_set->impl, RCL_RET_WAIT_SET_INVALID);
|
||||
|
@ -419,6 +434,13 @@ rcl_wait_set_resize(
|
|||
SET_RESIZE_RMW_REALLOC(
|
||||
service, rmw_services.services, rmw_services.service_count)
|
||||
);
|
||||
SET_RESIZE(event,
|
||||
SET_RESIZE_RMW_DEALLOC(
|
||||
rmw_events.events, rmw_events.event_count),
|
||||
SET_RESIZE_RMW_REALLOC(
|
||||
event, rmw_events.events, rmw_events.event_count)
|
||||
);
|
||||
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
|
@ -477,6 +499,18 @@ rcl_wait_set_add_service(
|
|||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_wait_set_add_event(
|
||||
rcl_wait_set_t * wait_set,
|
||||
const rcl_event_t * event,
|
||||
size_t * index)
|
||||
{
|
||||
SET_ADD(event)
|
||||
SET_ADD_RMW(event, rmw_events.events, rmw_events.event_count)
|
||||
wait_set->impl->rmw_events.events[current_index] = rmw_handle;
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
rcl_ret_t
|
||||
rcl_wait(rcl_wait_set_t * wait_set, int64_t timeout)
|
||||
{
|
||||
|
@ -490,7 +524,8 @@ rcl_wait(rcl_wait_set_t * wait_set, int64_t timeout)
|
|||
wait_set->size_of_guard_conditions == 0 &&
|
||||
wait_set->size_of_timers == 0 &&
|
||||
wait_set->size_of_clients == 0 &&
|
||||
wait_set->size_of_services == 0)
|
||||
wait_set->size_of_services == 0 &&
|
||||
wait_set->size_of_events == 0)
|
||||
{
|
||||
RCL_SET_ERROR_MSG("wait set is empty");
|
||||
return RCL_RET_WAIT_SET_EMPTY;
|
||||
|
@ -569,6 +604,7 @@ rcl_wait(rcl_wait_set_t * wait_set, int64_t timeout)
|
|||
&wait_set->impl->rmw_guard_conditions,
|
||||
&wait_set->impl->rmw_services,
|
||||
&wait_set->impl->rmw_clients,
|
||||
&wait_set->impl->rmw_events,
|
||||
wait_set->impl->rmw_wait_set,
|
||||
timeout_argument);
|
||||
|
||||
|
@ -631,6 +667,14 @@ rcl_wait(rcl_wait_set_t * wait_set, int64_t timeout)
|
|||
wait_set->services[i] = NULL;
|
||||
}
|
||||
}
|
||||
// Set corresponding rcl event handles NULL.
|
||||
for (i = 0; i < wait_set->size_of_events; ++i) {
|
||||
bool is_ready = wait_set->impl->rmw_events.events[i] != NULL;
|
||||
RCUTILS_LOG_DEBUG_EXPRESSION_NAMED(is_ready, ROS_PACKAGE_NAME, "Event in wait set is ready");
|
||||
if (!is_ready) {
|
||||
wait_set->events[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (RMW_RET_TIMEOUT == ret && !is_timer_timeout) {
|
||||
return RCL_RET_TIMEOUT;
|
||||
|
|
|
@ -199,6 +199,21 @@ function(test_target_function)
|
|||
AMENT_DEPENDENCIES ${rmw_implementation} "osrf_testing_tools_cpp" "test_msgs"
|
||||
)
|
||||
|
||||
# TODO(mm318): test_events seem to be failing on the macOS build farm.
|
||||
# we will try to re-enable this test asap.
|
||||
set(AMENT_GTEST_ARGS "")
|
||||
if(APPLE)
|
||||
set(AMENT_GTEST_ARGS "SKIP_TEST")
|
||||
endif()
|
||||
rcl_add_custom_gtest(test_events${target_suffix}
|
||||
SRCS rcl/test_events.cpp
|
||||
ENV ${rmw_implementation_env_var}
|
||||
APPEND_LIBRARY_DIRS ${extra_lib_dirs}
|
||||
LIBRARIES ${PROJECT_NAME}
|
||||
AMENT_DEPENDENCIES ${rmw_implementation} "osrf_testing_tools_cpp" "test_msgs"
|
||||
${AMENT_GTEST_ARGS}
|
||||
)
|
||||
|
||||
rcl_add_custom_gtest(test_wait${target_suffix}
|
||||
SRCS rcl/test_wait.cpp
|
||||
ENV ${rmw_implementation_env_var}
|
||||
|
|
|
@ -63,7 +63,8 @@ wait_for_client_to_be_ready(
|
|||
int64_t period_ms)
|
||||
{
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret = rcl_wait_set_init(&wait_set, 0, 0, 0, 1, 0, context, rcl_get_default_allocator());
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 0, 0, 1, 0, 0, context, rcl_get_default_allocator());
|
||||
if (ret != RCL_RET_OK) {
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
ROS_PACKAGE_NAME, "Error in wait set init: %s", rcl_get_error_string().str);
|
||||
|
|
|
@ -36,7 +36,8 @@ wait_for_service_to_be_ready(
|
|||
int64_t period_ms)
|
||||
{
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret = rcl_wait_set_init(&wait_set, 0, 0, 0, 0, 1, context, rcl_get_default_allocator());
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 0, 0, 0, 1, 0, context, rcl_get_default_allocator());
|
||||
if (ret != RCL_RET_OK) {
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
ROS_PACKAGE_NAME, "Error in wait set init: %s", rcl_get_error_string().str);
|
||||
|
|
|
@ -122,7 +122,7 @@ public:
|
|||
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, this->context_ptr, rcl_get_default_allocator());
|
||||
this->wait_set_ptr, 0, 1, 0, 0, 0, 0, this->context_ptr, rcl_get_default_allocator());
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
|
@ -194,7 +194,7 @@ TEST_F(CLASSNAME(TestCountFixture, RMW_IMPLEMENTATION),
|
|||
|
||||
rcl_publisher_t pub = rcl_get_zero_initialized_publisher();
|
||||
|
||||
rcl_publisher_options_t pub_ops;
|
||||
rcl_publisher_options_t pub_ops = rcl_publisher_get_default_options();
|
||||
pub_ops.qos.history = RMW_QOS_POLICY_HISTORY_KEEP_LAST;
|
||||
pub_ops.qos.depth = 10;
|
||||
pub_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
|
||||
|
@ -214,7 +214,7 @@ TEST_F(CLASSNAME(TestCountFixture, RMW_IMPLEMENTATION),
|
|||
|
||||
rcl_subscription_t sub = rcl_get_zero_initialized_subscription();
|
||||
|
||||
rcl_subscription_options_t sub_ops;
|
||||
rcl_subscription_options_t sub_ops = rcl_subscription_get_default_options();
|
||||
sub_ops.qos.history = RMW_QOS_POLICY_HISTORY_KEEP_LAST;
|
||||
sub_ops.qos.depth = 10;
|
||||
sub_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_RELIABLE;
|
||||
|
|
639
rcl/test/rcl/test_events.cpp
Normal file
639
rcl/test/rcl/test_events.cpp
Normal file
|
@ -0,0 +1,639 @@
|
|||
// Copyright 2019 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 <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "rcl/rcl.h"
|
||||
#include "rcl/subscription.h"
|
||||
#include "rcl/error_handling.h"
|
||||
|
||||
#include "test_msgs/msg/strings.h"
|
||||
#include "rosidl_generator_c/string_functions.h"
|
||||
|
||||
#include "osrf_testing_tools_cpp/scope_exit.hpp"
|
||||
|
||||
using std::chrono::milliseconds;
|
||||
using std::chrono::seconds;
|
||||
|
||||
constexpr seconds LIVELINESS_LEASE_DURATION_IN_S(1);
|
||||
constexpr seconds DEADLINE_PERIOD_IN_S(1);
|
||||
|
||||
#ifdef RMW_IMPLEMENTATION
|
||||
# define CLASSNAME_(NAME, SUFFIX) NAME ## __ ## SUFFIX
|
||||
# define CLASSNAME(NAME, SUFFIX) CLASSNAME_(NAME, SUFFIX)
|
||||
#else
|
||||
# define CLASSNAME(NAME, SUFFIX) NAME
|
||||
#endif
|
||||
|
||||
class CLASSNAME (TestEventFixture, RMW_IMPLEMENTATION) : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp()
|
||||
{
|
||||
is_opensplice = (std::string(rmw_get_implementation_identifier()).find("rmw_opensplice") == 0);
|
||||
is_fastrtps = (std::string(rmw_get_implementation_identifier()).find("rmw_fastrtps") == 0);
|
||||
|
||||
// TODO(mm318): Revisit once FastRTPS supports these QoS policies
|
||||
is_unsupported = is_fastrtps;
|
||||
|
||||
rcl_ret_t ret;
|
||||
{
|
||||
rcl_init_options_t init_options = rcl_get_zero_initialized_init_options();
|
||||
ret = rcl_init_options_init(&init_options, rcl_get_default_allocator());
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
EXPECT_EQ(RCL_RET_OK, rcl_init_options_fini(&init_options)) << rcl_get_error_string().str;
|
||||
});
|
||||
this->context_ptr = new rcl_context_t;
|
||||
*this->context_ptr = rcl_get_zero_initialized_context();
|
||||
ret = rcl_init(0, nullptr, &init_options, this->context_ptr);
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
this->node_ptr = new rcl_node_t;
|
||||
*this->node_ptr = rcl_get_zero_initialized_node();
|
||||
const char * name = "test_event_node";
|
||||
rcl_node_options_t node_options = rcl_node_get_default_options();
|
||||
ret = rcl_node_init(this->node_ptr, name, "", this->context_ptr, &node_options);
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
ts = ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
|
||||
}
|
||||
|
||||
rcl_ret_t setup_publisher(
|
||||
const rmw_time_t & deadline,
|
||||
const rmw_time_t & lifespan,
|
||||
const rmw_time_t & liveliness_lease_duration,
|
||||
const rmw_qos_liveliness_policy_t liveliness_policy)
|
||||
{
|
||||
// init publisher
|
||||
publisher = rcl_get_zero_initialized_publisher();
|
||||
rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
|
||||
publisher_options.qos.deadline = deadline;
|
||||
publisher_options.qos.lifespan = lifespan;
|
||||
publisher_options.qos.liveliness = liveliness_policy;
|
||||
publisher_options.qos.liveliness_lease_duration = liveliness_lease_duration;
|
||||
return rcl_publisher_init(
|
||||
&publisher,
|
||||
this->node_ptr,
|
||||
this->ts,
|
||||
this->topic,
|
||||
&publisher_options);
|
||||
}
|
||||
|
||||
rcl_ret_t setup_subscriber(
|
||||
const rmw_time_t & deadline,
|
||||
const rmw_time_t & lifespan,
|
||||
const rmw_time_t & liveliness_lease_duration,
|
||||
const rmw_qos_liveliness_policy_t liveliness_policy)
|
||||
{
|
||||
// init publisher
|
||||
subscription = rcl_get_zero_initialized_subscription();
|
||||
rcl_subscription_options_t subscription_options = rcl_subscription_get_default_options();
|
||||
subscription_options.qos.deadline = deadline;
|
||||
subscription_options.qos.lifespan = lifespan;
|
||||
subscription_options.qos.liveliness = liveliness_policy;
|
||||
subscription_options.qos.liveliness_lease_duration = liveliness_lease_duration;
|
||||
|
||||
return rcl_subscription_init(
|
||||
&subscription,
|
||||
this->node_ptr,
|
||||
this->ts,
|
||||
this->topic,
|
||||
&subscription_options);
|
||||
}
|
||||
|
||||
void setup_publisher_and_subscriber(
|
||||
const rcl_publisher_event_type_t & pub_event_type,
|
||||
const rcl_subscription_event_type_t & sub_event_type)
|
||||
{
|
||||
rcl_ret_t ret;
|
||||
|
||||
rmw_time_t lifespan {0, 0};
|
||||
rmw_time_t deadline {DEADLINE_PERIOD_IN_S.count(), 0};
|
||||
rmw_time_t lease_duration {LIVELINESS_LEASE_DURATION_IN_S.count(), 0};
|
||||
rmw_qos_liveliness_policy_t liveliness_policy = RMW_QOS_POLICY_LIVELINESS_AUTOMATIC;
|
||||
|
||||
// init publisher
|
||||
ret = setup_publisher(deadline, lifespan, lease_duration, liveliness_policy);
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
// init publisher events
|
||||
publisher_event = rcl_get_zero_initialized_event();
|
||||
ret = rcl_publisher_event_init(&publisher_event, &publisher, pub_event_type);
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
// init subscription
|
||||
ret = setup_subscriber(deadline, lifespan, lease_duration, liveliness_policy);
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
// init subscription event
|
||||
subscription_event = rcl_get_zero_initialized_event();
|
||||
ret = rcl_subscription_event_init(&subscription_event, &subscription, sub_event_type);
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
// wait for discovery
|
||||
// total wait time of 10 seconds, if never ready
|
||||
size_t max_iterations = 1000;
|
||||
milliseconds wait_period(10);
|
||||
size_t iteration = 0;
|
||||
do {
|
||||
iteration++;
|
||||
size_t count = 0;
|
||||
rcl_ret_t ret = rcl_subscription_get_publisher_count(&subscription, &count);
|
||||
ASSERT_EQ(ret, RCL_RET_OK);
|
||||
if (count > 0) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(wait_period);
|
||||
} while (iteration < max_iterations);
|
||||
}
|
||||
|
||||
void tear_down_publisher_subscriber()
|
||||
{
|
||||
rcl_ret_t ret;
|
||||
|
||||
ret = rcl_event_fini(&subscription_event);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_subscription_fini(&subscription, this->node_ptr);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_event_fini(&publisher_event);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_publisher_fini(&publisher, this->node_ptr);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
rcl_ret_t ret;
|
||||
|
||||
ret = rcl_node_fini(this->node_ptr);
|
||||
delete this->node_ptr;
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_shutdown(this->context_ptr);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_context_fini(this->context_ptr);
|
||||
delete this->context_ptr;
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
protected:
|
||||
rcl_context_t * context_ptr;
|
||||
rcl_node_t * node_ptr;
|
||||
|
||||
rcl_publisher_t publisher;
|
||||
rcl_event_t publisher_event;
|
||||
rcl_subscription_t subscription;
|
||||
rcl_event_t subscription_event;
|
||||
bool is_fastrtps;
|
||||
bool is_opensplice;
|
||||
bool is_unsupported;
|
||||
const char * topic = "rcl_test_publisher_subscription_events";
|
||||
const rosidl_message_type_support_t * ts;
|
||||
};
|
||||
|
||||
rcl_ret_t
|
||||
wait_for_msgs_and_events(
|
||||
rcl_subscription_t * subscription,
|
||||
rcl_event_t * subscription_event,
|
||||
rcl_event_t * publisher_event,
|
||||
rcl_context_t * context,
|
||||
int64_t period_ms,
|
||||
bool * msg_ready,
|
||||
bool * subscription_event_ready,
|
||||
bool * publisher_event_ready)
|
||||
{
|
||||
*msg_ready = false;
|
||||
*subscription_event_ready = false;
|
||||
*publisher_event_ready = false;
|
||||
|
||||
int num_subscriptions = (nullptr == subscription ? 0 : 1);
|
||||
int num_events = (nullptr == subscription_event ? 0 : 1) + (nullptr == publisher_event ? 0 : 1);
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret = rcl_wait_set_init(
|
||||
&wait_set,
|
||||
num_subscriptions, 0, 0, 0, 0, num_events,
|
||||
context,
|
||||
rcl_get_default_allocator());
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
rcl_ret_t ret = rcl_wait_set_fini(&wait_set);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
});
|
||||
|
||||
ret = rcl_wait_set_clear(&wait_set);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
if (nullptr != subscription) {
|
||||
ret = rcl_wait_set_add_subscription(&wait_set, subscription, NULL);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
if (nullptr != subscription_event) {
|
||||
ret = rcl_wait_set_add_event(&wait_set, subscription_event, NULL);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
if (nullptr != publisher_event) {
|
||||
ret = rcl_wait_set_add_event(&wait_set, publisher_event, NULL);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
ret = rcl_wait(&wait_set, RCL_MS_TO_NS(period_ms));
|
||||
if (ret == RCL_RET_TIMEOUT) {
|
||||
return ret;
|
||||
}
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
for (size_t i = 0; i < wait_set.size_of_subscriptions; ++i) {
|
||||
if (wait_set.subscriptions[i] && wait_set.subscriptions[i] == subscription) {
|
||||
*msg_ready = true;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < wait_set.size_of_events; ++i) {
|
||||
if (nullptr != wait_set.events[i]) {
|
||||
if (wait_set.events[i] == subscription_event) {
|
||||
*subscription_event_ready = true;
|
||||
} else if (wait_set.events[i] == publisher_event) {
|
||||
*publisher_event_ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
/// Conditional function for determining when the wait_for_msgs_and_events loop is complete.
|
||||
/**
|
||||
* Conditional function for determining when the wait_for_msgs_and_events loop is complete.
|
||||
*
|
||||
* \param msg_persist_ready true if a msg has ever been received
|
||||
* \param subscription_persist_readytrue if a subscription has been received
|
||||
* \param publisher_persist_ready true if a pulbisher event has been received
|
||||
* \return true when the desired conditions are met
|
||||
*/
|
||||
using WaitConditionPredicate = std::function<
|
||||
bool (
|
||||
const bool & msg_persist_ready,
|
||||
const bool & subscription_persist_ready,
|
||||
const bool & publisher_persist_ready
|
||||
)>;
|
||||
|
||||
// Wait for msgs and events until all conditions are met or a timeout has occured.
|
||||
template<typename S, typename P>
|
||||
rcl_ret_t
|
||||
conditional_wait_for_msgs_and_events(
|
||||
const WaitConditionPredicate & events_ready,
|
||||
rcl_subscription_t * subscription,
|
||||
rcl_event_t * subscription_event,
|
||||
rcl_event_t * publisher_event,
|
||||
rcl_context_t * context,
|
||||
int64_t period_ms,
|
||||
bool * msg_persist_ready,
|
||||
bool * subscription_persist_ready,
|
||||
bool * publisher_persist_ready,
|
||||
test_msgs__msg__Strings * msg,
|
||||
S * subscription_discrete_event,
|
||||
P * publisher_discrete_event)
|
||||
{
|
||||
*msg_persist_ready = false;
|
||||
*subscription_persist_ready = false;
|
||||
*publisher_persist_ready = false;
|
||||
auto timeout = milliseconds(2000);
|
||||
auto start_time = std::chrono::system_clock::now();
|
||||
bool msg_ready, subscription_event_ready, publisher_event_ready;
|
||||
do {
|
||||
// wait for msg and events
|
||||
wait_for_msgs_and_events(subscription, subscription_event, publisher_event,
|
||||
context, period_ms, &msg_ready, &subscription_event_ready, &publisher_event_ready);
|
||||
// test that the message published to topic is as expected
|
||||
if (msg_ready) {
|
||||
EXPECT_EQ(rcl_take(subscription, msg, nullptr, nullptr), RCL_RET_OK);
|
||||
}
|
||||
if (subscription_event_ready && subscription_discrete_event) {
|
||||
EXPECT_EQ(rcl_take_event(subscription_event, subscription_discrete_event), RCL_RET_OK);
|
||||
}
|
||||
if (publisher_event_ready && publisher_discrete_event) {
|
||||
EXPECT_EQ(rcl_take_event(publisher_event, publisher_discrete_event), RCL_RET_OK);
|
||||
}
|
||||
*msg_persist_ready |= msg_ready;
|
||||
*subscription_persist_ready |= subscription_event_ready;
|
||||
*publisher_persist_ready |= publisher_event_ready;
|
||||
if (std::chrono::system_clock::now() - start_time > timeout) {
|
||||
return RCL_RET_TIMEOUT;
|
||||
}
|
||||
} while (!(events_ready(*msg_persist_ready,
|
||||
*subscription_persist_ready,
|
||||
*publisher_persist_ready)));
|
||||
return RCL_RET_OK;
|
||||
}
|
||||
|
||||
TEST_F(CLASSNAME(TestEventFixture, RMW_IMPLEMENTATION), test_unsupported_lifespan) {
|
||||
if (is_unsupported) {
|
||||
rmw_time_t deadline {0, 0};
|
||||
rmw_time_t lifespan {1, 0};
|
||||
rmw_time_t lease_duration {1, 0};
|
||||
rmw_qos_liveliness_policy_t liveliness_policy = RMW_QOS_POLICY_LIVELINESS_AUTOMATIC;
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_subscriber(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized subscriber lifespan when unsupported";
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_publisher(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized publisher lifespan when unsupported";
|
||||
|
||||
lifespan = {0, 1};
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_subscriber(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized subscriber lifespan when unsupported";
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_publisher(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized publisher lifespan when unsupported";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CLASSNAME(TestEventFixture, RMW_IMPLEMENTATION), test_unsupported_liveliness) {
|
||||
if (is_unsupported) {
|
||||
rmw_time_t deadline {0, 0};
|
||||
rmw_time_t lifespan {0, 0};
|
||||
rmw_time_t lease_duration {0, 0};
|
||||
rmw_qos_liveliness_policy_t liveliness_policy = RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_NODE;
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_subscriber(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) <<
|
||||
"Initialized subscriber RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_NODE when unsupported";
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_publisher(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) <<
|
||||
"Initialized publisher RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_NODE when unsupported";
|
||||
|
||||
liveliness_policy = RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC;
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_subscriber(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) <<
|
||||
"Initialized subscriber RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC when unsupported";
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_publisher(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) <<
|
||||
"Initialized publisher RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC when unsupported";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CLASSNAME(TestEventFixture, RMW_IMPLEMENTATION), test_unsupported_unsupported_deadline) {
|
||||
if (is_unsupported) {
|
||||
rmw_time_t deadline {1, 0};
|
||||
rmw_time_t lifespan {0, 0};
|
||||
rmw_time_t lease_duration {0, 0};
|
||||
rmw_qos_liveliness_policy_t liveliness_policy = RMW_QOS_POLICY_LIVELINESS_AUTOMATIC;
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_subscriber(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized subscriber deadline when unsupported";
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_publisher(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized publisher deadline when unsupported";
|
||||
|
||||
deadline = {0, 1};
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_subscriber(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized subscriber deadline when unsupported";
|
||||
EXPECT_EQ(RCL_RET_ERROR,
|
||||
setup_publisher(deadline, lifespan, lease_duration,
|
||||
liveliness_policy)) << "Initialized publisher deadline when unsupported";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic test of publisher and subscriber liveliness events, with publisher killed
|
||||
*/
|
||||
TEST_F(CLASSNAME(TestEventFixture, RMW_IMPLEMENTATION), test_pubsub_liveliness_kill_pub)
|
||||
{
|
||||
if (is_unsupported) {
|
||||
return;
|
||||
}
|
||||
setup_publisher_and_subscriber(RCL_PUBLISHER_LIVELINESS_LOST,
|
||||
RCL_SUBSCRIPTION_LIVELINESS_CHANGED);
|
||||
rcl_ret_t ret;
|
||||
|
||||
// publish message to topic
|
||||
const char * test_string = "testing";
|
||||
{
|
||||
test_msgs__msg__Strings msg;
|
||||
test_msgs__msg__Strings__init(&msg);
|
||||
ASSERT_TRUE(rosidl_generator_c__String__assign(&msg.string_value, test_string));
|
||||
ret = rcl_publish(&publisher, &msg, nullptr);
|
||||
test_msgs__msg__Strings__fini(&msg);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
// kill the publisher
|
||||
ret = rcl_event_fini(&publisher_event);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
ret = rcl_publisher_fini(&publisher, this->node_ptr);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
||||
WaitConditionPredicate msg_and_subevent_ready = [](
|
||||
const bool & msg_persist_ready,
|
||||
const bool & subscription_persist_ready,
|
||||
const bool &) {
|
||||
return msg_persist_ready && subscription_persist_ready;
|
||||
};
|
||||
test_msgs__msg__Strings msg;
|
||||
test_msgs__msg__Strings__init(&msg);
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
test_msgs__msg__Strings__fini(&msg);
|
||||
});
|
||||
rmw_liveliness_changed_status_t liveliness_status;
|
||||
bool msg_persist_ready, subscription_persist_ready, publisher_persist_ready;
|
||||
rcl_ret_t wait_res = conditional_wait_for_msgs_and_events<rmw_liveliness_changed_status_t, void>(
|
||||
msg_and_subevent_ready, &subscription, &subscription_event, nullptr, context_ptr, 1000,
|
||||
&msg_persist_ready, &subscription_persist_ready, &publisher_persist_ready,
|
||||
&msg, &liveliness_status, nullptr);
|
||||
EXPECT_EQ(wait_res, RCL_RET_OK);
|
||||
|
||||
// test that the message published to topic is as expected
|
||||
EXPECT_TRUE(msg_persist_ready);
|
||||
// test subscriber/datareader liveliness changed status
|
||||
EXPECT_TRUE(subscription_persist_ready);
|
||||
// test that the killed publisher/datawriter has no active events
|
||||
EXPECT_FALSE(publisher_persist_ready);
|
||||
|
||||
if (msg_persist_ready) {
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(std::string(msg.string_value.data, msg.string_value.size),
|
||||
std::string(test_string));
|
||||
}
|
||||
if (subscription_persist_ready) {
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(liveliness_status.alive_count, 0);
|
||||
// TODO(mm3188): Connext and OpenSplice seem to be tracking alive_count_change differently.
|
||||
// Issue has been raised at https://github.com/ADLINK-IST/opensplice/issues/88
|
||||
if (is_opensplice) {
|
||||
EXPECT_EQ(liveliness_status.alive_count_change, 2);
|
||||
} else {
|
||||
EXPECT_EQ(liveliness_status.alive_count_change, 0);
|
||||
}
|
||||
EXPECT_EQ(liveliness_status.not_alive_count, 0);
|
||||
EXPECT_EQ(liveliness_status.not_alive_count_change, 0);
|
||||
}
|
||||
|
||||
// clean up
|
||||
ret = rcl_event_fini(&subscription_event);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
ret = rcl_subscription_fini(&subscription, this->node_ptr);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic test of publisher and subscriber deadline events, with first message sent after deadline
|
||||
*/
|
||||
TEST_F(CLASSNAME(TestEventFixture, RMW_IMPLEMENTATION), test_pubsub_deadline_missed)
|
||||
{
|
||||
if (is_unsupported) {
|
||||
return;
|
||||
}
|
||||
setup_publisher_and_subscriber(RCL_PUBLISHER_OFFERED_DEADLINE_MISSED,
|
||||
RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED);
|
||||
rcl_ret_t ret;
|
||||
|
||||
// publish message to topic
|
||||
const char * test_string = "testing";
|
||||
{
|
||||
test_msgs__msg__Strings msg;
|
||||
test_msgs__msg__Strings__init(&msg);
|
||||
ASSERT_TRUE(rosidl_generator_c__String__assign(&msg.string_value, test_string));
|
||||
ret = rcl_publish(&publisher, &msg, nullptr);
|
||||
test_msgs__msg__Strings__fini(&msg);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
WaitConditionPredicate all_ready = [](
|
||||
const bool & msg_persist_ready,
|
||||
const bool & subscription_persist_ready,
|
||||
const bool & publisher_persist_ready) {
|
||||
return msg_persist_ready && subscription_persist_ready && publisher_persist_ready;
|
||||
};
|
||||
test_msgs__msg__Strings msg;
|
||||
test_msgs__msg__Strings__init(&msg);
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
test_msgs__msg__Strings__fini(&msg);
|
||||
});
|
||||
rmw_offered_deadline_missed_status_t offered_deadline_status;
|
||||
rmw_requested_deadline_missed_status_t requested_deadline_status;
|
||||
bool msg_persist_ready, subscription_persist_ready, publisher_persist_ready;
|
||||
rcl_ret_t wait_res = conditional_wait_for_msgs_and_events(
|
||||
all_ready, &subscription, &subscription_event, &publisher_event, context_ptr, 1000,
|
||||
&msg_persist_ready, &subscription_persist_ready, &publisher_persist_ready,
|
||||
&msg, &requested_deadline_status, &offered_deadline_status);
|
||||
EXPECT_EQ(wait_res, RCL_RET_OK);
|
||||
|
||||
// test that the message published to topic is as expected
|
||||
if (msg_persist_ready) {
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(std::string(msg.string_value.data, msg.string_value.size),
|
||||
std::string(test_string));
|
||||
}
|
||||
|
||||
if (subscription_persist_ready) {
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(requested_deadline_status.total_count, 1);
|
||||
EXPECT_EQ(requested_deadline_status.total_count_change, 1);
|
||||
}
|
||||
|
||||
if (publisher_persist_ready) {
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(offered_deadline_status.total_count, 1);
|
||||
EXPECT_EQ(offered_deadline_status.total_count_change, 1);
|
||||
}
|
||||
// test that the message published to topic is as expected
|
||||
EXPECT_TRUE(msg_persist_ready);
|
||||
|
||||
// test subscriber/datareader deadline missed status
|
||||
EXPECT_TRUE(subscription_persist_ready);
|
||||
|
||||
// test publisher/datawriter deadline missed status
|
||||
EXPECT_TRUE(publisher_persist_ready);
|
||||
|
||||
// clean up
|
||||
tear_down_publisher_subscriber();
|
||||
}
|
||||
|
||||
/*
|
||||
* Basic test of publisher and subscriber deadline events, with first message sent before deadline
|
||||
*/
|
||||
TEST_F(CLASSNAME(TestEventFixture, RMW_IMPLEMENTATION), test_pubsub_no_deadline_missed)
|
||||
{
|
||||
if (is_unsupported) {
|
||||
return;
|
||||
}
|
||||
setup_publisher_and_subscriber(RCL_PUBLISHER_OFFERED_DEADLINE_MISSED,
|
||||
RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED);
|
||||
rcl_ret_t ret;
|
||||
|
||||
// publish message to topic
|
||||
const char * test_string = "testing";
|
||||
{
|
||||
test_msgs__msg__Strings msg;
|
||||
test_msgs__msg__Strings__init(&msg);
|
||||
ASSERT_TRUE(rosidl_generator_c__String__assign(&msg.string_value, test_string));
|
||||
ret = rcl_publish(&publisher, &msg, nullptr);
|
||||
test_msgs__msg__Strings__fini(&msg);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
// wait for msg and events
|
||||
bool msg_ready, subscription_event_ready, publisher_event_ready;
|
||||
EXPECT_EQ(wait_for_msgs_and_events(&subscription, &subscription_event, &publisher_event,
|
||||
context_ptr, 1000, &msg_ready, &subscription_event_ready, &publisher_event_ready), RCL_RET_OK);
|
||||
|
||||
// test that the message published to topic is as expected
|
||||
EXPECT_TRUE(msg_ready);
|
||||
{
|
||||
test_msgs__msg__Strings msg;
|
||||
test_msgs__msg__Strings__init(&msg);
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
test_msgs__msg__Strings__fini(&msg);
|
||||
});
|
||||
ret = rcl_take(&subscription, &msg, nullptr, nullptr);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(std::string(msg.string_value.data, msg.string_value.size), std::string(test_string));
|
||||
}
|
||||
|
||||
// test subscriber/datareader deadline missed status
|
||||
EXPECT_FALSE(subscription_event_ready);
|
||||
{
|
||||
rmw_requested_deadline_missed_status_t deadline_status;
|
||||
ret = rcl_take_event(&subscription_event, &deadline_status);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(deadline_status.total_count, 0);
|
||||
EXPECT_EQ(deadline_status.total_count_change, 0);
|
||||
}
|
||||
|
||||
// test publisher/datawriter deadline missed status
|
||||
EXPECT_FALSE(publisher_event_ready);
|
||||
{
|
||||
rmw_offered_deadline_missed_status_t deadline_status;
|
||||
ret = rcl_take_event(&publisher_event, &deadline_status);
|
||||
EXPECT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
EXPECT_EQ(deadline_status.total_count, 0);
|
||||
EXPECT_EQ(deadline_status.total_count_change, 0);
|
||||
}
|
||||
|
||||
// clean up
|
||||
tear_down_publisher_subscriber();
|
||||
}
|
|
@ -70,9 +70,7 @@ class TEST_FIXTURE_P_RMW (TestGetActualQoS)
|
|||
: public ::testing::TestWithParam<TestParameters>
|
||||
{
|
||||
public:
|
||||
rcl_node_t * node_ptr;
|
||||
rcl_context_t * context_ptr;
|
||||
void SetUp()
|
||||
void SetUp() override
|
||||
{
|
||||
rcl_ret_t ret;
|
||||
rcl_node_options_t node_options = rcl_node_get_default_options();
|
||||
|
@ -91,7 +89,7 @@ public:
|
|||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
void TearDown() override
|
||||
{
|
||||
rcl_ret_t ret;
|
||||
|
||||
|
@ -106,6 +104,10 @@ public:
|
|||
delete this->context_ptr;
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
protected:
|
||||
rcl_node_t * node_ptr;
|
||||
rcl_context_t * context_ptr;
|
||||
};
|
||||
|
||||
TEST_P_RMW(TestGetActualQoS, test_publisher_get_qos_settings) {
|
||||
|
@ -199,8 +201,8 @@ get_parameters()
|
|||
|
||||
#ifdef RMW_IMPLEMENTATION_STR
|
||||
std::string rmw_implementation_str = RMW_IMPLEMENTATION_STR;
|
||||
if (!rmw_implementation_str.compare("rmw_fastrtps_cpp") ||
|
||||
!rmw_implementation_str.compare("rmw_fastrtps_dynamic_cpp"))
|
||||
if (rmw_implementation_str == "rmw_fastrtps_cpp" ||
|
||||
rmw_implementation_str == "rmw_fastrtps_dynamic_cpp")
|
||||
{
|
||||
rmw_qos_profile_t expected_system_default_qos = expected_fastrtps_default_qos_profile();
|
||||
parameters.push_back({
|
||||
|
@ -208,9 +210,9 @@ get_parameters()
|
|||
expected_system_default_qos,
|
||||
"publisher_system_default_qos"});
|
||||
} else {
|
||||
if (!rmw_implementation_str.compare("rmw_opensplice_cpp") ||
|
||||
!rmw_implementation_str.compare("rmw_connext_cpp") ||
|
||||
!rmw_implementation_str.compare("rmw_connext_dynamic_cpp"))
|
||||
if (rmw_implementation_str == "rmw_connext_cpp" ||
|
||||
rmw_implementation_str == "rmw_connext_dynamic_cpp" ||
|
||||
rmw_implementation_str == "rmw_opensplice_cpp")
|
||||
{
|
||||
rmw_qos_profile_t expected_system_default_qos = expected_system_default_qos_profile();
|
||||
parameters.push_back({
|
||||
|
|
|
@ -99,7 +99,7 @@ public:
|
|||
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, this->context_ptr, rcl_get_default_allocator());
|
||||
this->wait_set_ptr, 0, 1, 0, 0, 0, 0, this->context_ptr, rcl_get_default_allocator());
|
||||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,8 @@ wait_for_service_to_be_ready(
|
|||
bool & success)
|
||||
{
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret = rcl_wait_set_init(&wait_set, 0, 0, 0, 0, 1, context, rcl_get_default_allocator());
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 0, 0, 0, 1, 0, context, rcl_get_default_allocator());
|
||||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
rcl_ret_t ret = rcl_wait_set_fini(&wait_set);
|
||||
|
|
|
@ -85,7 +85,8 @@ wait_for_subscription_to_be_ready(
|
|||
bool & success)
|
||||
{
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret = rcl_wait_set_init(&wait_set, 1, 0, 0, 0, 0, context, rcl_get_default_allocator());
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 1, 0, 0, 0, 0, 0, context, rcl_get_default_allocator());
|
||||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT({
|
||||
rcl_ret_t ret = rcl_wait_set_fini(&wait_set);
|
||||
|
|
|
@ -84,7 +84,7 @@ TEST_F(TestTimerFixture, test_two_timers) {
|
|||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 2, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 2, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_wait_set_add_timer(&wait_set, &timer, NULL);
|
||||
|
@ -140,7 +140,7 @@ TEST_F(TestTimerFixture, test_two_timers_ready_before_timeout) {
|
|||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 2, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 2, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_wait_set_add_timer(&wait_set, &timer, NULL);
|
||||
|
@ -191,7 +191,7 @@ TEST_F(TestTimerFixture, test_timer_not_ready) {
|
|||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_wait_set_add_timer(&wait_set, &timer, NULL);
|
||||
|
@ -239,7 +239,7 @@ TEST_F(TestTimerFixture, test_canceled_timer) {
|
|||
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_wait_set_add_timer(&wait_set, &timer, NULL);
|
||||
|
@ -459,7 +459,11 @@ TEST_F(TestTimerFixture, test_ros_time_wakes_wait) {
|
|||
|
||||
std::thread wait_thr([&](void) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ret = rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
ret = rcl_wait_set_init(
|
||||
&wait_set,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
context_ptr,
|
||||
rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
ASSERT_EQ(RCL_RET_OK, rcl_wait_set_add_timer(&wait_set, &timer, NULL)) <<
|
||||
|
|
|
@ -69,10 +69,10 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), test_resize_to_zero) {
|
|||
// Initialize a wait set with a subscription and then resize it to zero.
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 1, 1, 1, 1, 1, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 1, 1, 1, 1, 1, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
ret = rcl_wait_set_resize(&wait_set, 0u, 0u, 0u, 0u, 0u);
|
||||
ret = rcl_wait_set_resize(&wait_set, 0u, 0u, 0u, 0u, 0u, 0u);
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
EXPECT_EQ(wait_set.size_of_subscriptions, 0ull);
|
||||
|
@ -89,7 +89,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), test_resize_to_zero) {
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), finite_timeout) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 0, 0, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
int64_t timeout = RCL_MS_TO_NS(10); // nanoseconds
|
||||
|
@ -109,7 +109,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), finite_timeout) {
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), negative_timeout) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
// Add a dummy guard condition to avoid an error
|
||||
|
@ -156,7 +156,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), negative_timeout) {
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), zero_timeout) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
// Add a dummy guard condition to avoid an error
|
||||
|
@ -189,7 +189,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), zero_timeout) {
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), zero_timeout_triggered_guard_condition) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 0, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
rcl_guard_condition_t guard_cond = rcl_get_zero_initialized_guard_condition();
|
||||
|
@ -223,7 +223,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), zero_timeout_triggered
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), canceled_timer) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 1, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
// Add a dummy guard condition to avoid an error
|
||||
|
@ -276,7 +276,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), canceled_timer) {
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), excess_capacity) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 42, 42, 42, 42, 42, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 42, 42, 42, 42, 42, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
int64_t timeout = 1;
|
||||
|
@ -361,7 +361,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), multi_wait_set_threade
|
|||
// setup the wait set
|
||||
test_set.wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ret = rcl_wait_set_init(
|
||||
&test_set.wait_set, 0, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
&test_set.wait_set, 0, 1, 0, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
ret = rcl_wait_set_add_guard_condition(&test_set.wait_set, &test_set.guard_condition, NULL);
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
@ -419,7 +419,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), multi_wait_set_threade
|
|||
TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), guard_condition) {
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret =
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
rcl_wait_set_init(&wait_set, 0, 1, 0, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
rcl_guard_condition_t guard_cond = rcl_get_zero_initialized_guard_condition();
|
||||
ret = rcl_guard_condition_init(
|
||||
|
@ -467,7 +467,7 @@ TEST_F(CLASSNAME(WaitSetTestFixture, RMW_IMPLEMENTATION), add_with_index) {
|
|||
const size_t kNumEntities = 3u;
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_ret_t ret = rcl_wait_set_init(
|
||||
&wait_set, 0, kNumEntities, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
&wait_set, 0, kNumEntities, 0, 0, 0, 0, context_ptr, rcl_get_default_allocator());
|
||||
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
|
||||
|
||||
// Initialize to invalid value
|
||||
|
|
|
@ -98,6 +98,7 @@ protected:
|
|||
num_timers_server + num_timers_client,
|
||||
num_clients_server + num_clients_client,
|
||||
num_services_server + num_services_client,
|
||||
0 /* num_events_server + num_events_client */,
|
||||
&context,
|
||||
rcl_get_default_allocator());
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
|
|
@ -110,6 +110,7 @@ protected:
|
|||
num_timers_server + num_timers_client,
|
||||
num_clients_server + num_clients_client,
|
||||
num_services_server + num_services_client,
|
||||
0 /* num_events_server + num_events_client */,
|
||||
&context,
|
||||
rcl_get_default_allocator());
|
||||
ASSERT_EQ(ret, RCL_RET_OK) << rcl_get_error_string().str;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue