Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add mimick based patch() functionality. #92

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -117,6 +119,9 @@ if(BUILD_TESTING)
"_TEST_LIBRARY_DIR=$<TARGET_FILE_DIR:test_library>;_TEST_LIBRARY=$<TARGET_FILE: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()
Expand Down
200 changes: 200 additions & 0 deletions include/rcpputils/patch.hpp
Original file line number Diff line number Diff line change
@@ -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 <functional>
#include <string>
#include <utility>

#include "mimick/mimick.h"
#include "rcutils/macros.h"

namespace rcpputils
{

template<size_t N, typename SignatureT>
struct patch_traits;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these structs, c++ style suggests using PascalCase yah?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I tend to fallback to stl style whenever writing templates.


template<size_t N, typename ReturnType>
struct patch_traits<N, ReturnType(void)>
{
mmk_mock_define(mock_type, ReturnType);
};

template<size_t N, typename ReturnType, typename ArgType0>
struct patch_traits<N, ReturnType(ArgType0)>
{
mmk_mock_define(mock_type, ReturnType, ArgType0);
};

template<size_t N, typename ReturnType,
typename ArgType0, typename ArgType1>
struct patch_traits<N, ReturnType(ArgType0, ArgType1)>
{
mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1);
};

template<size_t N, typename ReturnType,
typename ArgType0, typename ArgType1, typename ArgType2>
struct patch_traits<N, ReturnType(ArgType0, ArgType1, ArgType2)>
{
mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2);
};

template<size_t N, typename ReturnType,
typename ArgType0, typename ArgType1,
typename ArgType2, typename ArgType3>
struct patch_traits<N, ReturnType(ArgType0, ArgType1, ArgType2, ArgType3)>
{
mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3);
};

template<size_t N, typename ReturnType,
typename ArgType0, typename ArgType1,
typename ArgType2, typename ArgType3, typename ArgType4>
struct patch_traits<N, ReturnType(ArgType0, ArgType1, ArgType2, ArgType3, ArgType4)>
{
mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4);
};

template<size_t N, typename ReturnType,
typename ArgType0, typename ArgType1,
typename ArgType2, typename ArgType3,
typename ArgType4, typename ArgType5>
struct patch_traits<N, ReturnType(ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5)>
{
mmk_mock_define(
mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5);
};

template<size_t N, typename SignatureT>
struct trampoline;

template<size_t N, typename ReturnT, typename ... ArgTs>
struct trampoline<N, ReturnT(ArgTs...)>
{
static ReturnT base(ArgTs... args)
{
return target(std::forward<ArgTs>(args)...);
}

static std::function<ReturnT(ArgTs...)> target;
};

template<size_t N, typename ReturnT, typename ... ArgTs>
std::function<ReturnT(ArgTs...)>
trampoline<N, ReturnT(ArgTs...)>::target;

template<size_t N, typename SignatureT>
auto prepare_trampoline(std::function<SignatureT> target)
{
trampoline<N, SignatureT>::target = target;
return trampoline<N, SignatureT>::base;
}

template<size_t N, typename SignatureT>
class patch;

template<size_t N, typename ReturnT, typename ... ArgTs>
class patch<N, ReturnT(ArgTs...)>
{
public:
using mock_type = typename patch_traits<N, ReturnT(ArgTs...)>::mock_type;

patch(const std::string & target, std::function<ReturnT(ArgTs...)> proxy)
: proxy_(proxy)
{
auto MMK_MANGLE(mock_type, create) =
patch_traits<N, ReturnT(ArgTs...)>::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<ReturnT(ArgTs...)> replacement) &
{
auto type_erased_trampoline =
reinterpret_cast<mmk_fn>(prepare_trampoline<N>(replacement));
mmk_when(proxy_(any<ArgTs>()...), .then_call = type_erased_trampoline);
return *this;
}

patch && then_call(std::function<ReturnT(ArgTs...)> replacement) &&
{
auto type_erased_trampoline =
reinterpret_cast<mmk_fn>(prepare_trampoline<N>(replacement));
mmk_when(proxy_(any<ArgTs>()...), .then_call = type_erased_trampoline);
return std::move(*this);
}

private:
template<typename T>
T any() {return mmk_any(T);}

mock_type mock_;
std::function<ReturnT(ArgTs...)> proxy_;
};

template<size_t N, typename SignatureT>
auto make_patch(const std::string & target, std::function<SignatureT> proxy)
{
return patch<N, SignatureT>(target, proxy);
}

} // namespace rcpputils

#define RCPPUTILS_PATCH_IGNORE_OPERATOR(type, op) \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure you'll add more info in comments, but what purpose does overloading these operators have? It's not clear from the macro name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For every argument type, a set of comparison operators must be defined (even if meaningless) or otherwise mimick's macro generated code won't compile. This macro attempts to be a handy way to generate these dummy overloads. Name is up for debate 😅

FWIW I tried to come up with a smarter mechanism to hide this but couldn't find a way.

bool operator op(const type &, const type &) { \
return false; \
}

#define RCPPUTILS_PATCH_PROXY(func) \
[] (auto && ... args)->decltype(auto) { \
return func(std::forward<decltype(args)>(args)...); \
}

#define RCPPUTILS_PATCH_TARGET(what, where) \
(std::string(RCUTILS_STRINGIFY(where)) + "@" + (what))

#define patch(what, where, with) \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your macro name is the same as your struct name, will that lead to confusion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps. I don't expect the base class being used directly, but we can rename it.

make_patch<__COUNTER__, decltype(where)>( \
RCPPUTILS_PATCH_TARGET(what, where), RCPPUTILS_PATCH_PROXY(where) \
).then_call(with)

#endif // RCPPUTILS__PATCH_HPP_
1 change: 1 addition & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>ament_cmake_ros</buildtool_depend>

<depend>mimick_vendor</depend>
<depend>rcutils</depend>

<test_depend>ament_lint_common</test_depend>
Expand Down
40 changes: 40 additions & 0 deletions test/test_patch.cpp
Original file line number Diff line number Diff line change
@@ -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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXPECT_STREQ or EXPECT_EQ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXPECT_EQ, as rcpputils::get_executable_name() returns an std::string.

}

EXPECT_EQ("test_patch", rcpputils::get_executable_name());
}