Skip to content

Commit ee883ab

Browse files
authored
Improve test coverage mocking system calls (#272)
Signed-off-by: Michel Hidalgo <[email protected]>
1 parent 8faa915 commit ee883ab

File tree

11 files changed

+937
-23
lines changed

11 files changed

+937
-23
lines changed

CMakeLists.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ if(BUILD_TESTING)
181181
osrf_testing_tools_cpp::memory_tools LIBRARY_PRELOAD_ENVIRONMENT_IS_AVAILABLE)
182182

183183
ament_add_gtest(test_logging test/test_logging.cpp)
184-
target_link_libraries(test_logging ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools)
184+
target_link_libraries(test_logging ${PROJECT_NAME} mimick osrf_testing_tools_cpp::memory_tools)
185185

186186
add_executable(test_logging_long_messages test/test_logging_long_messages.cpp)
187187
target_link_libraries(test_logging_long_messages ${PROJECT_NAME})
@@ -234,7 +234,7 @@ if(BUILD_TESTING)
234234
test/test_char_array.cpp
235235
)
236236
if(TARGET test_char_array)
237-
target_link_libraries(test_char_array ${PROJECT_NAME})
237+
target_link_libraries(test_char_array ${PROJECT_NAME} mimick)
238238
endif()
239239

240240
# Can't use C++ with stdatomic_helper.h
@@ -275,7 +275,7 @@ if(BUILD_TESTING)
275275
test/test_split.cpp
276276
)
277277
if(TARGET test_split)
278-
target_link_libraries(test_split ${PROJECT_NAME})
278+
target_link_libraries(test_split ${PROJECT_NAME} mimick)
279279
endif()
280280

281281
rcutils_custom_add_gtest(test_find
@@ -322,7 +322,7 @@ if(BUILD_TESTING)
322322
)
323323
if(TARGET test_filesystem)
324324
ament_target_dependencies(test_filesystem "osrf_testing_tools_cpp")
325-
target_link_libraries(test_filesystem ${PROJECT_NAME})
325+
target_link_libraries(test_filesystem ${PROJECT_NAME} mimick)
326326
target_compile_definitions(test_filesystem PRIVATE BUILD_DIR="${CMAKE_CURRENT_BINARY_DIR}")
327327
endif()
328328

@@ -337,7 +337,7 @@ if(BUILD_TESTING)
337337
test/test_format_string.cpp
338338
)
339339
if(TARGET test_format_string)
340-
target_link_libraries(test_format_string ${PROJECT_NAME})
340+
target_link_libraries(test_format_string ${PROJECT_NAME} mimick)
341341
endif()
342342

343343
rcutils_custom_add_gtest(test_string_map
@@ -375,14 +375,14 @@ if(BUILD_TESTING)
375375
# which is appropriate when building the dll but not consuming it.
376376
target_compile_definitions(dummy_shared_library PRIVATE "DUMMY_SHARED_LIBRARY_BUILDING_DLL")
377377
endif()
378-
target_link_libraries(test_shared_library ${PROJECT_NAME})
378+
target_link_libraries(test_shared_library ${PROJECT_NAME} mimick)
379379
endif()
380380

381381
rcutils_custom_add_gtest(test_time
382382
test/test_time.cpp
383383
ENV ${memory_tools_test_env_vars})
384384
if(TARGET test_time)
385-
target_link_libraries(test_time ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools)
385+
target_link_libraries(test_time ${PROJECT_NAME} mimick osrf_testing_tools_cpp::memory_tools)
386386
endif()
387387

388388
rcutils_custom_add_gtest(test_snprintf
@@ -436,7 +436,7 @@ if(BUILD_TESTING)
436436
RCUTILS_COLORIZED_OUTPUT=1
437437
)
438438
if(TARGET test_logging_custom_env)
439-
target_link_libraries(test_logging_custom_env ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools)
439+
target_link_libraries(test_logging_custom_env ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools mimick)
440440
endif()
441441

442442
# RCUTILS_LOGGING_MAX_OUTPUT_FORMAT_LEN is defined as 2048, truncation should occur
@@ -452,7 +452,7 @@ if(BUILD_TESTING)
452452
RCUTILS_COLORIZED_OUTPUT=0
453453
)
454454
if(TARGET test_logging_custom_env2)
455-
target_link_libraries(test_logging_custom_env2 ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools)
455+
target_link_libraries(test_logging_custom_env2 ${PROJECT_NAME} osrf_testing_tools_cpp::memory_tools mimick)
456456
endif()
457457

