From beeeb3e187414f34d9e91898acf258d7a71e56b5 Mon Sep 17 00:00:00 2001 From: Jorge Perez Date: Mon, 10 Aug 2020 17:32:39 -0300 Subject: [PATCH] Add mock tests, publisher 95% coverage (#732) * Add mimick test for rcl_publisher_get_subscription_count * Remove const qualifiers * Add missing mock suffix * Improve test description * Add mock test for rcl_publisher_assert_liveliness * Add class to init publisher tests * Add test for mocked rmw_publish * Add mock test for rcl_publish_serialized * Mock rcutils_string_map_init to make init fail * Add mock test making rmw_publisher_get_actual_qos fail * Add class to ease mimick usage * Reformat tests to use helper class * Add mocked rcutils_string_map_init to make init fail * Add tests mocking loaned functions * Add mock fail tests for publisher_init * Add publisher fini fail mock tests * Add nullptr tests * Update mocking utilities * Reformat with macro utility * Add comments for mocked tests * Check count_size value after test * Reformat to use constexpr where possible * Add variable making clear bad param test * Add link to original file to help tracking changes Signed-off-by: Jorge Perez --- rcl/package.xml | 1 + rcl/test/CMakeLists.txt | 4 +- rcl/test/mocking_utils/patch.hpp | 355 ++++++++++++++++++++++++++++ rcl/test/rcl/test_publisher.cpp | 381 +++++++++++++++++++++++++++++-- 4 files changed, 724 insertions(+), 17 deletions(-) create mode 100644 rcl/test/mocking_utils/patch.hpp diff --git a/rcl/package.xml b/rcl/package.xml index 6334f95bb9..a28382afff 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 9e0d85fddb..c177d2ed2c 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 0000000000..544dec27aa --- /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 40cc36c125..c536f92425 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; +}