diff --git a/rcl_yaml_param_parser/CMakeLists.txt b/rcl_yaml_param_parser/CMakeLists.txt
index aacf634ac..3498ea57c 100644
--- a/rcl_yaml_param_parser/CMakeLists.txt
+++ b/rcl_yaml_param_parser/CMakeLists.txt
@@ -107,7 +107,7 @@ if(BUILD_TESTING)
"rcutils"
"osrf_testing_tools_cpp"
)
- target_link_libraries(test_parse ${PROJECT_NAME})
+ target_link_libraries(test_parse ${PROJECT_NAME} mimick)
endif()
ament_add_gtest(test_parser
@@ -119,7 +119,7 @@ if(BUILD_TESTING)
"rcutils"
"osrf_testing_tools_cpp"
)
- target_link_libraries(test_parser ${PROJECT_NAME})
+ target_link_libraries(test_parser ${PROJECT_NAME} mimick)
target_compile_definitions(test_parser PUBLIC RCUTILS_ENABLE_FAULT_INJECTION)
endif()
diff --git a/rcl_yaml_param_parser/package.xml b/rcl_yaml_param_parser/package.xml
index e710a6ff0..bcd750d4a 100644
--- a/rcl_yaml_param_parser/package.xml
+++ b/rcl_yaml_param_parser/package.xml
@@ -16,6 +16,7 @@
ament_cmake_gtest
ament_lint_common
ament_lint_auto
+ mimick_vendor
osrf_testing_tools_cpp
performance_test_fixture
rcpputils
diff --git a/rcl_yaml_param_parser/test/mocking_utils/patch.hpp b/rcl_yaml_param_parser/test/mocking_utils/patch.hpp
new file mode 100644
index 000000000..d170c7fa3
--- /dev/null
+++ b/rcl_yaml_param_parser/test/mocking_utils/patch.hpp
@@ -0,0 +1,376 @@
+// 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)
+ : target_(target), proxy_(proxy)
+ {
+ }
+
+ // 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) &
+ {
+ replace_with(replacement);
+ return *this;
+ }
+
+ /// Inject a @p replacement for the patched function.
+ Patch && then_call(std::function replacement) &&
+ {
+ replace_with(replacement);
+ return std::move(*this);
+ }
+
+private:
+ // Helper for template parameter pack expansion using `mmk_any`
+ // macro as pattern.
+ template
+ T any() {return mmk_any(T);}
+
+ void replace_with(std::function replacement)
+ {
+ if (mock_) {
+ throw std::logic_error("Cannot configure patch more than once");
+ }
+ auto type_erased_trampoline =
+ reinterpret_cast(prepare_trampoline(replacement));
+ auto MMK_MANGLE(mock_type, create) =
+ PatchTraits::MMK_MANGLE(mock_type, create);
+ mock_ = mmk_mock(target_.c_str(), mock_type);
+ mmk_when(proxy_(any()...), .then_call = type_erased_trampoline);
+ }
+
+ mock_type mock_{nullptr};
+ std::string target_;
+ 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))
+
+/// Prepare a mocking_utils::Patch for patching a `function` in a given `scope`
+/// but defer applying any changes.
+#define prepare_patch(scope, function) \
+ make_patch<__COUNTER__, decltype(function)>( \
+ MOCKING_UTILS_PATCH_TARGET(scope, function), MOCKING_UTILS_PATCH_PROXY(function) \
+ )
+
+/// Patch a `function` with a used-provided `replacement` in a given `scope`.
+#define patch(scope, function, replacement) \
+ prepare_patch(scope, function).then_call(replacement)
+
+/// Patch a `function` to always yield a given `return_code` in a given `scope`.
+#define patch_and_return(scope, function, return_code) \
+ patch(scope, function, [&](auto && ...) {return return_code;})
+
+/// Patch a `function` to execute normally but always yield a given `return_code`
+/// in a given `scope`.
+#define inject_on_return(scope, function, return_code) \
+ patch( \
+ scope, function, ([&, base = function](auto && ... __args) { \
+ static_cast(base(std::forward(__args)...)); \
+ return return_code; \
+ }))
+
+} // 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_yaml_param_parser/test/test_parse.cpp b/rcl_yaml_param_parser/test/test_parse.cpp
index 428fa0f83..51f2b927d 100644
--- a/rcl_yaml_param_parser/test/test_parse.cpp
+++ b/rcl_yaml_param_parser/test/test_parse.cpp
@@ -25,6 +25,8 @@
#include "../src/impl/node_params.h"
#include "rcutils/filesystem.h"
+#include "./mocking_utils/patch.hpp"
+
TEST(TestParse, parse_value) {
rcutils_allocator_t allocator = rcutils_get_default_allocator();
yaml_event_t event;
@@ -446,3 +448,73 @@ TEST(TestParse, parse_key_bad_args)
EXPECT_TRUE(rcutils_error_is_set());
rcutils_reset_error();
}
+
+TEST(TestParse, parse_file_events_mock_yaml_parser_parse) {
+ char cur_dir[1024];
+ rcutils_reset_error();
+ EXPECT_TRUE(rcutils_get_cwd(cur_dir, 1024)) << rcutils_get_error_string().str;
+
+ rcutils_allocator_t allocator = rcutils_get_default_allocator();
+ char * test_path = rcutils_join_path(cur_dir, "test", allocator);
+ char * path = rcutils_join_path(test_path, "correct_config.yaml", allocator);
+ ASSERT_TRUE(NULL != path) << rcutils_get_error_string().str;
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ allocator.deallocate(test_path, allocator.state);
+ allocator.deallocate(path, allocator.state);
+ });
+
+ rcl_params_t * params_hdl = rcl_yaml_node_struct_init(allocator);
+ ASSERT_NE(nullptr, params_hdl);
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ rcl_yaml_node_struct_fini(params_hdl);
+ });
+
+ yaml_parser_t parser;
+ ASSERT_NE(0, yaml_parser_initialize(&parser));
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ yaml_parser_delete(&parser);
+ });
+
+ FILE * yaml_file = fopen(path, "r");
+ ASSERT_NE(nullptr, yaml_file);
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ fclose(yaml_file);
+ });
+ yaml_parser_set_input_file(&parser, yaml_file);
+
+ namespace_tracker_t ns_tracker;
+ memset(&ns_tracker, 0, sizeof(namespace_tracker_t));
+
+ auto mock = mocking_utils::patch(
+ "lib:rcl_yaml_param_parser", yaml_parser_parse, [](yaml_parser_t *, yaml_event_t * event) {
+ event->start_mark.line = 0u;
+ event->type = YAML_NO_EVENT;
+ return 1;
+ });
+ EXPECT_EQ(RCUTILS_RET_ERROR, parse_file_events(&parser, &ns_tracker, params_hdl));
+}
+
+TEST(TestParse, parse_value_events_mock_yaml_parser_parse) {
+ constexpr char node_name[] = "node name";
+ constexpr char param_name[] = "param name";
+ constexpr char yaml_value[] = "true";
+ rcutils_allocator_t allocator = rcutils_get_default_allocator();
+
+ rcl_params_t * params_st = rcl_yaml_node_struct_init(allocator);
+ ASSERT_NE(params_st, nullptr);
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ rcl_yaml_node_struct_fini(params_st);
+ });
+ auto mock = mocking_utils::patch(
+ "lib:rcl_yaml_param_parser", yaml_parser_parse, [](yaml_parser_t *, yaml_event_t * event) {
+ event->start_mark.line = 0u;
+ event->type = YAML_NO_EVENT;
+ return 1;
+ });
+ EXPECT_FALSE(rcl_parse_yaml_value(node_name, param_name, yaml_value, params_st));
+}
diff --git a/rcl_yaml_param_parser/test/test_parser.cpp b/rcl_yaml_param_parser/test/test_parser.cpp
index abf88bd7e..e85162ec7 100644
--- a/rcl_yaml_param_parser/test/test_parser.cpp
+++ b/rcl_yaml_param_parser/test/test_parser.cpp
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include
+
#include
#include
@@ -22,6 +24,7 @@
#include "rcutils/error_handling.h"
#include "rcutils/filesystem.h"
#include "rcutils/testing/fault_injection.h"
+#include "./mocking_utils/patch.hpp"
#include "./time_bomb_allocator_testing_utils.h"
TEST(RclYamlParamParser, node_init_fini) {
@@ -361,6 +364,46 @@ TEST(RclYamlParamParser, test_parse_file_with_bad_allocator) {
}
}
+TEST(RclYamlParamParser, test_parse_yaml_initialize_mock) {
+ char cur_dir[1024];
+ rcutils_reset_error();
+ EXPECT_TRUE(rcutils_get_cwd(cur_dir, 1024)) << rcutils_get_error_string().str;
+
+ rcutils_allocator_t allocator = rcutils_get_default_allocator();
+ char * test_path = rcutils_join_path(cur_dir, "test", allocator);
+
+ char * path = rcutils_join_path(test_path, "correct_config.yaml", allocator);
+ ASSERT_TRUE(NULL != path) << rcutils_get_error_string().str;
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ allocator.deallocate(test_path, allocator.state);
+ allocator.deallocate(path, allocator.state);
+ });
+
+ rcl_params_t * params_hdl = rcl_yaml_node_struct_init(allocator);
+ ASSERT_NE(nullptr, params_hdl);
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ rcl_yaml_node_struct_fini(params_hdl);
+ });
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rcl_yaml_param_parser", yaml_parser_initialize, false);
+
+ EXPECT_FALSE(rcl_parse_yaml_file(path, params_hdl));
+
+ constexpr char node_name[] = "node name";
+ constexpr char param_name[] = "param name";
+ constexpr char yaml_value[] = "true";
+
+ rcl_params_t * params_st = rcl_yaml_node_struct_init(allocator);
+ ASSERT_NE(params_st, nullptr);
+ OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
+ {
+ rcl_yaml_node_struct_fini(params_st);
+ });
+ EXPECT_FALSE(rcl_parse_yaml_value(node_name, param_name, yaml_value, params_st));
+}
+
int32_t main(int32_t argc, char ** argv)
{