458458
rcutils_custom_add_gtest(test_logging_bad_env test/test_logging_bad_env.cpp

test/mocking_utils/filesystem.hpp

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// Copyright 2020 Open Source Robotics Foundation, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef MOCKING_UTILS__FILESYSTEM_HPP_
16+
#define MOCKING_UTILS__FILESYSTEM_HPP_
17+
18+
#include <stdio.h>
19+
#include <string.h>
20+
#include <sys/stat.h>
21+
22+
#ifndef _WIN32
23+
#include <sys/types.h>
24+
#include <dirent.h>
25+
#else
26+
#include <windows.h>
27+
#endif
28+
29+
#include <map>
30+
#include <string>
31+
#include <type_traits>
32+
33+
#include "rcutils/macros.h"
34+
35+
#include "patch.hpp"
36+
37+
namespace mocking_utils
38+
{
39+
namespace filesystem
40+
{
41+
42+
/// Platform-independent set of file type constants.
43+
namespace file_types
44+
{
45+
constexpr auto REGULAR_FILE = S_IFREG;
46+
constexpr auto DIRECTORY = S_IFDIR;
47+
} // namespace file_types
48+
49+
/// Platform-independent set of file permission constants.
50+
namespace permissions
51+
{
52+
#ifndef _WIN32
53+
constexpr auto USER_READABLE = S_IRUSR;
54+
constexpr auto USER_WRITABLE = S_IWUSR;
55+
#else
56+
constexpr auto USER_READABLE = _S_IREAD;
57+
constexpr auto USER_WRITABLE = _S_IWRITE;
58+
#endif
59+
} // namespace permissions
60+
61+
// Deal with binary API quirks in 64 bit MacOS.
62+
#if defined(__MACH__) && defined(_DARWIN_FEATURE_64_BIT_INODE)
63+
#define MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, function) \
64+
(std::string(RCUTILS_STRINGIFY(function) "$INODE64") + "@" + (scope))
65+
#else
66+
#define MOCKING_UTILS_FILESYSTEM_PATCH_TARGET MOCKING_UTILS_PATCH_TARGET
67+
#endif
68+
69+
#if !defined(_WIN32)
70+
71+
/// Helper class for patching the filesystem API.
72+
/**
73+
* \tparam ID Numerical identifier for this patches. Ought to be unique.
74+
*/
75+
template<size_t ID>
76+
class FileSystem
77+
{
78+
public:
79+
/// Construct mocked filesystem.
80+
/**
81+
* \param[in] scope Scope target string, using Mimick syntax.
82+
* \see mocking_utils::Patch documentation for further reference.
83+
*/
84+
explicit FileSystem(const std::string & scope)
85+
: opendir_mock_(
86+
MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, opendir),
87+
MOCKING_UTILS_PATCH_PROXY(opendir)),
88+
#ifndef _GNU_SOURCE
89+
stat_mock_(
90+
MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, stat),
91+
MOCKING_UTILS_PATCH_PROXY(stat))
92+
{
93+
stat_mock_.then_call(
94+
std::bind(
95+
&FileSystem::do_stat, this,
96+
std::placeholders::_1, std::placeholders::_2));
97+
#else
98+
// Deal with binary API quirks in GNU Linux.
99+
__xstat_mock_(
100+
MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, __xstat),
101+
MOCKING_UTILS_PATCH_PROXY(__xstat))
102+
{
103+
__xstat_mock_.then_call(
104+
std::bind(
105+
&FileSystem::do___xstat, this, std::placeholders::_1,
106+
std::placeholders::_2, std::placeholders::_3));
107+
#endif
108+
opendir_mock_.then_call(std::bind(&FileSystem::do_opendir, this, std::placeholders::_1));
109+
}
110+
111+
/// Force APIs that return file descriptors or handles to fail as if these had been exhausted.
112+
void exhaust_file_descriptors()
113+
{
114+
forced_errno_ = EMFILE;
115+
}
116+
117+
/// Get information from file in the mocked filesystem.
118+
/**
119+
* \param[in] path Path to the file whose information is to be retrieved.
120+
* If file is not found, one will be added.
121+
* \return mutable reference to file information.
122+
*/
123+
struct stat & file_info(const std::string & path)
124+
{
125+
return files_info_[path];
126+
}
127+
128+
private:
129+
DIR * do_opendir(const char *)
130+
{
131+
if (forced_errno_ != 0) {
132+
errno = forced_errno_;
133+
return NULL;
134+
}
135+
errno = ENOENT;
136+
return NULL;
137+
}
138+
MOCKING_UTILS_PATCH_TYPE(ID, opendir) opendir_mock_;
139+
140+
#ifndef _GNU_SOURCE
141+
int do_stat(const char * path, struct stat * info)
142+
{
143+
#else
144+
int do___xstat(int, const char * path, struct stat * info)
145+
{
146+
#endif
147+
if (files_info_.count(path) == 0) {
148+
errno = ENOENT;
149+
return -1;
150+
}
151+
*info = files_info_[path];
152+
return 0;
153+
}
154+
155+
#ifndef _GNU_SOURCE
156+
MOCKING_UTILS_PATCH_TYPE(ID, stat) stat_mock_;
157+
#else
158+
MOCKING_UTILS_PATCH_TYPE(ID, __xstat) __xstat_mock_;
159+
#endif
160+
161+
int forced_errno_{0};
162+
std::map<std::string, struct stat> files_info_;
163+
};
164+
165+
#else // !defined(_WIN32)
166+
167+
/// Helper class for patching the filesystem API.
168+
/**
169+
* \tparam ID Numerical identifier for this patches. Ought to be unique.
170+
*/
171+
template<size_t ID>
172+
class FileSystem
173+
{
174+
public:
175+
/// Construct mocked filesystem.
176+
/**
177+
* \param[in] scope Scope target string, using Mimick syntax.
178+
* \see mocking_utils::Patch documentation for further reference.
179+
*/
180+
explicit FileSystem(const std::string & scope)
181+
: find_first_file_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, FindFirstFileA),
182+
MOCKING_UTILS_PATCH_PROXY(FindFirstFileA)),
183+
_stat_mock_(MOCKING_UTILS_FILESYSTEM_PATCH_TARGET(scope, _stat),
184+
MOCKING_UTILS_PATCH_PROXY(_stat))
185+
{
186+
find_first_file_mock_.then_call(
187+
std::bind(
188+
&FileSystem::do_FindFirstFileA, this,
189+
std::placeholders::_1, std::placeholders::_2));
190+
_stat_mock_.then_call(
191+
std::bind(
192+
&FileSystem::do__stat, this,
193+
std::placeholders::_1, std::placeholders::_2));
194+
}
195+
196+
/// Force APIs that return file descriptors or handles to fail as if these had been exhausted.
197+
void exhaust_file_descriptors()
198+
{
199+
forced_errno_ = ERROR_NO_MORE_SEARCH_HANDLES;
200+
}
201+
202+
/// Get information from file in the mocked filesystem.
203+
/**
204+
* \param[in] path Path to the file whose information is to be retrieved.
205+
* If file is not found, one will be added.
206+
* \return mutable reference to file information.
207+
*/
208+
struct _stat & file_info(const std::string & path)
209+
{
210+
return files_info_[path];
211+
}
212+
213+
private:
214+
HANDLE do_FindFirstFileA(LPCSTR, LPWIN32_FIND_DATAA)
215+
{
216+
if (forced_errno_ != 0) {
217+
SetLastError(forced_errno_);
218+
return INVALID_HANDLE_VALUE;
219+
}
220+
SetLastError(ERROR_FILE_NOT_FOUND);
221+
return INVALID_HANDLE_VALUE;
222+
}
223+
224+
MOCKING_UTILS_PATCH_TYPE(ID, FindFirstFileA) find_first_file_mock_;
225+
226+
int do__stat(const char * path, struct _stat * info)
227+
{
228+
if (files_info_.count(path) == 0) {
229+
errno = ENOENT;
230+
return -1;
231+
}
232+
*info = files_info_[path];
233+
return 0;
234+
}
235+
236+
MOCKING_UTILS_PATCH_TYPE(ID, _stat) _stat_mock_;
237+
238+
int forced_errno_{0};
239+
std::map<std::string, struct _stat> files_info_;
240+
};
241+
242+
#endif // else !defined(_WIN32)
243+
244+
} // namespace filesystem
245+
246+
/// Patch filesystem API in a given `scope`.
247+
#define patch_filesystem(scope) filesystem::FileSystem<__COUNTER__>(scope)
248+
249+
} // namespace mocking_utils
250+
251+
#endif // MOCKING_UTILS__FILESYSTEM_HPP_

0 commit comments

Comments
 (0)