#ifndef LIBTORRENT_HELPERS_MOCK_FUNCTION_H #define LIBTORRENT_HELPERS_MOCK_FUNCTION_H #include #include #include #include #include #include #include #include #include "test/helpers/mock_compare.h" namespace torrent { extern int fd__accept(int socket, sockaddr *address, socklen_t *address_len); extern int fd__bind(int socket, const sockaddr *address, socklen_t address_len); extern int fd__close(int fildes); extern int fd__connect(int socket, const sockaddr *address, socklen_t address_len); extern int fd__fcntl_int(int fildes, int cmd, int arg); extern int fd__listen(int socket, int backlog); extern int fd__setsockopt_int(int socket, int level, int option_name, int option_value); extern int fd__socket(int domain, int type, int protocol); } enum mock_redirect_flags { mock_redirect_all = ~0, }; void mock_init(); void mock_cleanup(); void mock_redirect_defaults(mock_redirect_flags flags = mock_redirect_all); template struct mock_function_map { typedef std::tuple call_type; typedef std::vector call_list_type; typedef std::map func_map_type; typedef std::function function_type; typedef std::map redirect_map_type; static std::mutex mutex; static func_map_type functions; static redirect_map_type redirects; static bool cleanup(void* fn) { std::lock_guard lock(mutex); redirects.erase(fn); return functions.erase(fn) == 0; } static R ret_erase(void* fn) { auto itr = functions.find(fn); auto ret = std::get<0>(itr->second.front()); itr->second.erase(itr->second.begin()); if (itr->second.empty()) functions.erase(itr); return ret; } }; template std::mutex mock_function_map::mutex; template typename mock_function_map::func_map_type mock_function_map::functions; template typename mock_function_map::redirect_map_type mock_function_map::redirects; struct mock_void {}; template struct mock_function_type { typedef mock_function_map type; static int compare_expected(typename type::call_type lhs, Args... rhs) { return mock_compare_tuple(lhs, std::make_tuple(rhs...)); } static R ret_erase(void* fn) { return type::ret_erase(fn); } static bool has_redirect(void* fn) { std::lock_guard lock(type::mutex); return type::redirects.find(fn) != type::redirects.end(); } static R call_redirect(void* fn, Args... args) { std::lock_guard lock(type::mutex); return type::redirects.find(fn)->second(args...); } }; template struct mock_function_type { typedef mock_function_map type; static int compare_expected(typename type::call_type lhs, Args... rhs) { return mock_compare_tuple(lhs, std::make_tuple(rhs...)); } static void ret_erase(void* fn) { type::ret_erase(fn); } static bool has_redirect(void* fn) { std::lock_guard lock(type::mutex); return type::redirects.find(fn) != type::redirects.end(); } static void call_redirect(void* fn, Args... args) { std::lock_guard lock(type::mutex); type::redirects.find(fn)->second(args...); } }; template bool mock_cleanup_map(R fn[[gnu::unused]](Args...)) { return mock_function_type::type::cleanup(reinterpret_cast(fn)); } template void mock_expect(R fn(Args...), R ret, Args... args) { typedef mock_function_map mock_map; std::lock_guard lock(mock_map::mutex); mock_map::functions[reinterpret_cast(fn)].push_back(std::tuple(ret, args...)); } template void mock_expect(void fn(Args...), Args... args) { typedef mock_function_map mock_map; std::lock_guard lock(mock_map::mutex); mock_map::functions[reinterpret_cast(fn)].push_back(std::tuple(mock_void(), args...)); } template void mock_redirect(R fn(Args...), std::function func) { typedef mock_function_map mock_map; std::lock_guard lock(mock_map::mutex); mock_map::redirects[reinterpret_cast(fn)] = func; } template auto mock_call_direct(std::string name, R fn(Args...), Args... args) -> decltype(fn(args...)) { typedef mock_function_type mock_type; std::lock_guard lock(mock_type::type::mutex); auto itr = mock_type::type::functions.find(reinterpret_cast(fn)); CPPUNIT_ASSERT_MESSAGE(("mock_call expected function calls exhausted by '" + name + "'").c_str(), itr != mock_type::type::functions.end()); auto mismatch_arg = mock_type::compare_expected(itr->second.front(), args...); CPPUNIT_ASSERT_MESSAGE(("mock_call expected function call argument " + std::to_string(mismatch_arg) + " mismatch for '" + name + "'").c_str(), mismatch_arg == 0); return mock_type::ret_erase(reinterpret_cast(fn)); } template auto mock_call(std::string name, R fn(Args...), Args... args) -> decltype(fn(args...)) { typedef mock_function_type mock_type; if (mock_type::has_redirect(reinterpret_cast(fn))) return mock_type::call_redirect(reinterpret_cast(fn), args...); return mock_call_direct(name, fn, args...); } #endif