diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bdd256..ea01a05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(rcpputils) find_package(ament_cmake REQUIRED) find_package(ament_cmake_ros REQUIRED) +find_package(mimick_vendor REQUIRED) find_package(rcutils REQUIRED) # Default to C11 @@ -35,6 +36,7 @@ if(WIN32) target_compile_definitions(${PROJECT_NAME} PRIVATE "RCPPUTILS_BUILDING_LIBRARY") endif() +target_link_libraries(${PROJECT_NAME} mimick) ament_target_dependencies(${PROJECT_NAME} rcutils) ament_export_libraries(${PROJECT_NAME}) ament_export_targets(${PROJECT_NAME}) @@ -117,6 +119,9 @@ if(BUILD_TESTING) "_TEST_LIBRARY_DIR=$;_TEST_LIBRARY=$") ament_add_gtest(test_clamp test/test_clamp.cpp) + + ament_add_gtest(test_patch test/test_patch.cpp) + target_link_libraries(test_patch ${PROJECT_NAME}) endif() ament_package() diff --git a/include/rcpputils/patch.hpp b/include/rcpputils/patch.hpp new file mode 100644 index 0000000..281eece --- /dev/null +++ b/include/rcpputils/patch.hpp @@ -0,0 +1,200 @@ +// 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. + +#ifndef RCPPUTILS__PATCH_HPP_ +#define RCPPUTILS__PATCH_HPP_ + +#include +#include +#include + +#include "mimick/mimick.h" +#include "rcutils/macros.h" + +namespace rcpputils +{ + +template +struct patch_traits; + +template +struct patch_traits +{ + mmk_mock_define(mock_type, ReturnType); +}; + +template +struct patch_traits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0); +}; + +template +struct patch_traits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1); +}; + +template +struct patch_traits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2); +}; + +template +struct patch_traits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3); +}; + +template +struct patch_traits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4); +}; + +template +struct patch_traits +{ + mmk_mock_define( + mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5); +}; + +template +struct trampoline; + +template +struct trampoline +{ + static ReturnT base(ArgTs... args) + { + return target(std::forward(args)...); + } + + static std::function target; +}; + +template +std::function +trampoline::target; + +template +auto prepare_trampoline(std::function target) +{ + trampoline::target = target; + return trampoline::base; +} + +template +class patch; + +template +class patch +{ +public: + using mock_type = typename patch_traits::mock_type; + + patch(const std::string & target, std::function proxy) + : proxy_(proxy) + { + auto MMK_MANGLE(mock_type, create) = + patch_traits::MMK_MANGLE(mock_type, create); + mock_ = mmk_mock(target.c_str(), mock_type); + } + + 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_); + } + } + + 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; + } + + 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: + template + T any() {return mmk_any(T);} + + mock_type mock_; + std::function proxy_; +}; + +template +auto make_patch(const std::string & target, std::function proxy) +{ + return patch(target, proxy); +} + +} // namespace rcpputils + +#define RCPPUTILS_PATCH_IGNORE_OPERATOR(type, op) \ + bool operator op(const type &, const type &) { \ + return false; \ + } + +#define RCPPUTILS_PATCH_PROXY(func) \ + [] (auto && ... args)->decltype(auto) { \ + return func(std::forward(args)...); \ + } + +#define RCPPUTILS_PATCH_TARGET(what, where) \ + (std::string(RCUTILS_STRINGIFY(where)) + "@" + (what)) + +#define patch(what, where, with) \ + make_patch<__COUNTER__, decltype(where)>( \ + RCPPUTILS_PATCH_TARGET(what, where), RCPPUTILS_PATCH_PROXY(where) \ + ).then_call(with) + +#endif // RCPPUTILS__PATCH_HPP_ diff --git a/package.xml b/package.xml index 7a3ddb2..8e2462f 100644 --- a/package.xml +++ b/package.xml @@ -10,6 +10,7 @@ ament_cmake ament_cmake_ros + mimick_vendor rcutils ament_lint_common diff --git a/test/test_patch.cpp b/test/test_patch.cpp new file mode 100644 index 0000000..e9c28a3 --- /dev/null +++ b/test/test_patch.cpp @@ -0,0 +1,40 @@ +// 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. + +#include "gtest/gtest.h" + +#include "rcutils/allocator.h" +#include "rcutils/process.h" +#include "rcutils/strdup.h" + +#include "rcpputils/patch.hpp" +#include "rcpputils/process.hpp" + +RCPPUTILS_PATCH_IGNORE_OPERATOR(rcutils_allocator_t, ==) +RCPPUTILS_PATCH_IGNORE_OPERATOR(rcutils_allocator_t, !=) +RCPPUTILS_PATCH_IGNORE_OPERATOR(rcutils_allocator_t, <) +RCPPUTILS_PATCH_IGNORE_OPERATOR(rcutils_allocator_t, >) + +TEST(TestPatch, test_patched_get_executable_name) { + { + auto p = rcpputils::patch( + "self", rcutils_get_executable_name, + [](rcutils_allocator_t allocator) { + return rcutils_strdup("dummy_name", allocator); + }); + EXPECT_EQ("dummy_name", rcpputils::get_executable_name()); + } + + EXPECT_EQ("test_patch", rcpputils::get_executable_name()); +}