diff --git a/rcl/package.xml b/rcl/package.xml
index 6334f95bb..a28382aff 100644
--- a/rcl/package.xml
+++ b/rcl/package.xml
@@ -25,6 +25,7 @@
ament_cmake_gtest
ament_lint_auto
ament_lint_common
+ mimick_vendor
rcpputils
rmw
rmw_implementation_cmake
diff --git a/rcl/test/CMakeLists.txt b/rcl/test/CMakeLists.txt
index 9e0d85fdd..c177d2ed2 100644
--- a/rcl/test/CMakeLists.txt
+++ b/rcl/test/CMakeLists.txt
@@ -3,6 +3,8 @@ find_package(launch_testing_ament_cmake REQUIRED)
find_package(test_msgs REQUIRED)
+find_package(mimick_vendor REQUIRED)
+
find_package(rcpputils REQUIRED)
find_package(rcutils REQUIRED)
find_package(rmw_implementation_cmake REQUIRED)
@@ -207,7 +209,7 @@ function(test_target_function)
ENV ${rmw_implementation_env_var}
APPEND_LIBRARY_DIRS ${extra_lib_dirs}
INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../src/rcl/
- LIBRARIES ${PROJECT_NAME}
+ LIBRARIES ${PROJECT_NAME} mimick
AMENT_DEPENDENCIES ${rmw_implementation} "osrf_testing_tools_cpp" "test_msgs"
)
diff --git a/rcl/test/mocking_utils/patch.hpp b/rcl/test/mocking_utils/patch.hpp
new file mode 100644
index 000000000..544dec27a
--- /dev/null
+++ b/rcl/test/mocking_utils/patch.hpp
@@ -0,0 +1,355 @@
+// Copyright 2020 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.
+
+// Original file taken from:
+// https://github.com/ros2/rcutils/blob/master/test/mocking_utils/patch.hpp
+
+#ifndef MOCKING_UTILS__PATCH_HPP_
+#define MOCKING_UTILS__PATCH_HPP_
+
+#define MOCKING_UTILS_SUPPORT_VA_LIST
+#if (defined(__aarch64__) || defined(__arm__) || defined(_M_ARM) || defined(__thumb__))
+// In ARM machines, va_list does not define comparison operators
+// nor the compiler allows defining them via operator overloads.
+// Thus, Mimick argument matching code will not compile.
+#undef MOCKING_UTILS_SUPPORT_VA_LIST
+#endif
+
+#ifdef MOCKING_UTILS_SUPPORT_VA_LIST
+#include
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "mimick/mimick.h"
+#include "rcutils/macros.h"
+
+namespace mocking_utils
+{
+
+/// Mimick specific traits for each mocking_utils::Patch instance.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam SignatureT Type of the symbol to be patched.
+*/
+template
+struct PatchTraits;
+
+/// Traits specialization for ReturnT(void) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT);
+};
+
+/// Traits specialization for ReturnT(ArgT0) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgT0 Argument type.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2, ArgT3) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4)
+/// free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5)
+/// free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(
+ mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5);
+};
+
+/// Generic trampoline to wrap generalized callables in plain functions.
+/**
+ * \tparam ID Numerical identifier of this trampoline. Ought to be unique.
+ * \tparam SignatureT Type of the symbol this trampoline replaces.
+ */
+template
+struct Trampoline;
+
+/// Trampoline specialization for free functions.
+template
+struct Trampoline
+{
+ static ReturnT base(ArgTs... args)
+ {
+ return target(std::forward(args)...);
+ }
+
+ static std::function target;
+};
+
+template
+std::function
+Trampoline::target;
+
+/// Setup trampoline with the given @p target.
+/**
+ * \param[in] target Callable that this trampoline will target.
+ * \return the plain base function of this trampoline.
+ *
+ * \tparam ID Numerical identifier of this trampoline. Ought to be unique.
+ * \tparam SignatureT Type of the symbol this trampoline replaces.
+ */
+template
+auto prepare_trampoline(std::function target)
+{
+ Trampoline::target = target;
+ return Trampoline::base;
+}
+
+/// Patch class for binary API mocking
+/**
+ * Built on top of Mimick, to enable symbol mocking on a per dynamically
+ * linked binary object basis.
+ *
+ * \tparam ID Numerical identifier for this patch. Ought to be unique.
+ * \tparam SignatureT Type of the symbol to be patched.
+ */
+template
+class Patch;
+
+/// Patch specialization for ReturnT(ArgTs...) free functions.
+/**
+ * \tparam ID Numerical identifier for this patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTs Argument types.
+ */
+template
+class Patch
+{
+public:
+ using mock_type = typename PatchTraits::mock_type;
+
+ /// Construct a patch.
+ /**
+ * \param[in] target Symbol target string, using Mimick syntax
+ * i.e. "symbol(@scope)?", where scope may be "self" to target the current
+ * binary, "lib:library_name" to target a given library, "file:path/to/library"
+ * to target a given file, or "sym:other_symbol" to target the first library
+ * that defines said symbol.
+ * \param[in] proxy An indirection to call the target function.
+ * This indirection must ensure this call goes through the function's
+ * trampoline, as setup by the dynamic linker.
+ * \return a mocking_utils::Patch instance.
+ */
+ explicit Patch(const std::string & target, std::function proxy)
+ : proxy_(proxy)
+ {
+ auto MMK_MANGLE(mock_type, create) =
+ PatchTraits::MMK_MANGLE(mock_type, create);
+ mock_ = mmk_mock(target.c_str(), mock_type);
+ }
+
+ // Copy construction and assignment are disabled.
+ Patch(const Patch &) = delete;
+ Patch & operator=(const Patch &) = delete;
+
+ Patch(Patch && other)
+ {
+ mock_ = other.mock_;
+ other.mock_ = nullptr;
+ }
+
+ Patch & operator=(Patch && other)
+ {
+ if (mock_) {
+ mmk_reset(mock_);
+ }
+ mock_ = other.mock_;
+ other.mock_ = nullptr;
+ }
+
+ ~Patch()
+ {
+ if (mock_) {
+ mmk_reset(mock_);
+ }
+ }
+
+ /// Inject a @p replacement for the patched function.
+ Patch & then_call(std::function replacement) &
+ {
+ auto type_erased_trampoline =
+ reinterpret_cast(prepare_trampoline(replacement));
+ mmk_when(proxy_(any()...), .then_call = type_erased_trampoline);
+ return *this;
+ }
+
+ /// Inject a @p replacement for the patched function.
+ Patch && then_call(std::function replacement) &&
+ {
+ auto type_erased_trampoline =
+ reinterpret_cast(prepare_trampoline(replacement));
+ mmk_when(proxy_(any()...), .then_call = type_erased_trampoline);
+ return std::move(*this);
+ }
+
+private:
+ // Helper for template parameter pack expansion using `mmk_any`
+ // macro as pattern.
+ template
+ T any() {return mmk_any(T);}
+
+ mock_type mock_;
+ std::function proxy_;
+};
+
+/// Make a patch for a `target` function.
+/**
+ * Useful for type deduction during \ref mocking_utils::Patch construction.
+ *
+ * \param[in] target Symbol target string, using Mimick syntax.
+ * \param[in] proxy An indirection to call the target function.
+ * \return a mocking_utils::Patch instance.
+ *
+ * \tparam ID Numerical identifier for this patch. Ought to be unique.
+ * \tparam SignatureT Type of the function to be patched.
+ *
+ * \sa mocking_utils::Patch for further reference.
+ */
+template
+auto make_patch(const std::string & target, std::function proxy)
+{
+ return Patch(target, proxy);
+}
+
+/// Define a dummy operator `op` for a given `type`.
+/**
+ * Useful to enable patching functions that take arguments whose types
+ * do not define basic comparison operators, as required by Mimick.
+*/
+#define MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(type_, op) \
+ template \
+ typename std::enable_if::value, bool>::type \
+ operator op(const T &, const T &) { \
+ return false; \
+ }
+
+/// Get the exact \ref mocking_utils::Patch type for a given `id` and `function`.
+/**
+ * Useful to avoid ignored attribute warnings when using the \b decltype operator.
+ */
+#define MOCKING_UTILS_PATCH_TYPE(id, function) \
+ decltype(mocking_utils::make_patch("", nullptr))
+
+/// A transparent forwarding proxy to a given `function`.
+/**
+ * Useful to ensure a call to `function` goes through its trampoline.
+ */
+#define MOCKING_UTILS_PATCH_PROXY(function) \
+ [] (auto && ... args)->decltype(auto) { \
+ return function(std::forward(args)...); \
+ }
+
+/// Compute a Mimick symbol target string based on which `function` is to be patched
+/// in which `scope`.
+#define MOCKING_UTILS_PATCH_TARGET(scope, function) \
+ (std::string(RCUTILS_STRINGIFY(function)) + "@" + (scope))
+
+/// Patch a `function` with a used-provided `replacement` in a given `scope`.
+#define patch(scope, function, replacement) \
+ make_patch<__COUNTER__, decltype(function)>( \
+ MOCKING_UTILS_PATCH_TARGET(scope, function), MOCKING_UTILS_PATCH_PROXY(function) \
+ ).then_call(replacement)
+
+/// Patch a function with a function that only returns a value
+#define patch_and_return(scope, function, return_value) \
+ patch(scope, function, [&](auto && ...) {return return_value;})
+
+} // namespace mocking_utils
+
+#ifdef MOCKING_UTILS_SUPPORT_VA_LIST
+// Define dummy comparison operators for C standard va_list type
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, ==)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, !=)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, <)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, >)
+#endif
+
+#endif // MOCKING_UTILS__PATCH_HPP_
diff --git a/rcl/test/rcl/test_publisher.cpp b/rcl/test/rcl/test_publisher.cpp
index 40cc36c12..c536f9242 100644
--- a/rcl/test/rcl/test_publisher.cpp
+++ b/rcl/test/rcl/test_publisher.cpp
@@ -21,11 +21,15 @@
#include "test_msgs/msg/strings.h"
#include "rosidl_runtime_c/string_functions.h"
-#include "./failing_allocator_functions.hpp"
+#include "mimick/mimick.h"
#include "osrf_testing_tools_cpp/scope_exit.hpp"
#include "rcl/error_handling.h"
+#include "rmw/validate_full_topic_name.h"
+#include "rmw/validate_node_name.h"
+#include "./failing_allocator_functions.hpp"
#include "./publisher_impl.h"
+#include "../mocking_utils/patch.hpp"
#ifdef RMW_IMPLEMENTATION
# define CLASSNAME_(NAME, SUFFIX) NAME ## __ ## SUFFIX
@@ -57,7 +61,7 @@ class CLASSNAME (TestPublisherFixture, RMW_IMPLEMENTATION) : public ::testing::T
}
this->node_ptr = new rcl_node_t;
*this->node_ptr = rcl_get_zero_initialized_node();
- const char * name = "test_publisher_node";
+ constexpr char name[] = "test_publisher_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(RCL_RET_OK, ret) << rcl_get_error_string().str;
@@ -76,6 +80,34 @@ class CLASSNAME (TestPublisherFixture, RMW_IMPLEMENTATION) : public ::testing::T
}
};
+class CLASSNAME (TestPublisherFixtureInit, RMW_IMPLEMENTATION)
+ : public CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION)
+{
+public:
+ const rosidl_message_type_support_t * ts =
+ ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, BasicTypes);
+ const char * topic_name = "chatter";
+ rcl_publisher_t publisher;
+ rcl_publisher_options_t publisher_options;
+
+ void SetUp() override
+ {
+ CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION) ::SetUp();
+ publisher = rcl_get_zero_initialized_publisher();
+ publisher_options = rcl_publisher_get_default_options();
+ rcl_ret_t ret = rcl_publisher_init(
+ &publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+ }
+
+ void TearDown() override
+ {
+ rcl_ret_t ret = rcl_publisher_fini(&publisher, this->node_ptr);
+ EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+ CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION) ::TearDown();
+ }
+};
+
/* Basic nominal test of a publisher.
*/
TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_publisher_nominal) {
@@ -83,8 +115,8 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_publisher_nomin
rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
const rosidl_message_type_support_t * ts =
ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, BasicTypes);
- const char * topic_name = "chatter";
- const char * expected_topic_name = "/chatter";
+ constexpr char topic_name[] = "chatter";
+ constexpr char expected_topic_name[] = "/chatter";
rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
@@ -109,7 +141,7 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_publisher_nomin
rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
const rosidl_message_type_support_t * ts =
ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
- const char * topic_name = "chatter";
+ constexpr char topic_name[] = "chatter";
rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
@@ -187,7 +219,7 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_publisher_init_
rcl_publisher_t publisher;
const rosidl_message_type_support_t * ts =
ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, BasicTypes);
- const char * topic_name = "chatter";
+ constexpr char topic_name[] = "chatter";
rcl_publisher_options_t default_publisher_options = rcl_publisher_get_default_options();
// Check if null publisher is valid
@@ -216,6 +248,11 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_publisher_init_
EXPECT_EQ(RCL_RET_NODE_INVALID, ret) << rcl_get_error_string().str;
rcl_reset_error();
+ // Pass nullptr publisher to fini
+ ret = rcl_publisher_fini(nullptr, this->node_ptr);
+ EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+
// Try passing null for publisher in init.
ret = rcl_publisher_init(nullptr, this->node_ptr, ts, topic_name, &default_publisher_options);
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret) << rcl_get_error_string().str;
@@ -305,7 +342,7 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_publisher_loan)
rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
const rosidl_message_type_support_t * ts =
ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
- const char * topic_name = "chatter";
+ constexpr char topic_name[] = "chatter";
rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
rcl_ret_t ret =
rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
@@ -337,7 +374,7 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_invalid_publish
rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
const rosidl_message_type_support_t * ts =
ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
- const char * topic_name = "chatter";
+ constexpr char topic_name[] = "chatter";
rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
rcl_ret_t ret =
rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
@@ -371,6 +408,7 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_invalid_publish
rcl_publisher_impl_t * saved_impl = publisher.impl;
rcl_context_t * saved_context = publisher.impl->context;
rmw_publisher_t * saved_rmw_handle = publisher.impl->rmw_handle;
+ rmw_publisher_allocation_t * null_allocation_is_valid_arg = nullptr;
// Change internal context to nullptr
publisher.impl->context = nullptr;
@@ -391,14 +429,25 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_invalid_publish
rcl_reset_error();
EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publisher_assert_liveliness(&publisher));
rcl_reset_error();
- EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(&publisher, &msg, nullptr));
+ EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(&publisher, &msg, null_allocation_is_valid_arg));
rcl_reset_error();
EXPECT_EQ(
RCL_RET_PUBLISHER_INVALID,
- rcl_publish_serialized_message(&publisher, &serialized_msg, nullptr));
+ rcl_publish_serialized_message(&publisher, &serialized_msg, null_allocation_is_valid_arg));
rcl_reset_error();
publisher.impl->context = saved_context;
+ // nullptr arguments
+ EXPECT_EQ(
+ RCL_RET_INVALID_ARGUMENT, rcl_publish(&publisher, nullptr, null_allocation_is_valid_arg));
+ rcl_reset_error();
+ EXPECT_EQ(
+ RCL_RET_INVALID_ARGUMENT,
+ rcl_publish_serialized_message(&publisher, nullptr, null_allocation_is_valid_arg));
+ rcl_reset_error();
+ EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, rcl_publisher_get_subscription_count(&publisher, nullptr));
+ rcl_reset_error();
+
// Change internal rmw_handle to nullptr
publisher.impl->rmw_handle = nullptr;
EXPECT_FALSE(rcl_publisher_is_valid_except_context(&publisher));
@@ -422,11 +471,11 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_invalid_publish
rcl_reset_error();
EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publisher_assert_liveliness(&publisher));
rcl_reset_error();
- EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(&publisher, &msg, nullptr));
+ EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(&publisher, &msg, null_allocation_is_valid_arg));
rcl_reset_error();
EXPECT_EQ(
RCL_RET_PUBLISHER_INVALID,
- rcl_publish_serialized_message(&publisher, &serialized_msg, nullptr));
+ rcl_publish_serialized_message(&publisher, &serialized_msg, null_allocation_is_valid_arg));
rcl_reset_error();
publisher.impl->rmw_handle = saved_rmw_handle;
@@ -453,11 +502,11 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_invalid_publish
rcl_reset_error();
EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publisher_assert_liveliness(&publisher));
rcl_reset_error();
- EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(&publisher, &msg, nullptr));
+ EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(&publisher, &msg, null_allocation_is_valid_arg));
rcl_reset_error();
EXPECT_EQ(
RCL_RET_PUBLISHER_INVALID,
- rcl_publish_serialized_message(&publisher, &serialized_msg, nullptr));
+ rcl_publish_serialized_message(&publisher, &serialized_msg, null_allocation_is_valid_arg));
rcl_reset_error();
publisher.impl = saved_impl;
@@ -483,10 +532,310 @@ TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_invalid_publish
rcl_reset_error();
EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publisher_assert_liveliness(nullptr));
rcl_reset_error();
- EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(nullptr, &msg, nullptr));
+ EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_publish(nullptr, &msg, null_allocation_is_valid_arg));
rcl_reset_error();
EXPECT_EQ(
RCL_RET_PUBLISHER_INVALID,
- rcl_publish_serialized_message(nullptr, &serialized_msg, nullptr));
+ rcl_publish_serialized_message(nullptr, &serialized_msg, null_allocation_is_valid_arg));
+ rcl_reset_error();
+}
+
+// Mocking rmw_publisher_count_matched_subscriptions to make
+// rcl_publisher_get_subscription_count fail
+TEST_F(
+ CLASSNAME(TestPublisherFixtureInit, RMW_IMPLEMENTATION),
+ test_mock_publisher_get_subscription_count)
+{
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_publisher_count_matched_subscriptions, RMW_RET_BAD_ALLOC);
+
+ // Now normal usage of the function rcl_publisher_get_subscription_count returning
+ // unexpected RMW_RET_BAD_ALLOC
+ size_t count_size = 2u;
+ EXPECT_EQ(
+ RCL_RET_BAD_ALLOC, rcl_publisher_get_subscription_count(&publisher, &count_size));
+ EXPECT_EQ(2u, count_size);
+ rcl_reset_error();
+}
+
+// Mocking rmw_publisher_assert_liveliness to make
+// rcl_publisher_assert_liveliness fail
+TEST_F(CLASSNAME(TestPublisherFixtureInit, RMW_IMPLEMENTATION), test_mock_assert_liveliness) {
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_publisher_assert_liveliness, RMW_RET_ERROR);
+
+ // Now normal usage of the function rcl_publisher_assert_liveliness returning
+ // unexpected RMW_RET_ERROR
+ EXPECT_EQ(
+ RCL_RET_ERROR, rcl_publisher_assert_liveliness(&publisher));
+ EXPECT_TRUE(rcl_error_is_set());
+ rcl_reset_error();
+}
+
+// Mocking rmw_publish to make rcl_publish fail
+TEST_F(CLASSNAME(TestPublisherFixtureInit, RMW_IMPLEMENTATION), test_mock_publish) {
+ auto mock = mocking_utils::patch_and_return("lib:rcl", rmw_publish, RMW_RET_ERROR);
+
+ // Test normal usage of the function rcl_publish returning unexpected RMW_RET_ERROR
+ test_msgs__msg__BasicTypes msg;
+ test_msgs__msg__BasicTypes__init(&msg);
+ msg.int64_value = 42;
+ rcl_ret_t ret = rcl_publish(&publisher, &msg, nullptr);
+ test_msgs__msg__BasicTypes__fini(&msg);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ EXPECT_TRUE(rcl_error_is_set());
+ rcl_reset_error();
+}
+
+// Mocking rmw_publish_serialized_message to make rcl_publish_serialized_message fail
+TEST_F(
+ CLASSNAME(TestPublisherFixtureInit, RMW_IMPLEMENTATION), test_mock_publish_serialized_message)
+{
+ rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message();
+ size_t initial_size_serialized = 0u;
+ rcl_allocator_t allocator = rcl_get_default_allocator();
+ ASSERT_EQ(
+ RCL_RET_OK, rmw_serialized_message_init(
+ &serialized_msg, initial_size_serialized, &allocator)) << rcl_get_error_string().str;
+ constexpr char test_string[] = "testing";
+ test_msgs__msg__Strings msg;
+ test_msgs__msg__Strings__init(&msg);
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ test_msgs__msg__Strings__fini(&msg);
+ });
+
+ ASSERT_TRUE(rosidl_runtime_c__String__assign(&msg.string_value, test_string));
+ ASSERT_STREQ(msg.string_value.data, test_string);
+ rcl_ret_t ret = rmw_serialize(&msg, ts, &serialized_msg);
+ ASSERT_EQ(RMW_RET_OK, ret);
+
+ rmw_ret_t rmw_publish_serialized_return = RMW_RET_ERROR;
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_publish_serialized_message, rmw_publish_serialized_return);
+ {
+ // Test normal usage of the function rcl_publish_serialized_message
+ // returning unexpected RMW_RET_ERROR
+ ret = rcl_publish_serialized_message(&publisher, &serialized_msg, nullptr);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ EXPECT_TRUE(rcl_error_is_set());
+ rcl_reset_error();
+ }
+ {
+ // Repeat, but now returning BAD_ALLOC
+ rmw_publish_serialized_return = RMW_RET_BAD_ALLOC;
+ ret = rcl_publish_serialized_message(&publisher, &serialized_msg, nullptr);
+ EXPECT_EQ(RCL_RET_BAD_ALLOC, ret) << rcl_get_error_string().str;
+ EXPECT_TRUE(rcl_error_is_set());
+ rcl_reset_error();
+ }
+}
+
+// Define dummy comparison operators for rcutils_allocator_t type for use with the Mimick Library
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(rcutils_allocator_t, ==)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(rcutils_allocator_t, <)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(rcutils_allocator_t, >)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(rcutils_allocator_t, !=)
+
+TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_mock_publisher_init) {
+ rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
+ const rosidl_message_type_support_t * ts =
+ ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
+ constexpr char topic_name[] = "chatter";
+ rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
+ rcl_ret_t ret = RCL_RET_OK;
+
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rcutils_string_map_init, RCUTILS_RET_ERROR);
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+}
+
+TEST_F(
+ CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_mock_publisher_init_fail_qos)
+{
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_publisher_get_actual_qos, RMW_RET_ERROR);
+
+ rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
+ const rosidl_message_type_support_t * ts =
+ ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
+ constexpr char topic_name[] = "chatter";
+ rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
+
+ rcl_ret_t ret =
+ rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
rcl_reset_error();
}
+
+// Tests for loaned msgs functions. Mocked as the rmw tier1 vendors don't support it
+TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_mock_loaned_functions) {
+ rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
+ rcl_publisher_t not_init_publisher = rcl_get_zero_initialized_publisher();
+ const rosidl_message_type_support_t * ts =
+ ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, BasicTypes);
+ constexpr char topic_name[] = "chatter";
+ constexpr char expected_topic_name[] = "/chatter";
+ rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
+
+ rcl_ret_t ret = rcl_publisher_init(
+ &publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ ret = rcl_publisher_fini(&publisher, this->node_ptr);
+ EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+ });
+
+ EXPECT_EQ(strcmp(rcl_publisher_get_topic_name(&publisher), expected_topic_name), 0);
+ test_msgs__msg__BasicTypes msg;
+ test_msgs__msg__BasicTypes__init(&msg);
+ msg.int64_value = 42;
+ void * msg_pointer = &msg;
+ rmw_publisher_allocation_t * null_allocation_is_valid_arg = nullptr;
+
+ {
+ // mocked, publish nominal usage
+ auto mock = mocking_utils::patch_and_return("lib:rcl", rmw_publish_loaned_message, RMW_RET_OK);
+ EXPECT_EQ(RCL_RET_OK, rcl_publish_loaned_message(&publisher, &msg, nullptr));
+ }
+ {
+ // bad params publish
+ EXPECT_EQ(
+ RCL_RET_PUBLISHER_INVALID,
+ rcl_publish_loaned_message(nullptr, &msg, null_allocation_is_valid_arg));
+ EXPECT_EQ(
+ RCL_RET_PUBLISHER_INVALID,
+ rcl_publish_loaned_message(¬_init_publisher, &msg, null_allocation_is_valid_arg));
+ EXPECT_EQ(
+ RCL_RET_INVALID_ARGUMENT,
+ rcl_publish_loaned_message(&publisher, nullptr, null_allocation_is_valid_arg));
+ }
+ {
+ // mocked, failure publish
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_publish_loaned_message, RMW_RET_ERROR);
+ EXPECT_EQ(RCL_RET_ERROR, rcl_publish_loaned_message(&publisher, &msg, nullptr));
+ }
+ {
+ // mocked, borrow loaned nominal usage
+ auto mock = mocking_utils::patch_and_return("lib:rcl", rmw_borrow_loaned_message, RMW_RET_OK);
+ EXPECT_EQ(RCL_RET_OK, rcl_borrow_loaned_message(&publisher, ts, &msg_pointer));
+ }
+ {
+ // bad params borrow loaned
+ EXPECT_EQ(RCL_RET_PUBLISHER_INVALID, rcl_borrow_loaned_message(nullptr, ts, &msg_pointer));
+ EXPECT_EQ(
+ RCL_RET_PUBLISHER_INVALID, rcl_borrow_loaned_message(¬_init_publisher, ts, &msg_pointer));
+ }
+ {
+ // mocked, nominal return loaned message
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_return_loaned_message_from_publisher, RMW_RET_OK);
+ EXPECT_EQ(RCL_RET_OK, rcl_return_loaned_message_from_publisher(&publisher, &msg));
+ }
+ {
+ // bad params return loaned message
+ EXPECT_EQ(
+ RCL_RET_PUBLISHER_INVALID,
+ rcl_return_loaned_message_from_publisher(nullptr, &msg));
+ EXPECT_EQ(
+ RCL_RET_PUBLISHER_INVALID,
+ rcl_return_loaned_message_from_publisher(¬_init_publisher, &msg));
+ EXPECT_EQ(
+ RCL_RET_INVALID_ARGUMENT,
+ rcl_return_loaned_message_from_publisher(&publisher, nullptr));
+ }
+
+ test_msgs__msg__BasicTypes__fini(&msg);
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+}
+
+// Tests mocking ini/fini functions for specific failures
+TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_mocks_fail_publisher_init) {
+ rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
+ const rosidl_message_type_support_t * ts =
+ ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, Strings);
+ constexpr char topic_name[] = "chatter";
+ rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
+ rcl_ret_t ret = RCL_RET_OK;
+
+ {
+ // Internal rmw failure validating node name
+ auto mock = mocking_utils::patch_and_return("lib:rcl", rmw_validate_node_name, RMW_RET_ERROR);
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ }
+ {
+ // Internal rmw failure validating node name
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_validate_node_name, RMW_RET_INVALID_ARGUMENT);
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ }
+ {
+ // Internal failure when fini rcutils_string_map returns error, targets substitution_map fini
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rcutils_string_map_fini, RCUTILS_RET_ERROR);
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ }
+ {
+ // Internal failure when fini rcutils_string_map returns error, targets rcl_remap_topic_name
+ auto mock = mocking_utils::patch(
+ "lib:rcl", rcutils_string_map_init, [](auto...) {
+ static int counter = 1;
+ if (counter == 1) {
+ counter++;
+ return RCUTILS_RET_OK;
+ } else {
+ // This makes rcl_remap_topic_name fail
+ return RCUTILS_RET_ERROR;
+ }
+ });
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ }
+ {
+ // Internal rmw failure validating topic name
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl", rmw_validate_full_topic_name, RMW_RET_ERROR);
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ }
+ {
+ // Internal rmw failure validating node name, returns OK but the result is set to error
+ auto mock = mocking_utils::patch(
+ "lib:rcl", rmw_validate_full_topic_name, [](auto, int * result, auto) {
+ *result = RMW_TOPIC_INVALID_NOT_ABSOLUTE;
+ return RMW_RET_OK;
+ });
+ ret = rcl_publisher_init(&publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ EXPECT_EQ(RCL_RET_TOPIC_NAME_INVALID, ret) << rcl_get_error_string().str;
+ rcl_reset_error();
+ }
+}
+
+// Test mocked fail fini publisher
+TEST_F(CLASSNAME(TestPublisherFixture, RMW_IMPLEMENTATION), test_mock_publisher_fini_fail) {
+ rcl_publisher_t publisher = rcl_get_zero_initialized_publisher();
+ const rosidl_message_type_support_t * ts =
+ ROSIDL_GET_MSG_TYPE_SUPPORT(test_msgs, msg, BasicTypes);
+ constexpr char topic_name[] = "chatter";
+ rcl_publisher_options_t publisher_options = rcl_publisher_get_default_options();
+ rcl_ret_t ret = rcl_publisher_init(
+ &publisher, this->node_ptr, ts, topic_name, &publisher_options);
+ ASSERT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;
+
+ // Internal rmw failure destroying publisher
+ auto mock = mocking_utils::patch_and_return("lib:rcl", rmw_destroy_publisher, RMW_RET_ERROR);
+ ret = rcl_publisher_fini(&publisher, this->node_ptr);
+ EXPECT_EQ(RCL_RET_ERROR, ret) << rcl_get_error_string().str;
+}