From 9e004d7e1e11edabe0003fd77a0c81ff51d3462a Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Mon, 3 Aug 2020 13:48:04 -0300 Subject: [PATCH 01/20] Mock system calls in rcutils API implementation. Signed-off-by: Michel Hidalgo --- CMakeLists.txt | 18 +-- test/mocking_utils/filesystem.hpp | 172 +++++++++++++++++++++++ test/mocking_utils/patch.hpp | 218 ++++++++++++++++++++++++++++++ test/test_char_array.cpp | 33 ++++- test/test_filesystem.cpp | 53 ++++++-- test/test_format_string.cpp | 24 ++++ test/test_logging_custom_env.cpp | 8 ++ test/test_shared_library.cpp | 23 ++++ test/test_split.cpp | 2 +- test/test_strerror.cpp | 2 +- test/test_time.cpp | 99 ++++++++++++++ 11 files changed, 630 insertions(+), 22 deletions(-) create mode 100644 test/mocking_utils/filesystem.hpp create mode 100644 test/mocking_utils/patch.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 35904906..abb89c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,7 +181,7 @@ if(BUILD_TESTING) osrf_testing_tools_cpp::memory_tools LIBRARY_PRELOAD_ENVIRONMENT_IS_AVAILABLE) ament_add_gtest(test_logging test/test_logging.cpp) - target_link_libraries(test_logging ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools) + target_link_libraries(test_logging ${PROJECT_NAME} mimick osrf_testing_tools_cpp::memory_tools) add_executable(test_logging_long_messages test/test_logging_long_messages.cpp) target_link_libraries(test_logging_long_messages ${PROJECT_NAME}) @@ -234,7 +234,7 @@ if(BUILD_TESTING) test/test_char_array.cpp ) if(TARGET test_char_array) - target_link_libraries(test_char_array ${PROJECT_NAME}) + target_link_libraries(test_char_array ${PROJECT_NAME} mimick) endif() # Can't use C++ with stdatomic_helper.h @@ -275,7 +275,7 @@ if(BUILD_TESTING) test/test_split.cpp ) if(TARGET test_split) - target_link_libraries(test_split ${PROJECT_NAME}) + target_link_libraries(test_split ${PROJECT_NAME} mimick) endif() rcutils_custom_add_gtest(test_find @@ -322,7 +322,7 @@ if(BUILD_TESTING) ) if(TARGET test_filesystem) ament_target_dependencies(test_filesystem "osrf_testing_tools_cpp") - target_link_libraries(test_filesystem ${PROJECT_NAME}) + target_link_libraries(test_filesystem ${PROJECT_NAME} mimick) target_compile_definitions(test_filesystem PRIVATE BUILD_DIR="${CMAKE_CURRENT_BINARY_DIR}") endif() @@ -337,7 +337,7 @@ if(BUILD_TESTING) test/test_format_string.cpp ) if(TARGET test_format_string) - target_link_libraries(test_format_string ${PROJECT_NAME}) + target_link_libraries(test_format_string ${PROJECT_NAME} mimick) endif() rcutils_custom_add_gtest(test_string_map @@ -375,14 +375,14 @@ if(BUILD_TESTING) # which is appropriate when building the dll but not consuming it. target_compile_definitions(dummy_shared_library PRIVATE "DUMMY_SHARED_LIBRARY_BUILDING_DLL") endif() - target_link_libraries(test_shared_library ${PROJECT_NAME}) + target_link_libraries(test_shared_library ${PROJECT_NAME} mimick) endif() rcutils_custom_add_gtest(test_time test/test_time.cpp ENV ${memory_tools_test_env_vars}) if(TARGET test_time) - target_link_libraries(test_time ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools) + target_link_libraries(test_time ${PROJECT_NAME} mimick osrf_testing_tools_cpp::memory_tools) endif() rcutils_custom_add_gtest(test_snprintf @@ -436,7 +436,7 @@ if(BUILD_TESTING) RCUTILS_COLORIZED_OUTPUT=1 ) if(TARGET test_logging_custom_env) - target_link_libraries(test_logging_custom_env ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools) + target_link_libraries(test_logging_custom_env ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools mimick) endif() # RCUTILS_LOGGING_MAX_OUTPUT_FORMAT_LEN is defined as 2048, truncation should occur @@ -452,7 +452,7 @@ if(BUILD_TESTING) RCUTILS_COLORIZED_OUTPUT=0 ) if(TARGET test_logging_custom_env2) - target_link_libraries(test_logging_custom_env2 ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools) + target_link_libraries(test_logging_custom_env2 ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools mimick) endif() rcutils_custom_add_gtest(test_logging_bad_env test/test_logging_bad_env.cpp diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp new file mode 100644 index 00000000..b4fe26bf --- /dev/null +++ b/test/mocking_utils/filesystem.hpp @@ -0,0 +1,172 @@ +// 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 MOCKING_UTILS__FILESYSTEM_HPP_ +#define MOCKING_UTILS__FILESYSTEM_HPP_ + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#else +#include +#endif + +#include +#include +#include + +#include "rcutils/macros.h" + +#include "patch.hpp" + +namespace mocking_utils +{ +namespace filesystem +{ + +struct FileTypes +{ + static constexpr mode_t REGULAR_FILE = S_IFREG; + static constexpr mode_t DIRECTORY = S_IFDIR; +}; + +struct Permissions +{ +#ifndef _WIN32 + static constexpr mode_t USER_READABLE = S_IRUSR; + static constexpr mode_t USER_WRITABLE = S_IWUSR; +#else + static constexpr mode_t USER_READABLE = _S_IREAD; + static constexpr mode_t USER_WRITABLE = _S_IWRITE; +#endif +}; + +template +class FileSystem +{ +public: + explicit FileSystem(const std::string & target) +#ifndef _WIN32 + : opendir_mock_(MOCKING_UTILS_PATCH_TARGET(target, opendir), + MOCKING_UTILS_PATCH_PROXY(opendir)), +#else + : find_first_file_mock_(MOCKING_UTILS_PATCH_TARGET(target, FindFirstFile), + MOCKING_UTILS_PATCH_PROXY(FindFirstFile)), +#endif +#ifndef _GNU_SOURCE + stat_mock_(MOCKING_UTILS_PATCH_TARGET(target, stat), + MOCKING_UTILS_PATCH_PROXY(stat)) +#else + __xstat_mock_(MOCKING_UTILS_PATCH_TARGET(target, __xstat), + MOCKING_UTILS_PATCH_PROXY(__xstat)) +#endif + { +#ifndef _WIN32 + opendir_mock_.then_call(std::bind(&FileSystem::do_opendir, this, std::placeholders::_1)); +#else + find_first_file_mock_.then_call( + std::bind( + &FileSystem::do_FindFirstFile, this, + std::placeholders::_1, std::placeholders::_2)) +#endif +#ifndef _GNU_SOURCE + stat_mock_.then_call( + std::bind( + &FileSystem::do_stat, this, + std::placeholders::_1, std::placeholders::_2)); +#else + __xstat_mock_.then_call( + std::bind( + &FileSystem::do___xstat, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); +#endif + } + + void exhaust_file_descriptors() + { +#ifdef _WIN32 + forced_errno_ = ERROR_NO_MORE_SEARCH_HANDLES; +#else + forced_errno_ = EMFILE; +#endif + } + + struct stat & file_info(const std::string & path) + { + return files_info_[path]; + } + +private: +#ifndef _WIN32 + DIR * do_opendir(const char *) + { + if (forced_errno_ != 0) { + errno = forced_errno_; + return NULL; + } + errno = ENOENT; + return NULL; + } + MOCKING_UTILS_PATCH_TYPE(N, opendir) opendir_mock_; +#else + HANDLE do_FindFirstFile(LPCSTR, LPWIN32_FIND_DATAA) + { + if (forced_errno_ != 0) { + SetLastError(forced_errno_); + return INVALID_HANDLE_VALUE; + } + SetLastError(ERROR_FILE_NOT_FOUND); + return INVALID_HANDLE_VALUE; + } + + MOCKING_UTILS_PATCH_TYPE(N, FindFirstFile) find_first_file_mock_; +#endif + +#ifndef _GNU_SOURCE + int do_stat(const char * path, struct stat * info) + { +#else + int do___xstat(int, const char * path, struct stat * info) + { +#endif + if (files_info_.count(path) == 0) { + errno = ENOENT; + return -1; + } + *info = files_info_[path]; + return 0; + } + +#ifndef _GNU_SOURCE + MOCKING_UTILS_PATCH_TYPE(N, stat) stat_mock_; +#else + MOCKING_UTILS_PATCH_TYPE(N, __xstat) __xstat_mock_; +#endif + + int forced_errno_{0}; + std::map files_info_; +}; + +} // namespace filesystem + +/// Patch filesystem API, based on `what` binary object should be affected. +#define patch_filesystem(what) filesystem::FileSystem<__COUNTER__>(what) + +} // namespace mocking_utils + +#endif // MOCKING_UTILS__FILESYSTEM_HPP_ diff --git a/test/mocking_utils/patch.hpp b/test/mocking_utils/patch.hpp new file mode 100644 index 00000000..8b2de1da --- /dev/null +++ b/test/mocking_utils/patch.hpp @@ -0,0 +1,218 @@ +// 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 MOCKING_UTILS__PATCH_HPP_ +#define MOCKING_UTILS__PATCH_HPP_ + +#include +#include +#include +#include + +#include "mimick/mimick.h" +#include "rcutils/macros.h" + +namespace mocking_utils +{ + +template +struct PatchTraits; + +template +struct PatchTraits +{ + mmk_mock_define(mock_type, ReturnType); +}; + +template +struct PatchTraits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0); +}; + +template +struct PatchTraits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1); +}; + +template +struct PatchTraits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2); +}; + +template +struct PatchTraits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3); +}; + +template +struct PatchTraits +{ + mmk_mock_define(mock_type, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4); +}; + +template +struct PatchTraits +{ + 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 PatchTraits::mock_type; + + 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); + } + + 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); +} + +/// 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 required by Mimick. +#define MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(type, op) \ + bool operator op(const type &, const type &) { \ + return false; \ + } + +/// Get the exact 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-compliant, symbol target string based on `what` binary +/// object and `which` symbol should be targeted. +#define MOCKING_UTILS_PATCH_TARGET(what, which) \ + (std::string(RCUTILS_STRINGIFY(where)) + "@" + (what)) + +/// Patch a function `with` a used-provided replacement, based on `what` binary +/// object and `which` symbol should be patched. +#define patch(what, which, with) \ + make_patch<__COUNTER__, decltype(which)>( \ + MOCKING_UTILS_PATCH_TARGET(what, which), MOCKING_UTILS_PATCH_PROXY(where) \ + ).then_call(with) + +} // namespace mocking_utils + +#endif // MOCKING_UTILS__PATCH_HPP_ diff --git a/test/test_char_array.cpp b/test/test_char_array.cpp index 7c948105..e3aef8cc 100644 --- a/test/test_char_array.cpp +++ b/test/test_char_array.cpp @@ -20,6 +20,8 @@ #include "rcutils/error_handling.h" #include "rcutils/types/char_array.h" +#include "./mocking_utils/patch.hpp" + class ArrayCharTest : public ::testing::Test { protected: @@ -125,8 +127,37 @@ TEST_F(ArrayCharTest, vsprintf_fail) { ret = example_logger(&char_array, "Long string for the case %d", 2); EXPECT_EQ(RCUTILS_RET_BAD_ALLOC, ret); rcutils_reset_error(); - char_array.allocator = allocator; + +#ifdef _WIN32 +#define vsnprintf _vsnprintf_s +#endif + size_t buffer_threshold = 0; + auto mock = mocking_utils::patch( + "lib:rcutils", vsnprintf, + [&](char * buffer, size_t buffer_size, auto...) -> int { + if (nullptr == buffer || buffer_size < buffer_threshold) { + return static_cast(buffer_threshold); + } + errno = EINVAL; + return -1; + }); +#ifdef _WIN32 +#undef vsnprintf +#endif + + // Do not force char array resize. + buffer_threshold = 5; + ret = example_logger(&char_array, "Long string for the case %d", 2); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); + + // Force char array resize. + buffer_threshold = 20; + ret = example_logger(&char_array, "Long string for the case %d", 2); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); + EXPECT_EQ(RCUTILS_RET_OK, rcutils_char_array_fini(&char_array)); } diff --git a/test/test_filesystem.cpp b/test/test_filesystem.cpp index df93be07..30c8b3d0 100644 --- a/test/test_filesystem.cpp +++ b/test/test_filesystem.cpp @@ -19,6 +19,8 @@ #include "osrf_testing_tools_cpp/scope_exit.hpp" +#include "./mocking_utils/filesystem.hpp" + static rcutils_allocator_t g_allocator = rcutils_get_default_allocator(); class TestFilesystemFixture : public ::testing::Test @@ -201,6 +203,21 @@ TEST_F(TestFilesystemFixture, is_readable) { ASSERT_FALSE(nullptr == path); EXPECT_FALSE(rcutils_is_readable(path)); } + { + char * path = rcutils_join_path(this->test_path, "dummy_nonexisting_file.txt", g_allocator); + OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( + { + g_allocator.deallocate(path, g_allocator.state); + }); + ASSERT_FALSE(nullptr == path); + EXPECT_FALSE(rcutils_is_readable(path)); + } + { + auto fs = mocking_utils::patch_filesystem("lib:rcutils"); + const char * path = "dummy_non_readable_file.txt"; + fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_READABLE; + EXPECT_FALSE(rcutils_is_readable(path)); + } } TEST_F(TestFilesystemFixture, is_writable) { @@ -232,6 +249,12 @@ TEST_F(TestFilesystemFixture, is_writable) { ASSERT_FALSE(nullptr == path); EXPECT_FALSE(rcutils_is_writable(path)); } + { + auto fs = mocking_utils::patch_filesystem("lib:rcutils"); + const char * path = "dummy_non_writable_file.txt"; + fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; + EXPECT_FALSE(rcutils_is_writable(path)); + } } TEST_F(TestFilesystemFixture, is_readable_and_writable) { @@ -254,6 +277,13 @@ TEST_F(TestFilesystemFixture, is_readable_and_writable) { ASSERT_FALSE(nullptr == path); EXPECT_TRUE(rcutils_is_readable_and_writable(path)); } + { + auto fs = mocking_utils::patch_filesystem("lib:rcutils"); + const char * path = "fake_writable_but_non_readable_file.txt"; + fs.file_info(path).st_mode |= mocking_utils::filesystem::Permissions::USER_READABLE; + fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; + EXPECT_FALSE(rcutils_is_readable_and_writable(path)); + } { char * path = rcutils_join_path(this->test_path, "dummy_readable_writable_file.txt", g_allocator); @@ -330,22 +360,25 @@ TEST_F(TestFilesystemFixture, calculate_directory_size) { #ifdef WIN32 // Due to different line breaks on windows, we have one more byte in the file. // See https://github.com/ros2/rcutils/issues/198 - ASSERT_EQ(6u, size); + EXPECT_EQ(6u, size); #else - ASSERT_EQ(5u, size); + EXPECT_EQ(5u, size); #endif - OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( - { - g_allocator.deallocate(path, g_allocator.state); - }); + g_allocator.deallocate(path, g_allocator.state); char * non_existing_path = rcutils_join_path(this->test_path, "non_existing_folder", g_allocator); size = rcutils_calculate_directory_size(non_existing_path, g_allocator); - ASSERT_EQ(0u, size); - OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( + EXPECT_EQ(0u, size); + g_allocator.deallocate(non_existing_path, g_allocator.state); + { - g_allocator.deallocate(non_existing_path, g_allocator.state); - }); + auto fs = mocking_utils::patch_filesystem("lib:rcutils"); + const char * path = "some_fake_directory/some_fake_folder"; + fs.file_info(path).st_mode |= mocking_utils::filesystem::FileTypes::DIRECTORY; + fs.exhaust_file_descriptors(); + size = rcutils_calculate_directory_size(path, g_allocator); + EXPECT_EQ(0u, size); + } } TEST_F(TestFilesystemFixture, calculate_file_size) { diff --git a/test/test_format_string.cpp b/test/test_format_string.cpp index cc48f16d..4d418515 100644 --- a/test/test_format_string.cpp +++ b/test/test_format_string.cpp @@ -17,6 +17,8 @@ #include #include "./allocator_testing_utils.h" +#include "./mocking_utils/patch.hpp" + #include "rcutils/allocator.h" #include "rcutils/format_string.h" @@ -59,3 +61,25 @@ TEST(test_format_string_limit, invalid_arguments) { formatted = rcutils_format_string_limit(failing_allocator, 10, "%s", "test"); EXPECT_STREQ(NULL, formatted); } + +TEST(test_format_string_limit, on_internal_error) { +#ifdef _WIN32 +#define vsnprintf _vsnprintf_s +#endif + auto mock = mocking_utils::patch( + "lib:rcutils", vsnprintf, + [](char * buffer, auto...) { + if (nullptr == buffer) { + return 1; // provide a dummy value if buffer required size is queried + } + errno = EINVAL; + return -1; + }); +#ifdef _WIN32 +#undef vsnprintf +#endif + + rcutils_allocator_t allocator = rcutils_get_default_allocator(); + char * formatted = rcutils_format_string_limit(allocator, 10, "%s", "test"); + EXPECT_STREQ(NULL, formatted); +} diff --git a/test/test_logging_custom_env.cpp b/test/test_logging_custom_env.cpp index e76425ad..7fefa12a 100644 --- a/test/test_logging_custom_env.cpp +++ b/test/test_logging_custom_env.cpp @@ -18,6 +18,7 @@ #include #include "./allocator_testing_utils.h" +#include "./mocking_utils/patch.hpp" #include "osrf_testing_tools_cpp/scope_exit.hpp" #include "rcutils/logging.h" @@ -52,3 +53,10 @@ TEST(CLASSNAME(TestLoggingCustomEnv, RMW_IMPLEMENTATION), test_logging) { EXPECT_EQ(RCUTILS_RET_OK, rcutils_char_array_fini(&output_buf)); EXPECT_EQ(RCUTILS_RET_OK, rcutils_char_array_fini(&msg_buf)); } + +TEST(CLASSNAME(TestLoggingCustomEnv, RMW_IMPLEMENTATION), test_logging_with_buffering_issues) { + auto mock = mocking_utils::patch("lib:rcutils", setvbuf, [](auto && ...) {return -1;}); + EXPECT_FALSE(g_rcutils_logging_initialized); + EXPECT_EQ(RCUTILS_RET_ERROR, rcutils_logging_initialize()); + EXPECT_FALSE(g_rcutils_logging_initialized); +} diff --git a/test/test_shared_library.cpp b/test/test_shared_library.cpp index ca1b203a..f39b3105 100644 --- a/test/test_shared_library.cpp +++ b/test/test_shared_library.cpp @@ -24,6 +24,8 @@ #include "rcutils/get_env.h" +#include "./mocking_utils/patch.hpp" + class TestSharedLibrary : public ::testing::Test { protected: @@ -46,6 +48,27 @@ TEST_F(TestSharedLibrary, basic_load) { EXPECT_TRUE(lib.lib_pointer == NULL); EXPECT_FALSE(rcutils_is_shared_library_loaded(&lib)); + { +#ifdef _WIN32 +#define vsnprintf _vsnprintf_s +#endif + // Check internal errors are handled correctly + auto mock = mocking_utils::patch( + "lib:rcutils", vsnprintf, + [&](char * buffer, auto...) { + if (nullptr == buffer) { + return 1; + } + errno = EINVAL; + return -1; + }); +#ifdef _WIN32 +#undef vsnprintf +#endif + + ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false); + ASSERT_EQ(RCUTILS_RET_ERROR, ret); + } // Check debug name works first because rcutils_load_shared_library should be called on // non-debug symbol name ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, true); diff --git a/test/test_split.cpp b/test/test_split.cpp index 40bdfdeb..8a230aaf 100644 --- a/test/test_split.cpp +++ b/test/test_split.cpp @@ -19,6 +19,7 @@ #include "rcutils/error_handling.h" #include "rcutils/split.h" #include "rcutils/types/string_array.h" +#include "rcutils/types/char_array.h" #define ENABLE_LOGGING 1 @@ -77,7 +78,6 @@ TEST(test_split, split) { RCUTILS_RET_ERROR, rcutils_split("hello/world", '/', time_bomb_allocator, &tokens_fail)); - rcutils_string_array_t tokens0 = test_split("", '/', 0); ret = rcutils_string_array_fini(&tokens0); ASSERT_EQ(RCUTILS_RET_OK, ret); diff --git a/test/test_strerror.cpp b/test/test_strerror.cpp index a5759a85..6cee02a5 100644 --- a/test/test_strerror.cpp +++ b/test/test_strerror.cpp @@ -24,7 +24,7 @@ #include "rcutils/macros.h" #include "rcutils/strerror.h" -#include "./mimick.h" +#include "mimick/mimick.h" TEST(test_strerror, get_error) { // cleaning possible errors diff --git a/test/test_time.cpp b/test/test_time.cpp index 03796be5..aef3bbfa 100644 --- a/test/test_time.cpp +++ b/test/test_time.cpp @@ -23,6 +23,13 @@ #include "rcutils/error_handling.h" #include "rcutils/time.h" +#include "./mocking_utils/patch.hpp" + +// For mocking purposes +#ifdef _WIN32 +#define vsnprintf _vsnprintf_s +#endif + using osrf_testing_tools_cpp::memory_tools::disable_monitoring_in_all_threads; using osrf_testing_tools_cpp::memory_tools::enable_monitoring_in_all_threads; using osrf_testing_tools_cpp::memory_tools::on_unexpected_calloc; @@ -188,6 +195,59 @@ TEST_F(TestTimeFixture, test_rcutils_steady_time_now) { llabs(steady_diff - sc_diff), RCUTILS_MS_TO_NS(k_tolerance_ms)) << "steady_clock differs"; } +#if !defined(_WIN32) +// For mocking purposes +#if defined(__MACH__) +#define clock_gettime clock_get_time +#endif + +// Tests rcutils_system_time_now() and rcutils_steady_time_now() functions +// when system clocks misbehave. +TEST_F(TestTimeFixture, test_rcutils_with_bad_system_clocks) { + { + auto mock = mocking_utils::patch( + "lib:rcutils", clock_gettime, + [](auto, auto * ts) { + ts->tv_sec = -1; + ts->tv_nsec = 0; + return 0; + }); + + rcutils_time_point_value_t now = 0; + rcutils_ret_t ret = rcutils_system_time_now(&now); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); + + ret = rcutils_steady_time_now(&now); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); + } + + { + auto mock = mocking_utils::patch( + "lib:rcutils", clock_gettime, + [](auto, auto * ts) { + ts->tv_sec = 0; + ts->tv_nsec = -1; + return 0; + }); + + rcutils_time_point_value_t now = 0; + rcutils_ret_t ret = rcutils_system_time_now(&now); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); + + ret = rcutils_steady_time_now(&now); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); + } +} + +#if defined(__MACH__) +#undef clock_gettime +#endif +#endif // !defined(_WIN32) + // Tests the rcutils_time_point_value_as_nanoseconds_string() function. TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_nanoseconds_string) { rcutils_ret_t ret; @@ -239,6 +299,26 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_nanoseconds_string) { ret = rcutils_time_point_value_as_nanoseconds_string(&timepoint, buffer, sizeof(buffer)); EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; EXPECT_STREQ("-0000000000000000100", buffer); + +#ifdef _WIN32 +#define vsnprintf _vsnprintf_s +#endif + auto mock = mocking_utils::patch( + "lib:rcutils", vsnprintf, + [](char * buffer, auto...) { + if (nullptr == buffer) { + return 1; // provide a dummy value if buffer required size is queried + } + errno = EINVAL; + return -1; + }); +#ifdef _WIN32 +#undef vsnprintf +#endif + timepoint = 100; + ret = rcutils_time_point_value_as_nanoseconds_string(&timepoint, buffer, sizeof(buffer)); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); } // Tests the rcutils_time_point_value_as_seconds_string() function. @@ -292,4 +372,23 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_seconds_string) { ret = rcutils_time_point_value_as_seconds_string(&timepoint, buffer, sizeof(buffer)); EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; EXPECT_STREQ("-0000000000.000000100", buffer); + +#ifdef _WIN32 +#define vsnprintf _vsnprintf_s +#endif + auto mock = mocking_utils::patch( + "lib:rcutils", vsnprintf, [](char * buffer, auto...) { + if (nullptr == buffer) { + return 1; // provide a dummy value if buffer required size is queried + } + errno = EINVAL; + return -1; + }); +#ifdef _WIN32 +#undef vsnprintf +#endif + timepoint = 100; + ret = rcutils_time_point_value_as_seconds_string(&timepoint, buffer, sizeof(buffer)); + EXPECT_EQ(RCUTILS_RET_ERROR, ret); + rcutils_reset_error(); } From 164a36dc0860035fcb30e5e63211e273f9f75a4d Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 14:14:03 -0300 Subject: [PATCH 02/20] Address peer review comments. Signed-off-by: Michel Hidalgo --- test/test_filesystem.cpp | 2 ++ test/test_time.cpp | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/test/test_filesystem.cpp b/test/test_filesystem.cpp index 30c8b3d0..fbb24126 100644 --- a/test/test_filesystem.cpp +++ b/test/test_filesystem.cpp @@ -283,6 +283,8 @@ TEST_F(TestFilesystemFixture, is_readable_and_writable) { fs.file_info(path).st_mode |= mocking_utils::filesystem::Permissions::USER_READABLE; fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; EXPECT_FALSE(rcutils_is_readable_and_writable(path)); + EXPECT_TRUE(rcutils_is_writable(path)); + EXPECT_FALSE(rcutils_is_readable(path)); } { char * path = diff --git a/test/test_time.cpp b/test/test_time.cpp index aef3bbfa..4c282421 100644 --- a/test/test_time.cpp +++ b/test/test_time.cpp @@ -25,11 +25,6 @@ #include "./mocking_utils/patch.hpp" -// For mocking purposes -#ifdef _WIN32 -#define vsnprintf _vsnprintf_s -#endif - using osrf_testing_tools_cpp::memory_tools::disable_monitoring_in_all_threads; using osrf_testing_tools_cpp::memory_tools::enable_monitoring_in_all_threads; using osrf_testing_tools_cpp::memory_tools::on_unexpected_calloc; From 253d053ace137c7a5c2824e39d60535b320abac5 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 15:50:16 -0300 Subject: [PATCH 03/20] Fully document mocking_utils Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 42 +++-- test/mocking_utils/patch.hpp | 248 +++++++++++++++++++++--------- 2 files changed, 208 insertions(+), 82 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index b4fe26bf..e3a5950c 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -39,12 +39,14 @@ namespace mocking_utils namespace filesystem { +/// Platform-independent set of file type constants. struct FileTypes { static constexpr mode_t REGULAR_FILE = S_IFREG; static constexpr mode_t DIRECTORY = S_IFDIR; }; +/// Platform-independent set of file permission constants. struct Permissions { #ifndef _WIN32 @@ -56,23 +58,32 @@ struct Permissions #endif }; -template +/// Helper class for patching the filesystem API. +/** + * \tparam ID Numerical identifier for this patches. Ought to be unique. + */ +template class FileSystem { public: - explicit FileSystem(const std::string & target) + /// Construct mocked filesystem. + /** + * \param[in] scope Scope target string, using Mimick syntax. + * \see mocking_utils::Patch documentation for further reference. + */ + explicit FileSystem(const std::string & scope) #ifndef _WIN32 - : opendir_mock_(MOCKING_UTILS_PATCH_TARGET(target, opendir), + : opendir_mock_(MOCKING_UTILS_PATCH_TARGET(scope, opendir), MOCKING_UTILS_PATCH_PROXY(opendir)), #else - : find_first_file_mock_(MOCKING_UTILS_PATCH_TARGET(target, FindFirstFile), + : find_first_file_mock_(MOCKING_UTILS_PATCH_TARGET(scope, FindFirstFile), MOCKING_UTILS_PATCH_PROXY(FindFirstFile)), #endif #ifndef _GNU_SOURCE - stat_mock_(MOCKING_UTILS_PATCH_TARGET(target, stat), + stat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, stat), MOCKING_UTILS_PATCH_PROXY(stat)) #else - __xstat_mock_(MOCKING_UTILS_PATCH_TARGET(target, __xstat), + __xstat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, __xstat), MOCKING_UTILS_PATCH_PROXY(__xstat)) #endif { @@ -97,6 +108,7 @@ class FileSystem #endif } + /// Force APIs that return file descriptors or handles to fail as if these had been exhausted. void exhaust_file_descriptors() { #ifdef _WIN32 @@ -106,6 +118,12 @@ class FileSystem #endif } + /// Get information from file in the mocked filesystem. + /** + * \param[in] path Path to the file whose information is to be retrieved. + * If file is not found, one will be added. + * \return mutable reference to file information. + */ struct stat & file_info(const std::string & path) { return files_info_[path]; @@ -122,7 +140,7 @@ class FileSystem errno = ENOENT; return NULL; } - MOCKING_UTILS_PATCH_TYPE(N, opendir) opendir_mock_; + MOCKING_UTILS_PATCH_TYPE(ID, opendir) opendir_mock_; #else HANDLE do_FindFirstFile(LPCSTR, LPWIN32_FIND_DATAA) { @@ -134,7 +152,7 @@ class FileSystem return INVALID_HANDLE_VALUE; } - MOCKING_UTILS_PATCH_TYPE(N, FindFirstFile) find_first_file_mock_; + MOCKING_UTILS_PATCH_TYPE(ID, FindFirstFile) find_first_file_mock_; #endif #ifndef _GNU_SOURCE @@ -153,9 +171,9 @@ class FileSystem } #ifndef _GNU_SOURCE - MOCKING_UTILS_PATCH_TYPE(N, stat) stat_mock_; + MOCKING_UTILS_PATCH_TYPE(ID, stat) stat_mock_; #else - MOCKING_UTILS_PATCH_TYPE(N, __xstat) __xstat_mock_; + MOCKING_UTILS_PATCH_TYPE(ID, __xstat) __xstat_mock_; #endif int forced_errno_{0}; @@ -164,8 +182,8 @@ class FileSystem } // namespace filesystem -/// Patch filesystem API, based on `what` binary object should be affected. -#define patch_filesystem(what) filesystem::FileSystem<__COUNTER__>(what) +/// Patch filesystem API in a given `scope`. +#define patch_filesystem(scope) filesystem::FileSystem<__COUNTER__>(scope) } // namespace mocking_utils diff --git a/test/mocking_utils/patch.hpp b/test/mocking_utils/patch.hpp index 8b2de1da..4955dbe3 100644 --- a/test/mocking_utils/patch.hpp +++ b/test/mocking_utils/patch.hpp @@ -26,66 +26,120 @@ namespace mocking_utils { -template +/// 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; -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, ReturnType); + mmk_mock_define(mock_type, ReturnT); }; -template -struct PatchTraits +/// 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, ReturnType, ArgType0); + mmk_mock_define(mock_type, ReturnT, ArgT0); }; -template -struct PatchTraits +/// 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, ReturnType, ArgType0, ArgType1); + mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1); }; -template -struct PatchTraits +/// 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, ReturnType, ArgType0, ArgType1, ArgType2); + mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2); }; -template -struct PatchTraits +/// 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, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3); + mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3); }; -template -struct PatchTraits +/// 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, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4); + mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4); }; -template -struct PatchTraits +/// 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, ReturnType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5); + mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5); }; -template +/// 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; -template -struct Trampoline +/// Trampoline specialization for free functions. +template +struct Trampoline { static ReturnT base(ArgTs... args) { @@ -95,34 +149,69 @@ struct Trampoline static std::function target; }; -template +template std::function -Trampoline::target; - -template +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; + Trampoline::target = target; + return Trampoline::base; } -template +/// 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; -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; - - Patch(const std::string & target, std::function proxy) + 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); + 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; @@ -148,23 +237,27 @@ class Patch } } + /// Inject a @p replacement for the patched function. Patch & then_call(std::function replacement) & { auto type_erased_trampoline = - reinterpret_cast(prepare_trampoline(replacement)); + 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)); + 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);} @@ -172,46 +265,61 @@ class Patch std::function proxy_; }; -template +/// 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); + 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 required by Mimick. +/** + * Useful to enable patching functions that take arguments whose types + * do not define basic comparison operators, as required by Mimick. + */ #define MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(type, op) \ bool operator op(const type &, const type &) { \ return false; \ } -/// Get the exact mocking_utils::Patch type for a given id and function. -/// -/// Useful to avoid ignored attribute warnings when using the \b decltype operator. +/// 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. +/** + * 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-compliant, symbol target string based on `what` binary -/// object and `which` symbol should be targeted. -#define MOCKING_UTILS_PATCH_TARGET(what, which) \ - (std::string(RCUTILS_STRINGIFY(where)) + "@" + (what)) - -/// Patch a function `with` a used-provided replacement, based on `what` binary -/// object and `which` symbol should be patched. -#define patch(what, which, with) \ - make_patch<__COUNTER__, decltype(which)>( \ - MOCKING_UTILS_PATCH_TARGET(what, which), MOCKING_UTILS_PATCH_PROXY(where) \ - ).then_call(with) +/// 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) } // namespace mocking_utils From e4cbb8a2715f923f4149a82593db8459dcf54195 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 15:55:16 -0300 Subject: [PATCH 04/20] Fix filesystem test. Signed-off-by: Michel Hidalgo --- test/test_filesystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_filesystem.cpp b/test/test_filesystem.cpp index fbb24126..41903883 100644 --- a/test/test_filesystem.cpp +++ b/test/test_filesystem.cpp @@ -283,8 +283,8 @@ TEST_F(TestFilesystemFixture, is_readable_and_writable) { fs.file_info(path).st_mode |= mocking_utils::filesystem::Permissions::USER_READABLE; fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; EXPECT_FALSE(rcutils_is_readable_and_writable(path)); - EXPECT_TRUE(rcutils_is_writable(path)); - EXPECT_FALSE(rcutils_is_readable(path)); + EXPECT_FALSE(rcutils_is_writable(path)); + EXPECT_TRUE(rcutils_is_readable(path)); } { char * path = From b5098df1a76bee4a06852d00216ff36cb0e9d2ce Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 17:20:07 -0300 Subject: [PATCH 05/20] Fix ARM64 and MacOS failures. Signed-off-by: Michel Hidalgo --- test/mocking_utils/patch.hpp | 15 ++++++++++++--- test/test_time.cpp | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/test/mocking_utils/patch.hpp b/test/mocking_utils/patch.hpp index 4955dbe3..666a0857 100644 --- a/test/mocking_utils/patch.hpp +++ b/test/mocking_utils/patch.hpp @@ -15,6 +15,7 @@ #ifndef MOCKING_UTILS__PATCH_HPP_ #define MOCKING_UTILS__PATCH_HPP_ +#include #include #include #include @@ -288,9 +289,11 @@ auto make_patch(const std::string & target, std::function proxy) /** * Useful to enable patching functions that take arguments whose types * do not define basic comparison operators, as required by Mimick. - */ -#define MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(type, op) \ - bool operator op(const type &, const type &) { \ +*/ +#define MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(type_, op) \ + template \ + typename std::enable_if::value, bool>::type \ + operator op(const T &, const T &) { \ return false; \ } @@ -323,4 +326,10 @@ auto make_patch(const std::string & target, std::function proxy) } // namespace mocking_utils +// Define dummy operators for C standard va_list type +MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, ==) +MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, !=) +MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, <) +MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, >) + #endif // MOCKING_UTILS__PATCH_HPP_ diff --git a/test/test_time.cpp b/test/test_time.cpp index 4c282421..129c9bdf 100644 --- a/test/test_time.cpp +++ b/test/test_time.cpp @@ -191,8 +191,11 @@ TEST_F(TestTimeFixture, test_rcutils_steady_time_now) { } #if !defined(_WIN32) + // For mocking purposes #if defined(__MACH__) +#include +#include #define clock_gettime clock_get_time #endif From 83140ecc180b40aa9aff5e997d33cb19679d1b10 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 17:26:50 -0300 Subject: [PATCH 06/20] Address peer review comments. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 105 ++++++++++++++++++++++-------- test/mocking_utils/patch.hpp | 12 ++-- 2 files changed, 84 insertions(+), 33 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index e3a5950c..5a5c8c20 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -58,6 +58,8 @@ struct Permissions #endif }; +#if !defined(_WIN32) + /// Helper class for patching the filesystem API. /** * \tparam ID Numerical identifier for this patches. Ought to be unique. @@ -72,13 +74,8 @@ class FileSystem * \see mocking_utils::Patch documentation for further reference. */ explicit FileSystem(const std::string & scope) -#ifndef _WIN32 : opendir_mock_(MOCKING_UTILS_PATCH_TARGET(scope, opendir), MOCKING_UTILS_PATCH_PROXY(opendir)), -#else - : find_first_file_mock_(MOCKING_UTILS_PATCH_TARGET(scope, FindFirstFile), - MOCKING_UTILS_PATCH_PROXY(FindFirstFile)), -#endif #ifndef _GNU_SOURCE stat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, stat), MOCKING_UTILS_PATCH_PROXY(stat)) @@ -87,14 +84,7 @@ class FileSystem MOCKING_UTILS_PATCH_PROXY(__xstat)) #endif { -#ifndef _WIN32 opendir_mock_.then_call(std::bind(&FileSystem::do_opendir, this, std::placeholders::_1)); -#else - find_first_file_mock_.then_call( - std::bind( - &FileSystem::do_FindFirstFile, this, - std::placeholders::_1, std::placeholders::_2)) -#endif #ifndef _GNU_SOURCE stat_mock_.then_call( std::bind( @@ -111,11 +101,7 @@ class FileSystem /// Force APIs that return file descriptors or handles to fail as if these had been exhausted. void exhaust_file_descriptors() { -#ifdef _WIN32 - forced_errno_ = ERROR_NO_MORE_SEARCH_HANDLES; -#else forced_errno_ = EMFILE; -#endif } /// Get information from file in the mocked filesystem. @@ -130,7 +116,6 @@ class FileSystem } private: -#ifndef _WIN32 DIR * do_opendir(const char *) { if (forced_errno_ != 0) { @@ -141,7 +126,81 @@ class FileSystem return NULL; } MOCKING_UTILS_PATCH_TYPE(ID, opendir) opendir_mock_; + +#ifndef _GNU_SOURCE + int do_stat(const char * path, struct stat * info) + { #else + int do___xstat(int, const char * path, struct stat * info) + { +#endif + if (files_info_.count(path) == 0) { + errno = ENOENT; + return -1; + } + *info = files_info_[path]; + return 0; + } + +#ifndef _GNU_SOURCE + MOCKING_UTILS_PATCH_TYPE(ID, stat) stat_mock_; +#else + MOCKING_UTILS_PATCH_TYPE(ID, __xstat) __xstat_mock_; +#endif + + int forced_errno_{0}; + std::map files_info_; +}; + +#else // !defined(_WIN32) + +/// Helper class for patching the filesystem API. +/** + * \tparam ID Numerical identifier for this patches. Ought to be unique. + */ +template +class FileSystem +{ +public: + /// Construct mocked filesystem. + /** + * \param[in] scope Scope target string, using Mimick syntax. + * \see mocking_utils::Patch documentation for further reference. + */ + explicit FileSystem(const std::string & scope) + : find_first_file_mock_(MOCKING_UTILS_PATCH_TARGET(scope, FindFirstFile), + MOCKING_UTILS_PATCH_PROXY(FindFirstFile)), + stat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, stat), + MOCKING_UTILS_PATCH_PROXY(stat)) + { + find_first_file_mock_.then_call( + std::bind( + &FileSystem::do_FindFirstFile, this, + std::placeholders::_1, std::placeholders::_2)) + stat_mock_.then_call( + std::bind( + &FileSystem::do_stat, this, + std::placeholders::_1, std::placeholders::_2)); + } + + /// Force APIs that return file descriptors or handles to fail as if these had been exhausted. + void exhaust_file_descriptors() + { + forced_errno_ = ERROR_NO_MORE_SEARCH_HANDLES; + } + + /// Get information from file in the mocked filesystem. + /** + * \param[in] path Path to the file whose information is to be retrieved. + * If file is not found, one will be added. + * \return mutable reference to file information. + */ + struct stat & file_info(const std::string & path) + { + return files_info_[path]; + } + +private: HANDLE do_FindFirstFile(LPCSTR, LPWIN32_FIND_DATAA) { if (forced_errno_ != 0) { @@ -153,15 +212,9 @@ class FileSystem } MOCKING_UTILS_PATCH_TYPE(ID, FindFirstFile) find_first_file_mock_; -#endif -#ifndef _GNU_SOURCE int do_stat(const char * path, struct stat * info) { -#else - int do___xstat(int, const char * path, struct stat * info) - { -#endif if (files_info_.count(path) == 0) { errno = ENOENT; return -1; @@ -170,16 +223,14 @@ class FileSystem return 0; } -#ifndef _GNU_SOURCE MOCKING_UTILS_PATCH_TYPE(ID, stat) stat_mock_; -#else - MOCKING_UTILS_PATCH_TYPE(ID, __xstat) __xstat_mock_; -#endif int forced_errno_{0}; std::map files_info_; }; +#endif // else !defined(_WIN32) + } // namespace filesystem /// Patch filesystem API in a given `scope`. diff --git a/test/mocking_utils/patch.hpp b/test/mocking_utils/patch.hpp index 666a0857..8f13c82e 100644 --- a/test/mocking_utils/patch.hpp +++ b/test/mocking_utils/patch.hpp @@ -290,7 +290,7 @@ auto make_patch(const std::string & target, std::function proxy) * Useful to enable patching functions that take arguments whose types * do not define basic comparison operators, as required by Mimick. */ -#define MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(type_, op) \ +#define MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(type_, op) \ template \ typename std::enable_if::value, bool>::type \ operator op(const T &, const T &) { \ @@ -326,10 +326,10 @@ auto make_patch(const std::string & target, std::function proxy) } // namespace mocking_utils -// Define dummy operators for C standard va_list type -MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, ==) -MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, !=) -MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(va_list, <) -MOCKING_UTILS_DEFINE_DUMMY_OPERATOR(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 // MOCKING_UTILS__PATCH_HPP_ From 46a4c15b806cfe6ec1990260faed106e37763339 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 17:28:59 -0300 Subject: [PATCH 07/20] Remove unused include. Signed-off-by: Michel Hidalgo --- test/test_split.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_split.cpp b/test/test_split.cpp index 8a230aaf..8a5724a0 100644 --- a/test/test_split.cpp +++ b/test/test_split.cpp @@ -19,7 +19,6 @@ #include "rcutils/error_handling.h" #include "rcutils/split.h" #include "rcutils/types/string_array.h" -#include "rcutils/types/char_array.h" #define ENABLE_LOGGING 1 From 418a43322607d21c75d4529cf6fb43f92c36a40f Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 18:13:56 -0300 Subject: [PATCH 08/20] Address a few more review comments. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 11 +++++------ test/test_filesystem.cpp | 12 ++++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index 5a5c8c20..6ac35157 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -79,23 +79,22 @@ class FileSystem #ifndef _GNU_SOURCE stat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, stat), MOCKING_UTILS_PATCH_PROXY(stat)) -#else - __xstat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, __xstat), - MOCKING_UTILS_PATCH_PROXY(__xstat)) -#endif { - opendir_mock_.then_call(std::bind(&FileSystem::do_opendir, this, std::placeholders::_1)); -#ifndef _GNU_SOURCE stat_mock_.then_call( std::bind( &FileSystem::do_stat, this, std::placeholders::_1, std::placeholders::_2)); #else + __xstat_mock_( + MOCKING_UTILS_PATCH_TARGET(scope, __xstat), + MOCKING_UTILS_PATCH_PROXY(__xstat)) + { __xstat_mock_.then_call( std::bind( &FileSystem::do___xstat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); #endif + opendir_mock_.then_call(std::bind(&FileSystem::do_opendir, this, std::placeholders::_1)); } /// Force APIs that return file descriptors or handles to fail as if these had been exhausted. diff --git a/test/test_filesystem.cpp b/test/test_filesystem.cpp index 41903883..bc82969c 100644 --- a/test/test_filesystem.cpp +++ b/test/test_filesystem.cpp @@ -195,7 +195,7 @@ TEST_F(TestFilesystemFixture, is_readable) { EXPECT_TRUE(rcutils_is_readable(path)); } { - char * path = rcutils_join_path(this->test_path, "dummy_nonexisting_file.txt", g_allocator); + char * path = rcutils_join_path(this->test_path, "dummy_nonexistent_file.txt", g_allocator); OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( { g_allocator.deallocate(path, g_allocator.state); @@ -204,7 +204,7 @@ TEST_F(TestFilesystemFixture, is_readable) { EXPECT_FALSE(rcutils_is_readable(path)); } { - char * path = rcutils_join_path(this->test_path, "dummy_nonexisting_file.txt", g_allocator); + char * path = rcutils_join_path(this->test_path, "dummy_nonexistent_file.txt", g_allocator); OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( { g_allocator.deallocate(path, g_allocator.state); @@ -214,7 +214,7 @@ TEST_F(TestFilesystemFixture, is_readable) { } { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); - const char * path = "dummy_non_readable_file.txt"; + const char * path = "fake_unreadable_file.txt"; fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_READABLE; EXPECT_FALSE(rcutils_is_readable(path)); } @@ -241,7 +241,7 @@ TEST_F(TestFilesystemFixture, is_writable) { EXPECT_TRUE(rcutils_is_writable(path)); } { - char * path = rcutils_join_path(this->test_path, "dummy_nonexisting_file.txt", g_allocator); + char * path = rcutils_join_path(this->test_path, "dummy_nonexistent_file.txt", g_allocator); OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT( { g_allocator.deallocate(path, g_allocator.state); @@ -251,7 +251,7 @@ TEST_F(TestFilesystemFixture, is_writable) { } { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); - const char * path = "dummy_non_writable_file.txt"; + const char * path = "fake_unwritable_file.txt"; fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; EXPECT_FALSE(rcutils_is_writable(path)); } @@ -279,7 +279,7 @@ TEST_F(TestFilesystemFixture, is_readable_and_writable) { } { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); - const char * path = "fake_writable_but_non_readable_file.txt"; + const char * path = "fake_writable_but_unreadable_file.txt"; fs.file_info(path).st_mode |= mocking_utils::filesystem::Permissions::USER_READABLE; fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; EXPECT_FALSE(rcutils_is_readable_and_writable(path)); From 004f3cac598bd4e22a5f9df0afd7433fd23c1188 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 19:03:51 -0300 Subject: [PATCH 09/20] Disable va_list function patching on ARM machines. Signed-off-by: Michel Hidalgo --- test/mocking_utils/patch.hpp | 13 +++++++++++++ test/test_char_array.cpp | 2 ++ test/test_format_string.cpp | 2 ++ test/test_shared_library.cpp | 2 ++ test/test_time.cpp | 4 ++++ 5 files changed, 23 insertions(+) diff --git a/test/mocking_utils/patch.hpp b/test/mocking_utils/patch.hpp index 8f13c82e..2478d488 100644 --- a/test/mocking_utils/patch.hpp +++ b/test/mocking_utils/patch.hpp @@ -15,7 +15,18 @@ #ifndef MOCKING_UTILS__PATCH_HPP_ #define MOCKING_UTILS__PATCH_HPP_ +#define MOCKING_UTILS_SUPPORT_VA_LIST +#if 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 @@ -326,10 +337,12 @@ auto make_patch(const std::string & target, std::function proxy) } // 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/test/test_char_array.cpp b/test/test_char_array.cpp index e3aef8cc..ca37da76 100644 --- a/test/test_char_array.cpp +++ b/test/test_char_array.cpp @@ -129,6 +129,7 @@ TEST_F(ArrayCharTest, vsprintf_fail) { rcutils_reset_error(); char_array.allocator = allocator; +#ifdef MOCKING_UTILS_SUPPORT_VA_LIST #ifdef _WIN32 #define vsnprintf _vsnprintf_s #endif @@ -157,6 +158,7 @@ TEST_F(ArrayCharTest, vsprintf_fail) { ret = example_logger(&char_array, "Long string for the case %d", 2); EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); +#endif // MOCKING_UTILS_SUPPORT_VA_LIST EXPECT_EQ(RCUTILS_RET_OK, rcutils_char_array_fini(&char_array)); } diff --git a/test/test_format_string.cpp b/test/test_format_string.cpp index 4d418515..fbb1be15 100644 --- a/test/test_format_string.cpp +++ b/test/test_format_string.cpp @@ -62,6 +62,7 @@ TEST(test_format_string_limit, invalid_arguments) { EXPECT_STREQ(NULL, formatted); } +#ifdef MOCKING_UTILS_SUPPORT_VA_LIST TEST(test_format_string_limit, on_internal_error) { #ifdef _WIN32 #define vsnprintf _vsnprintf_s @@ -83,3 +84,4 @@ TEST(test_format_string_limit, on_internal_error) { char * formatted = rcutils_format_string_limit(allocator, 10, "%s", "test"); EXPECT_STREQ(NULL, formatted); } +#endif // MOCKING_UTILS_SUPPORT_VA_LIST diff --git a/test/test_shared_library.cpp b/test/test_shared_library.cpp index f39b3105..f566f041 100644 --- a/test/test_shared_library.cpp +++ b/test/test_shared_library.cpp @@ -49,6 +49,7 @@ TEST_F(TestSharedLibrary, basic_load) { EXPECT_FALSE(rcutils_is_shared_library_loaded(&lib)); { +#ifdef MOCKING_UTILS_SUPPORT_VA_LIST #ifdef _WIN32 #define vsnprintf _vsnprintf_s #endif @@ -65,6 +66,7 @@ TEST_F(TestSharedLibrary, basic_load) { #ifdef _WIN32 #undef vsnprintf #endif +#endif // MOCKING_UTILS_SUPPORT_VA_LIST ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false); ASSERT_EQ(RCUTILS_RET_ERROR, ret); diff --git a/test/test_time.cpp b/test/test_time.cpp index 129c9bdf..ef48ae6b 100644 --- a/test/test_time.cpp +++ b/test/test_time.cpp @@ -298,6 +298,7 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_nanoseconds_string) { EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; EXPECT_STREQ("-0000000000000000100", buffer); +#ifdef MOCKING_UTILS_SUPPORT_VA_LIST #ifdef _WIN32 #define vsnprintf _vsnprintf_s #endif @@ -317,6 +318,7 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_nanoseconds_string) { ret = rcutils_time_point_value_as_nanoseconds_string(&timepoint, buffer, sizeof(buffer)); EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); +#endif // MOCKING_UTILS_SUPPORT_VA_LIST } // Tests the rcutils_time_point_value_as_seconds_string() function. @@ -371,6 +373,7 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_seconds_string) { EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; EXPECT_STREQ("-0000000000.000000100", buffer); +#ifdef MOCKING_UTILS_SUPPORT_VA_LIST #ifdef _WIN32 #define vsnprintf _vsnprintf_s #endif @@ -389,4 +392,5 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_seconds_string) { ret = rcutils_time_point_value_as_seconds_string(&timepoint, buffer, sizeof(buffer)); EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); +#endif // MOCKING_UTILS_SUPPORT_VA_LIST } From 018a0cf2924edb17121524870ff252203085d611 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 19:07:33 -0300 Subject: [PATCH 10/20] Disable va_list function patching if __aarch64__ is defined. Signed-off-by: Michel Hidalgo --- test/mocking_utils/patch.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocking_utils/patch.hpp b/test/mocking_utils/patch.hpp index 2478d488..37442d06 100644 --- a/test/mocking_utils/patch.hpp +++ b/test/mocking_utils/patch.hpp @@ -16,7 +16,7 @@ #define MOCKING_UTILS__PATCH_HPP_ #define MOCKING_UTILS_SUPPORT_VA_LIST -#if defined(__arm__) || defined(_M_ARM) || defined(__thumb__) +#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. From 55d5c3d0fd2a5e6e8ceb102aff49cd0ab55be89f Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Wed, 5 Aug 2020 19:10:05 -0300 Subject: [PATCH 11/20] Fix shared library test. Signed-off-by: Michel Hidalgo --- test/test_shared_library.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_shared_library.cpp b/test/test_shared_library.cpp index f566f041..44c5d867 100644 --- a/test/test_shared_library.cpp +++ b/test/test_shared_library.cpp @@ -48,8 +48,8 @@ TEST_F(TestSharedLibrary, basic_load) { EXPECT_TRUE(lib.lib_pointer == NULL); EXPECT_FALSE(rcutils_is_shared_library_loaded(&lib)); - { #ifdef MOCKING_UTILS_SUPPORT_VA_LIST + { #ifdef _WIN32 #define vsnprintf _vsnprintf_s #endif @@ -66,11 +66,11 @@ TEST_F(TestSharedLibrary, basic_load) { #ifdef _WIN32 #undef vsnprintf #endif -#endif // MOCKING_UTILS_SUPPORT_VA_LIST - ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false); ASSERT_EQ(RCUTILS_RET_ERROR, ret); } +#endif // MOCKING_UTILS_SUPPORT_VA_LIST + // Check debug name works first because rcutils_load_shared_library should be called on // non-debug symbol name ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, true); From 893c09cb26a0a1a3a8867e551d7776dfd30e9e9c Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 13:51:09 -0300 Subject: [PATCH 12/20] Deal with vsnprintf quirks in Windows and MacOS. Signed-off-by: Michel Hidalgo --- test/mocking_utils/stdio.hpp | 55 ++++++++++++++++++++++++++++++++++++ test/test_char_array.cpp | 26 ++++++++++------- test/test_format_string.cpp | 21 ++++++++------ test/test_shared_library.cpp | 29 +++++++++++-------- test/test_time.cpp | 46 +++++++++++++++++++----------- 5 files changed, 130 insertions(+), 47 deletions(-) create mode 100644 test/mocking_utils/stdio.hpp diff --git a/test/mocking_utils/stdio.hpp b/test/mocking_utils/stdio.hpp new file mode 100644 index 00000000..50f255b1 --- /dev/null +++ b/test/mocking_utils/stdio.hpp @@ -0,0 +1,55 @@ +// 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 MOCKING_UTILS__STDIO_HPP_ +#define MOCKING_UTILS__STDIO_HPP_ + +#include +#include + +#include "patch.hpp" + +namespace mocking_utils +{ + +#define MOCKING_UTILS_CAN_PATCH_VSNPRINTF + +#if !defined(MOCKING_UTILS_SUPPORT_VA_LIST) +#undef MOCKING_UTILS_CAN_PATCH_VSNPRINTF +#endif + +#if !defined(__MACH__) // as vsnprintf binary API differs between C and C++ in MacOS +#undef MOCKING_UTILS_CAN_PATCH_VSNPRINTF +#endif + +#if defined(_WIN32) + +using _vsnprintf_s_type = + int (char *, size_t, size_t, const char *, va_list); // NOLINT(readability/casting) + +/// Patch _vsnprintf_s with the given `replacement` in the given `scope`. +#define patch__vsnprintf_s(scope, replacement) \ + // Signature must be explicitly provided to avoid ambiguity with template overloads. +make_patch<__COUNTER__, _vsnprintf_s_type>( \ + MOCKING_UTILS_PATCH_TARGET(scope, _vsnprintf_s), MOCKING_UTILS_PATCH_PROXY(_vsnprintf_s) \ +).then_call(replacement) + +/// Patch _vscprintf with the given `replacement` in the given `scope`. +#define patch__vscprintf(scope, replacement) patch(scope, _vscprintf, replacement) + +#endif // defined(_WIN32) + +} // namespace mocking_utils + +#endif // MOCKING_UTILS__STDIO_HPP_ diff --git a/test/test_char_array.cpp b/test/test_char_array.cpp index ca37da76..43bd712d 100644 --- a/test/test_char_array.cpp +++ b/test/test_char_array.cpp @@ -21,6 +21,7 @@ #include "rcutils/types/char_array.h" #include "./mocking_utils/patch.hpp" +#include "./mocking_utils/stdio.hpp" class ArrayCharTest : public ::testing::Test { @@ -129,24 +130,29 @@ TEST_F(ArrayCharTest, vsprintf_fail) { rcutils_reset_error(); char_array.allocator = allocator; -#ifdef MOCKING_UTILS_SUPPORT_VA_LIST -#ifdef _WIN32 -#define vsnprintf _vsnprintf_s -#endif +#ifdef MOCKING_UTILS_CAN_PATCH_VSNPRINTF size_t buffer_threshold = 0; +#ifdef _WIN32 + auto vscprintf_mock = mocking_utils::patch__vscprintf( + "lib:rcutils", [&](auto && ...) { + return static_cast(buffer_threshold); + }); + + auto vsnprintf_mock = mocking_utils::patch__vsnprintf_s( + "lib:rcutils", [&](auto && ...) { + errno = EINVAL; + return -1; + }); +#else auto mock = mocking_utils::patch( - "lib:rcutils", vsnprintf, - [&](char * buffer, size_t buffer_size, auto...) -> int { + "lib:rcutils", vsnprintf, [&](char * buffer, size_t buffer_size, auto...) { if (nullptr == buffer || buffer_size < buffer_threshold) { return static_cast(buffer_threshold); } errno = EINVAL; return -1; }); -#ifdef _WIN32 -#undef vsnprintf #endif - // Do not force char array resize. buffer_threshold = 5; ret = example_logger(&char_array, "Long string for the case %d", 2); @@ -158,7 +164,7 @@ TEST_F(ArrayCharTest, vsprintf_fail) { ret = example_logger(&char_array, "Long string for the case %d", 2); EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); -#endif // MOCKING_UTILS_SUPPORT_VA_LIST +#endif // MOCKING_UTILS_CAN_PATCH_VSNPRINTF EXPECT_EQ(RCUTILS_RET_OK, rcutils_char_array_fini(&char_array)); } diff --git a/test/test_format_string.cpp b/test/test_format_string.cpp index fbb1be15..6d3045a8 100644 --- a/test/test_format_string.cpp +++ b/test/test_format_string.cpp @@ -18,6 +18,7 @@ #include "./allocator_testing_utils.h" #include "./mocking_utils/patch.hpp" +#include "./mocking_utils/stdio.hpp" #include "rcutils/allocator.h" #include "rcutils/format_string.h" @@ -62,26 +63,30 @@ TEST(test_format_string_limit, invalid_arguments) { EXPECT_STREQ(NULL, formatted); } -#ifdef MOCKING_UTILS_SUPPORT_VA_LIST +#ifdef MOCKING_UTILS_CAN_PATCH_VSNPRINTF TEST(test_format_string_limit, on_internal_error) { #ifdef _WIN32 -#define vsnprintf _vsnprintf_s -#endif + auto _vscprintf_mock = mocking_utils::patch__vscprintf( + "lib:rcutils", [](auto && ...) {return 1;}); + + auto _vsnprintf_s_mock = mocking_utils::patch__vsnprintf_s( + "lib:rcutils", [](auto && ...) { + errno = EINVAL; + return -1; + }); +#else auto mock = mocking_utils::patch( - "lib:rcutils", vsnprintf, - [](char * buffer, auto...) { + "lib:rcutils", vsnprintf, [](char * buffer, auto && ...) { if (nullptr == buffer) { return 1; // provide a dummy value if buffer required size is queried } errno = EINVAL; return -1; }); -#ifdef _WIN32 -#undef vsnprintf #endif rcutils_allocator_t allocator = rcutils_get_default_allocator(); char * formatted = rcutils_format_string_limit(allocator, 10, "%s", "test"); EXPECT_STREQ(NULL, formatted); } -#endif // MOCKING_UTILS_SUPPORT_VA_LIST +#endif // MOCKING_UTILS_CAN_PATCH_VSNPRINTF diff --git a/test/test_shared_library.cpp b/test/test_shared_library.cpp index 44c5d867..e7b69089 100644 --- a/test/test_shared_library.cpp +++ b/test/test_shared_library.cpp @@ -17,6 +17,8 @@ #include #include "./allocator_testing_utils.h" +#include "./mocking_utils/patch.hpp" +#include "./mocking_utils/stdio.hpp" #include "rcutils/allocator.h" #include "rcutils/error_handling.h" @@ -24,8 +26,6 @@ #include "rcutils/get_env.h" -#include "./mocking_utils/patch.hpp" - class TestSharedLibrary : public ::testing::Test { protected: @@ -48,28 +48,33 @@ TEST_F(TestSharedLibrary, basic_load) { EXPECT_TRUE(lib.lib_pointer == NULL); EXPECT_FALSE(rcutils_is_shared_library_loaded(&lib)); -#ifdef MOCKING_UTILS_SUPPORT_VA_LIST +#ifdef MOCKING_UTILS_CAN_PATCH_VSNPRINTF { -#ifdef _WIN32 -#define vsnprintf _vsnprintf_s -#endif // Check internal errors are handled correctly +#ifdef _WIN32 + auto _vscprintf_mock = mocking_utils::patch__vscprintf( + "lib:rcutils", [](auto && ...) {return 1;}); + + auto _vsnprintf_s_mock = mocking_utils::patch__vsnprintf_s( + "lib:rcutils", [](auto && ...) { + errno = EINVAL; + return -1; + }); +#else auto mock = mocking_utils::patch( - "lib:rcutils", vsnprintf, - [&](char * buffer, auto...) { + "lib:rcutils", vsnprintf, [](char * buffer, auto && ...) { if (nullptr == buffer) { - return 1; + return 1; // provide a dummy value if buffer required size is queried } errno = EINVAL; return -1; }); -#ifdef _WIN32 -#undef vsnprintf #endif + ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false); ASSERT_EQ(RCUTILS_RET_ERROR, ret); } -#endif // MOCKING_UTILS_SUPPORT_VA_LIST +#endif // MOCKING_UTILS_CAN_PATCH_VSNPRINTF // Check debug name works first because rcutils_load_shared_library should be called on // non-debug symbol name diff --git a/test/test_time.cpp b/test/test_time.cpp index ef48ae6b..02233372 100644 --- a/test/test_time.cpp +++ b/test/test_time.cpp @@ -24,6 +24,7 @@ #include "rcutils/time.h" #include "./mocking_utils/patch.hpp" +#include "./mocking_utils/stdio.hpp" using osrf_testing_tools_cpp::memory_tools::disable_monitoring_in_all_threads; using osrf_testing_tools_cpp::memory_tools::enable_monitoring_in_all_threads; @@ -298,27 +299,32 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_nanoseconds_string) { EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; EXPECT_STREQ("-0000000000000000100", buffer); -#ifdef MOCKING_UTILS_SUPPORT_VA_LIST +#ifdef MOCKING_UTILS_CAN_PATCH_VSNPRINTF #ifdef _WIN32 -#define vsnprintf _vsnprintf_s -#endif + auto _vscprintf_mock = mocking_utils::patch__vscprintf( + "lib:rcutils", [](auto && ...) {return 1;}); + + auto _vsnprintf_s_mock = mocking_utils::patch__vsnprintf_s( + "lib:rcutils", [](auto && ...) { + errno = EINVAL; + return -1; + }); +#else auto mock = mocking_utils::patch( - "lib:rcutils", vsnprintf, - [](char * buffer, auto...) { + "lib:rcutils", vsnprintf, [](char * buffer, auto && ...) { if (nullptr == buffer) { - return 1; // provide a dummy value if buffer required size is queried + return 1; // provide a dummy value if buffer required size is queried } errno = EINVAL; return -1; }); -#ifdef _WIN32 -#undef vsnprintf #endif + timepoint = 100; ret = rcutils_time_point_value_as_nanoseconds_string(&timepoint, buffer, sizeof(buffer)); EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); -#endif // MOCKING_UTILS_SUPPORT_VA_LIST +#endif // MOCKING_UTILS_CAN_PATCH_VSNPRINTF } // Tests the rcutils_time_point_value_as_seconds_string() function. @@ -373,24 +379,30 @@ TEST_F(TestTimeFixture, test_rcutils_time_point_value_as_seconds_string) { EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; EXPECT_STREQ("-0000000000.000000100", buffer); -#ifdef MOCKING_UTILS_SUPPORT_VA_LIST +#ifdef MOCKING_UTILS_CAN_PATCH_VSNPRINTF #ifdef _WIN32 -#define vsnprintf _vsnprintf_s -#endif + auto _vscprintf_mock = mocking_utils::patch__vscprintf( + "lib:rcutils", [](auto && ...) {return 1;}); + + auto _vsnprintf_s_mock = mocking_utils::patch__vsnprintf_s( + "lib:rcutils", [](auto && ...) { + errno = EINVAL; + return -1; + }); +#else auto mock = mocking_utils::patch( - "lib:rcutils", vsnprintf, [](char * buffer, auto...) { + "lib:rcutils", vsnprintf, [](char * buffer, auto && ...) { if (nullptr == buffer) { - return 1; // provide a dummy value if buffer required size is queried + return 1; // provide a dummy value if buffer required size is queried } errno = EINVAL; return -1; }); -#ifdef _WIN32 -#undef vsnprintf #endif + timepoint = 100; ret = rcutils_time_point_value_as_seconds_string(&timepoint, buffer, sizeof(buffer)); EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); -#endif // MOCKING_UTILS_SUPPORT_VA_LIST +#endif // MOCKING_UTILS_CAN_PATCH_VSNPRINTF } From c8db8bd2a65c3020eb16571c2e46307cfed7455b Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 14:25:49 -0300 Subject: [PATCH 13/20] Fix failures on MacOS. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 16 +++++++++++++--- test/mocking_utils/stdio.hpp | 10 +++++----- test/test_time.cpp | 3 ++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index 6ac35157..adc2688b 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -60,6 +60,13 @@ struct Permissions #if !defined(_WIN32) +// Deal with binary API quirks in 64 bit MacOS. +#if defined(__MACH__) && defined(_DARWIN_FEATURE_64_BIT_INODE) +#define MOCKING_UTILS_FILESYSTEM_SYMBOL(sym) sym ## $INODE64 +#else +#define MOCKING_UTILS_FILESYSTEM_SYMBOL(sym) sym +#endif + /// Helper class for patching the filesystem API. /** * \tparam ID Numerical identifier for this patches. Ought to be unique. @@ -74,10 +81,12 @@ class FileSystem * \see mocking_utils::Patch documentation for further reference. */ explicit FileSystem(const std::string & scope) - : opendir_mock_(MOCKING_UTILS_PATCH_TARGET(scope, opendir), + : opendir_mock_( + MOCKING_UTILS_PATCH_TARGET(scope, MOCKING_UTILS_FILESYSTEM_SYMBOL(opendir)), MOCKING_UTILS_PATCH_PROXY(opendir)), #ifndef _GNU_SOURCE - stat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, stat), + stat_mock_( + MOCKING_UTILS_PATCH_TARGET(scope, MOCKING_UTILS_FILESYSTEM_SYMBOL(stat)), MOCKING_UTILS_PATCH_PROXY(stat)) { stat_mock_.then_call( @@ -85,8 +94,9 @@ class FileSystem &FileSystem::do_stat, this, std::placeholders::_1, std::placeholders::_2)); #else + // Deal with binary API quirks in GNU Linux. __xstat_mock_( - MOCKING_UTILS_PATCH_TARGET(scope, __xstat), + MOCKING_UTILS_PATCH_TARGET(scope, MOCKING_UTILS_FILESYSTEM_SYMBOL(__xstat)), MOCKING_UTILS_PATCH_PROXY(__xstat)) { __xstat_mock_.then_call( diff --git a/test/mocking_utils/stdio.hpp b/test/mocking_utils/stdio.hpp index 50f255b1..df753e90 100644 --- a/test/mocking_utils/stdio.hpp +++ b/test/mocking_utils/stdio.hpp @@ -29,7 +29,7 @@ namespace mocking_utils #undef MOCKING_UTILS_CAN_PATCH_VSNPRINTF #endif -#if !defined(__MACH__) // as vsnprintf binary API differs between C and C++ in MacOS +#if defined(__MACH__) // as vsnprintf binary API differs between C and C++ in MacOS #undef MOCKING_UTILS_CAN_PATCH_VSNPRINTF #endif @@ -39,11 +39,11 @@ using _vsnprintf_s_type = int (char *, size_t, size_t, const char *, va_list); // NOLINT(readability/casting) /// Patch _vsnprintf_s with the given `replacement` in the given `scope`. +// Signature must be explicitly provided to avoid ambiguity with template overloads. #define patch__vsnprintf_s(scope, replacement) \ - // Signature must be explicitly provided to avoid ambiguity with template overloads. -make_patch<__COUNTER__, _vsnprintf_s_type>( \ - MOCKING_UTILS_PATCH_TARGET(scope, _vsnprintf_s), MOCKING_UTILS_PATCH_PROXY(_vsnprintf_s) \ -).then_call(replacement) + make_patch<__COUNTER__, _vsnprintf_s_type>( \ + MOCKING_UTILS_PATCH_TARGET(scope, _vsnprintf_s), MOCKING_UTILS_PATCH_PROXY(_vsnprintf_s) \ + ).then_call(replacement) /// Patch _vscprintf with the given `replacement` in the given `scope`. #define patch__vscprintf(scope, replacement) patch(scope, _vscprintf, replacement) diff --git a/test/test_time.cpp b/test/test_time.cpp index 02233372..b4f798e2 100644 --- a/test/test_time.cpp +++ b/test/test_time.cpp @@ -203,6 +203,7 @@ TEST_F(TestTimeFixture, test_rcutils_steady_time_now) { // Tests rcutils_system_time_now() and rcutils_steady_time_now() functions // when system clocks misbehave. TEST_F(TestTimeFixture, test_rcutils_with_bad_system_clocks) { +#if !defined (__MACH__) // as tv_sec is an unsigned integer there { auto mock = mocking_utils::patch( "lib:rcutils", clock_gettime, @@ -221,7 +222,7 @@ TEST_F(TestTimeFixture, test_rcutils_with_bad_system_clocks) { EXPECT_EQ(RCUTILS_RET_ERROR, ret); rcutils_reset_error(); } - +#endif { auto mock = mocking_utils::patch( "lib:rcutils", clock_gettime, From 44109e76fde8e0d4f3ab1be88073f0e771e0eefb Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 14:42:19 -0300 Subject: [PATCH 14/20] Avoid -Wdollar-in-identifier-extension. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index adc2688b..8a5f34d5 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -62,9 +62,10 @@ struct Permissions // Deal with binary API quirks in 64 bit MacOS. #if defined(__MACH__) && defined(_DARWIN_FEATURE_64_BIT_INODE) -#define MOCKING_UTILS_FILESYSTEM_SYMBOL(sym) sym ## $INODE64 +#define MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, function) \ + (std::string(RCUTILS_STRINGIFY(function) "$INODE64") + "@" + (scope)) #else -#define MOCKING_UTILS_FILESYSTEM_SYMBOL(sym) sym +#define MOCKING_UTILS_FILESYSTEM_PATCH_TARGET MOCKING_UTILS_PATCH_TARGET #endif /// Helper class for patching the filesystem API. @@ -82,11 +83,11 @@ class FileSystem */ explicit FileSystem(const std::string & scope) : opendir_mock_( - MOCKING_UTILS_PATCH_TARGET(scope, MOCKING_UTILS_FILESYSTEM_SYMBOL(opendir)), + MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, opendir), MOCKING_UTILS_PATCH_PROXY(opendir)), #ifndef _GNU_SOURCE stat_mock_( - MOCKING_UTILS_PATCH_TARGET(scope, MOCKING_UTILS_FILESYSTEM_SYMBOL(stat)), + MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, stat), MOCKING_UTILS_PATCH_PROXY(stat)) { stat_mock_.then_call( @@ -96,7 +97,7 @@ class FileSystem #else // Deal with binary API quirks in GNU Linux. __xstat_mock_( - MOCKING_UTILS_PATCH_TARGET(scope, MOCKING_UTILS_FILESYSTEM_SYMBOL(__xstat)), + MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, __xstat), MOCKING_UTILS_PATCH_PROXY(__xstat)) { __xstat_mock_.then_call( From 2aaa433887d92f6d4123a7f2a35dd8264fe56a0b Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 15:32:16 -0300 Subject: [PATCH 15/20] Fix mocking_utils::filesystem constants. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 24 ++++++++++++------------ test/test_filesystem.cpp | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index 8a5f34d5..e350d31e 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -40,23 +40,23 @@ namespace filesystem { /// Platform-independent set of file type constants. -struct FileTypes +namespace file_types { - static constexpr mode_t REGULAR_FILE = S_IFREG; - static constexpr mode_t DIRECTORY = S_IFDIR; -}; +constexpr auto REGULAR_FILE = S_IFREG; +constexpr auto DIRECTORY = S_IFDIR; +} // namespace file_types /// Platform-independent set of file permission constants. -struct Permissions +namespace permissions { #ifndef _WIN32 - static constexpr mode_t USER_READABLE = S_IRUSR; - static constexpr mode_t USER_WRITABLE = S_IWUSR; +constexpr auto USER_READABLE = S_IRUSR; +constexpr auto USER_WRITABLE = S_IWUSR; #else - static constexpr mode_t USER_READABLE = _S_IREAD; - static constexpr mode_t USER_WRITABLE = _S_IWRITE; +constexpr auto USER_READABLE = _S_IREAD; +constexpr auto USER_WRITABLE = _S_IWRITE; #endif -}; +} // namespace permissions #if !defined(_WIN32) @@ -178,9 +178,9 @@ class FileSystem * \see mocking_utils::Patch documentation for further reference. */ explicit FileSystem(const std::string & scope) - : find_first_file_mock_(MOCKING_UTILS_PATCH_TARGET(scope, FindFirstFile), + : find_first_file_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, FindFirstFile), MOCKING_UTILS_PATCH_PROXY(FindFirstFile)), - stat_mock_(MOCKING_UTILS_PATCH_TARGET(scope, stat), + stat_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, stat), MOCKING_UTILS_PATCH_PROXY(stat)) { find_first_file_mock_.then_call( diff --git a/test/test_filesystem.cpp b/test/test_filesystem.cpp index bc82969c..8e9f1c16 100644 --- a/test/test_filesystem.cpp +++ b/test/test_filesystem.cpp @@ -215,7 +215,7 @@ TEST_F(TestFilesystemFixture, is_readable) { { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); const char * path = "fake_unreadable_file.txt"; - fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_READABLE; + fs.file_info(path).st_mode &= ~mocking_utils::filesystem::permissions::USER_READABLE; EXPECT_FALSE(rcutils_is_readable(path)); } } @@ -252,7 +252,7 @@ TEST_F(TestFilesystemFixture, is_writable) { { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); const char * path = "fake_unwritable_file.txt"; - fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; + fs.file_info(path).st_mode &= ~mocking_utils::filesystem::permissions::USER_WRITABLE; EXPECT_FALSE(rcutils_is_writable(path)); } } @@ -280,8 +280,8 @@ TEST_F(TestFilesystemFixture, is_readable_and_writable) { { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); const char * path = "fake_writable_but_unreadable_file.txt"; - fs.file_info(path).st_mode |= mocking_utils::filesystem::Permissions::USER_READABLE; - fs.file_info(path).st_mode &= ~mocking_utils::filesystem::Permissions::USER_WRITABLE; + fs.file_info(path).st_mode |= mocking_utils::filesystem::permissions::USER_READABLE; + fs.file_info(path).st_mode &= ~mocking_utils::filesystem::permissions::USER_WRITABLE; EXPECT_FALSE(rcutils_is_readable_and_writable(path)); EXPECT_FALSE(rcutils_is_writable(path)); EXPECT_TRUE(rcutils_is_readable(path)); @@ -376,7 +376,7 @@ TEST_F(TestFilesystemFixture, calculate_directory_size) { { auto fs = mocking_utils::patch_filesystem("lib:rcutils"); const char * path = "some_fake_directory/some_fake_folder"; - fs.file_info(path).st_mode |= mocking_utils::filesystem::FileTypes::DIRECTORY; + fs.file_info(path).st_mode |= mocking_utils::filesystem::file_types::DIRECTORY; fs.exhaust_file_descriptors(); size = rcutils_calculate_directory_size(path, g_allocator); EXPECT_EQ(0u, size); From 3f6416c2379134626ce615a8aad9e7166b0a5c7e Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 15:33:57 -0300 Subject: [PATCH 16/20] Fully qualify _vsnprintf_s_type. Signed-off-by: Michel Hidalgo --- test/mocking_utils/stdio.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocking_utils/stdio.hpp b/test/mocking_utils/stdio.hpp index df753e90..81143a9b 100644 --- a/test/mocking_utils/stdio.hpp +++ b/test/mocking_utils/stdio.hpp @@ -41,7 +41,7 @@ using _vsnprintf_s_type = /// Patch _vsnprintf_s with the given `replacement` in the given `scope`. // Signature must be explicitly provided to avoid ambiguity with template overloads. #define patch__vsnprintf_s(scope, replacement) \ - make_patch<__COUNTER__, _vsnprintf_s_type>( \ + make_patch<__COUNTER__, mocking_utils::_vsnprintf_s_type>( \ MOCKING_UTILS_PATCH_TARGET(scope, _vsnprintf_s), MOCKING_UTILS_PATCH_PROXY(_vsnprintf_s) \ ).then_call(replacement) From 55dfebefdc5e31873ce9ea9446ff43f51344aaf2 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 15:46:57 -0300 Subject: [PATCH 17/20] Fix failures on Windows. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 6 +++--- test/mocking_utils/stdio.hpp | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index e350d31e..7b3ce1a3 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -58,8 +58,6 @@ constexpr auto USER_WRITABLE = _S_IWRITE; #endif } // namespace permissions -#if !defined(_WIN32) - // Deal with binary API quirks in 64 bit MacOS. #if defined(__MACH__) && defined(_DARWIN_FEATURE_64_BIT_INODE) #define MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, function) \ @@ -68,6 +66,8 @@ constexpr auto USER_WRITABLE = _S_IWRITE; #define MOCKING_UTILS_FILESYSTEM_PATCH_TARGET MOCKING_UTILS_PATCH_TARGET #endif +#if !defined(_WIN32) + /// Helper class for patching the filesystem API. /** * \tparam ID Numerical identifier for this patches. Ought to be unique. @@ -186,7 +186,7 @@ class FileSystem find_first_file_mock_.then_call( std::bind( &FileSystem::do_FindFirstFile, this, - std::placeholders::_1, std::placeholders::_2)) + std::placeholders::_1, std::placeholders::_2)); stat_mock_.then_call( std::bind( &FileSystem::do_stat, this, diff --git a/test/mocking_utils/stdio.hpp b/test/mocking_utils/stdio.hpp index 81143a9b..aa1ee1f8 100644 --- a/test/mocking_utils/stdio.hpp +++ b/test/mocking_utils/stdio.hpp @@ -33,6 +33,10 @@ namespace mocking_utils #undef MOCKING_UTILS_CAN_PATCH_VSNPRINTF #endif +#if defined(_WIN32) // as vsnprintf binary APIs are undocumented in Windows +#undef MOCKING_UTILS_CAN_PATCH_VSNPRINTF +#endif + #if defined(_WIN32) using _vsnprintf_s_type = @@ -41,7 +45,7 @@ using _vsnprintf_s_type = /// Patch _vsnprintf_s with the given `replacement` in the given `scope`. // Signature must be explicitly provided to avoid ambiguity with template overloads. #define patch__vsnprintf_s(scope, replacement) \ - make_patch<__COUNTER__, mocking_utils::_vsnprintf_s_type>( \ + make_patch<__COUNTER__, mocking_utils::_vsnprintf_s_type>( \ MOCKING_UTILS_PATCH_TARGET(scope, _vsnprintf_s), MOCKING_UTILS_PATCH_PROXY(_vsnprintf_s) \ ).then_call(replacement) From 44194a1335407c0766d809179e4d1e683bececb8 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 16:29:46 -0300 Subject: [PATCH 18/20] Fix mocking_utils::filesystem on Windows. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index 7b3ce1a3..66fe9a09 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -178,18 +178,18 @@ class FileSystem * \see mocking_utils::Patch documentation for further reference. */ explicit FileSystem(const std::string & scope) - : find_first_file_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, FindFirstFile), - MOCKING_UTILS_PATCH_PROXY(FindFirstFile)), - stat_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, stat), - MOCKING_UTILS_PATCH_PROXY(stat)) + : find_first_file_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, FindFirstFileA), + MOCKING_UTILS_PATCH_PROXY(FindFirstFileA)), + stat_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, _stat), + MOCKING_UTILS_PATCH_PROXY(_stat)) { find_first_file_mock_.then_call( std::bind( - &FileSystem::do_FindFirstFile, this, + &FileSystem::do_FindFirstFileA, this, std::placeholders::_1, std::placeholders::_2)); stat_mock_.then_call( std::bind( - &FileSystem::do_stat, this, + &FileSystem::do__stat, this, std::placeholders::_1, std::placeholders::_2)); } @@ -205,13 +205,13 @@ class FileSystem * If file is not found, one will be added. * \return mutable reference to file information. */ - struct stat & file_info(const std::string & path) + struct _stat & file_info(const std::string & path) { return files_info_[path]; } private: - HANDLE do_FindFirstFile(LPCSTR, LPWIN32_FIND_DATAA) + HANDLE do_FindFirstFileA(LPCSTR, LPWIN32_FIND_DATAA) { if (forced_errno_ != 0) { SetLastError(forced_errno_); @@ -221,9 +221,9 @@ class FileSystem return INVALID_HANDLE_VALUE; } - MOCKING_UTILS_PATCH_TYPE(ID, FindFirstFile) find_first_file_mock_; + MOCKING_UTILS_PATCH_TYPE(ID, FindFirstFileA) find_first_file_mock_; - int do_stat(const char * path, struct stat * info) + int do__stat(const char * path, struct _stat * info) { if (files_info_.count(path) == 0) { errno = ENOENT; @@ -233,10 +233,10 @@ class FileSystem return 0; } - MOCKING_UTILS_PATCH_TYPE(ID, stat) stat_mock_; + MOCKING_UTILS_PATCH_TYPE(ID, _stat) _stat_mock_; int forced_errno_{0}; - std::map files_info_; + std::map files_info_; }; #endif // else !defined(_WIN32) From 17580458e4b35639daaadd2cb8c1107a151967d7 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 18:36:37 -0300 Subject: [PATCH 19/20] Fix typo. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index 66fe9a09..96c80909 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -180,7 +180,7 @@ class FileSystem explicit FileSystem(const std::string & scope) : find_first_file_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, FindFirstFileA), MOCKING_UTILS_PATCH_PROXY(FindFirstFileA)), - stat_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, _stat), + _stat_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, _stat), MOCKING_UTILS_PATCH_PROXY(_stat)) { find_first_file_mock_.then_call( From b22a57dd671d88c0e2b4d4487a15dde6b7bee155 Mon Sep 17 00:00:00 2001 From: Michel Hidalgo Date: Thu, 6 Aug 2020 19:16:07 -0300 Subject: [PATCH 20/20] Fix another typo. Signed-off-by: Michel Hidalgo --- test/mocking_utils/filesystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocking_utils/filesystem.hpp b/test/mocking_utils/filesystem.hpp index 96c80909..789a1ebc 100644 --- a/test/mocking_utils/filesystem.hpp +++ b/test/mocking_utils/filesystem.hpp @@ -187,7 +187,7 @@ class FileSystem std::bind( &FileSystem::do_FindFirstFileA, this, std::placeholders::_1, std::placeholders::_2)); - stat_mock_.then_call( + _stat_mock_.then_call( std::bind( &FileSystem::do__stat, this, std::placeholders::_1, std::placeholders::_2));