Skip to content

Commit

Permalink
Improve test coverage mocking system calls (#272)
Browse files Browse the repository at this point in the history
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
  • Loading branch information
hidmic authored and ahcorde committed Oct 2, 2020
1 parent 09515e6 commit ecad08b
Show file tree
Hide file tree
Showing 11 changed files with 937 additions and 23 deletions.
18 changes: 9 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
251 changes: 251 additions & 0 deletions test/mocking_utils/filesystem.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// 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 <stdio.h>
#include <string.h>
#include <sys/stat.h>

#ifndef _WIN32
#include <sys/types.h>
#include <dirent.h>
#else
#include <windows.h>
#endif

#include <map>
#include <string>
#include <type_traits>

#include "rcutils/macros.h"

#include "patch.hpp"

namespace mocking_utils
{
namespace filesystem
{

/// Platform-independent set of file type constants.
namespace file_types
{
constexpr auto REGULAR_FILE = S_IFREG;
constexpr auto DIRECTORY = S_IFDIR;
} // namespace file_types

/// Platform-independent set of file permission constants.
namespace permissions
{
#ifndef _WIN32
constexpr auto USER_READABLE = S_IRUSR;
constexpr auto USER_WRITABLE = S_IWUSR;
#else
constexpr auto USER_READABLE = _S_IREAD;
constexpr auto USER_WRITABLE = _S_IWRITE;
#endif
} // namespace permissions

// 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) \
(std::string(RCUTILS_STRINGIFY(function) "$INODE64") + "@" + (scope))
#else
#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.
*/
template<size_t ID>
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)
: opendir_mock_(
MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, opendir),
MOCKING_UTILS_PATCH_PROXY(opendir)),
#ifndef _GNU_SOURCE
stat_mock_(
MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, stat),
MOCKING_UTILS_PATCH_PROXY(stat))
{
stat_mock_.then_call(
std::bind(
&FileSystem::do_stat, this,
std::placeholders::_1, std::placeholders::_2));
#else
// Deal with binary API quirks in GNU Linux.
__xstat_mock_(
MOCKING_UTILS_FILESYSTEM_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.
void exhaust_file_descriptors()
{
forced_errno_ = EMFILE;
}

/// 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:
DIR * do_opendir(const char *)
{
if (forced_errno_ != 0) {
errno = forced_errno_;
return NULL;
}
errno = ENOENT;
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<std::string, struct stat> files_info_;
};

#else // !defined(_WIN32)

/// Helper class for patching the filesystem API.
/**
* \tparam ID Numerical identifier for this patches. Ought to be unique.
*/
template<size_t ID>
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_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_FindFirstFileA, 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_FindFirstFileA(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(ID, FindFirstFileA) find_first_file_mock_;

int do__stat(const char * path, struct _stat * info)
{
if (files_info_.count(path) == 0) {
errno = ENOENT;
return -1;
}
*info = files_info_[path];
return 0;
}

MOCKING_UTILS_PATCH_TYPE(ID, _stat) _stat_mock_;

int forced_errno_{0};
std::map<std::string, struct _stat> files_info_;
};

#endif // else !defined(_WIN32)

} // namespace filesystem

/// Patch filesystem API in a given `scope`.
#define patch_filesystem(scope) filesystem::FileSystem<__COUNTER__>(scope)

} // namespace mocking_utils

#endif // MOCKING_UTILS__FILESYSTEM_HPP_
Loading

0 comments on commit ecad08b

Please sign in to comment.