diff --git a/configure.ac b/configure.ac index 53842054..b128ad88 100644 --- a/configure.ac +++ b/configure.ac @@ -71,8 +71,6 @@ AC_DEFINE(HAVE_CONFIG_H, 1, true if config.h was included) AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION "/") + torrent::version()], Http user agent) AC_CHECK_FUNCS(posix_memalign) -TORRENT_CHECK_CACHELINE() -TORRENT_CHECK_POPCOUNT() CC_ATTRIBUTE_UNUSED( AC_DEFINE([__UNUSED], [__attribute__((unused))], [Wrapper around unused attribute]), @@ -85,10 +83,14 @@ LIBS="$PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $LIBCURL $LIBCURL_LIBS $DEPENDENCIE CFLAGS="$CFLAGS $PTHREAD_CFLAGS $LIBCURL_CPPFLAGS $LIBCURL_CFLAGS $DEPENDENCIES_CFLAGS $CURSES_CFLAGS" CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $LIBCURL_CPPFLAGS $LIBCURL_CFLAGS $DEPENDENCIES_CFLAGS $CURSES_CFLAGS" +TORRENT_CHECK_CACHELINE() +TORRENT_CHECK_POPCOUNT() + AC_CONFIG_FILES([ Makefile doc/Makefile src/Makefile test/Makefile ]) + AC_OUTPUT diff --git a/src/core/manager.cc b/src/core/manager.cc index 91536337..2f986648 100644 --- a/src/core/manager.cc +++ b/src/core/manager.cc @@ -51,15 +51,15 @@ Manager::push_log(const char* msg) { } Manager::Manager() : - m_hashingView(NULL), - m_log_important(torrent::log_open_log_buffer("important")), - m_log_complete(torrent::log_open_log_buffer("complete")) -{ - m_downloadStore = new DownloadStore(); - m_downloadList = new DownloadList(); - m_fileStatusCache = new FileStatusCache(); - m_httpQueue = new HttpQueue(); - m_httpStack = new CurlStack(); + m_hashingView(nullptr), + m_log_important(torrent::log_open_log_buffer("important")), + m_log_complete(torrent::log_open_log_buffer("complete")) { + + m_download_store = std::make_unique(); + m_download_list = std::make_unique(); + m_file_status_cache = std::make_unique(); + m_http_queue = std::make_unique(); + m_http_stack = std::make_unique(); torrent::Throttle* unthrottled = torrent::Throttle::create_throttle(); unthrottled->set_max_rate(0); @@ -68,19 +68,12 @@ Manager::Manager() : Manager::~Manager() { torrent::Throttle::destroy_throttle(m_throttles["NULL"].first); - delete m_downloadList; - - // TODO: Clean up logs objects. - - delete m_downloadStore; - delete m_httpQueue; - delete m_fileStatusCache; } void Manager::set_hashing_view(View* v) { - if (v == NULL || m_hashingView != NULL) - throw torrent::internal_error("Manager::set_hashing_view(...) received NULL or is already set."); + if (v == nullptr || m_hashingView != nullptr) + throw torrent::internal_error("Manager::set_hashing_view(...) received nullptr or is already set."); m_hashingView = v; m_hashingView->signal_changed().push_back(std::bind(&Manager::receive_hashing_changed, this)); @@ -89,12 +82,12 @@ Manager::set_hashing_view(View* v) { torrent::ThrottlePair Manager::get_throttle(const std::string& name) { ThrottleMap::const_iterator itr = m_throttles.find(name); - torrent::ThrottlePair throttles = (itr == m_throttles.end() ? torrent::ThrottlePair(NULL, NULL) : itr->second); + torrent::ThrottlePair throttles = (itr == m_throttles.end() ? torrent::ThrottlePair(nullptr, nullptr) : itr->second); - if (throttles.first == NULL) + if (throttles.first == nullptr) throttles.first = torrent::up_throttle_global(); - if (throttles.second == NULL) + if (throttles.second == nullptr) throttles.second = torrent::down_throttle_global(); return throttles; @@ -108,7 +101,7 @@ Manager::set_address_throttle(uint32_t begin, uint32_t end, torrent::ThrottlePai torrent::ThrottlePair Manager::get_address_throttle(const sockaddr* addr) { - return m_addressThrottles.get(rak::socket_address::cast_from(addr)->sa_inet()->address_h(), torrent::ThrottlePair(NULL, NULL)); + return m_addressThrottles.get(rak::socket_address::cast_from(addr)->sa_inet()->address_h(), torrent::ThrottlePair(nullptr, nullptr)); } int64_t @@ -121,7 +114,7 @@ Manager::retrieve_throttle_value(const torrent::Object::string_type& name, bool torrent::Throttle* throttle = up ? itr->second.first : itr->second.second; // check whether the actual up/down throttle exist (one of the pair can be missing) - if (throttle == NULL) + if (throttle == nullptr) return (int64_t)-1; int64_t throttle_max = (int64_t)throttle->max_rate(); @@ -143,36 +136,36 @@ Manager::retrieve_throttle_value(const torrent::Object::string_type& name, bool // Most of this should be possible to move out. void Manager::initialize_second() { - torrent::Http::slot_factory() = std::bind(&CurlStack::new_object, m_httpStack); - m_httpQueue->set_slot_factory(std::bind(&CurlStack::new_object, m_httpStack)); + torrent::Http::slot_factory() = std::bind(&CurlStack::new_object, m_http_stack.get()); + m_http_queue->set_slot_factory(std::bind(&CurlStack::new_object, m_http_stack.get())); CurlStack::global_init(); } void Manager::cleanup() { - m_httpStack->shutdown(); + m_http_stack->shutdown(); // Need to disconnect log signals? Not really since we won't receive // any more. - m_downloadList->clear(); + m_download_list->clear(); // When we implement asynchronous DNS lookups, we need to cancel them // here before the torrent::* objects are deleted. torrent::cleanup(); - delete m_httpStack; + m_http_stack.reset(); CurlStack::global_cleanup(); } void Manager::shutdown(bool force) { if (!force) - std::for_each(m_downloadList->begin(), m_downloadList->end(), [this](Download* d) { m_downloadList->pause_default(d); }); + std::for_each(m_download_list->begin(), m_download_list->end(), [this](Download* d) { m_download_list->pause_default(d); }); else - std::for_each(m_downloadList->begin(), m_downloadList->end(), [this](Download* d) { m_downloadList->close_quick(d); }); + std::for_each(m_download_list->begin(), m_download_list->end(), [this](Download* d) { m_download_list->close_quick(d); }); } void @@ -226,7 +219,7 @@ Manager::set_bind_address(const std::string& addr) { if ((err = rak::address_info::get_address_info(addr.c_str(), PF_INET, SOCK_STREAM, &ai)) != 0 && (err = rak::address_info::get_address_info(addr.c_str(), PF_INET6, SOCK_STREAM, &ai)) != 0) throw torrent::input_error("Could not set bind address: " + std::string(rak::address_info::strerror(err)) + "."); - + try { if (torrent::connection_manager()->listen_port() != 0) { @@ -238,7 +231,7 @@ Manager::set_bind_address(const std::string& addr) { torrent::connection_manager()->set_bind_address(ai->address()->c_sockaddr()); } - m_httpStack->set_bind_address(!ai->address()->is_address_any() ? ai->address()->address_str() : std::string()); + m_http_stack->set_bind_address(!ai->address()->is_address_any() ? ai->address()->address_str() : std::string()); rak::address_info::free_address_info(ai); @@ -477,13 +470,13 @@ void Manager::receive_hashing_changed() { bool foundHashing = std::find_if(m_hashingView->begin_visible(), m_hashingView->end_visible(), std::mem_fn(&Download::is_hash_checking)) != m_hashingView->end_visible(); - + // Try quick hashing all those with hashing == initial, set them to // something else when failed. for (View::iterator itr = m_hashingView->begin_visible(), last = m_hashingView->end_visible(); itr != last; ++itr) { if ((*itr)->is_hash_checked()) throw torrent::internal_error("core::Manager::receive_hashing_changed() (*itr)->is_hash_checked()."); - + if ((*itr)->is_hash_checking() || (*itr)->is_hash_failed()) continue; @@ -495,7 +488,7 @@ Manager::receive_hashing_changed() { continue; try { - m_downloadList->open_throw(*itr); + m_download_list->open_throw(*itr); // Since the bitfield is allocated on loading of resume load or // hash start, and unallocated on close, we know that if it it diff --git a/src/core/manager.h b/src/core/manager.h index f6567f3a..ebbf5373 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -4,7 +4,6 @@ #include #include #include - #include #include #include @@ -35,18 +34,15 @@ public: typedef DownloadList::iterator DListItr; typedef utils::FileStatusCache FileStatusCache; - // typedef std::function slot_ready; - // typedef std::function slot_void; - Manager(); ~Manager(); - DownloadList* download_list() { return m_downloadList; } - DownloadStore* download_store() { return m_downloadStore; } - FileStatusCache* file_status_cache() { return m_fileStatusCache; } + DownloadList* download_list() { return m_download_list.get(); } + DownloadStore* download_store() { return m_download_store.get(); } + FileStatusCache* file_status_cache() { return m_file_status_cache.get(); } - HttpQueue* http_queue() { return m_httpQueue; } - CurlStack* http_stack() { return m_httpStack; } + HttpQueue* http_queue() { return m_http_queue.get(); } + CurlStack* http_stack() { return m_http_stack.get(); } View* hashing_view() { return m_hashingView; } void set_hashing_view(View* v); @@ -109,11 +105,11 @@ private: void receive_http_failed(std::string msg); void receive_hashing_changed(); - DownloadList* m_downloadList; - DownloadStore* m_downloadStore; - FileStatusCache* m_fileStatusCache; - HttpQueue* m_httpQueue; - CurlStack* m_httpStack; + std::unique_ptr m_download_list; + std::unique_ptr m_download_store; + std::unique_ptr m_file_status_cache; + std::unique_ptr m_http_queue; + std::unique_ptr m_http_stack; View* m_hashingView; diff --git a/src/main.cc b/src/main.cc index 15706ade..5eaf243c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -159,7 +159,9 @@ main(int argc, char** argv) { srandom(random_seed); srand48(random_seed); - // Initialize logging: + torrent::Poll::slot_create_poll() = [](){ return create_poll(); }; + + torrent::initialize_main_thread(); torrent::log_initialize(); control = new Control; @@ -188,8 +190,6 @@ main(int argc, char** argv) { torrent::log_add_group_output(torrent::LOG_NOTICE, "important"); torrent::log_add_group_output(torrent::LOG_INFO, "complete"); - torrent::Poll::slot_create_poll() = [](){ return create_poll(); }; - torrent::initialize(); torrent::set_main_thread_slots(std::bind(&client_perform)); diff --git a/test/Makefile.am b/test/Makefile.am index 6f06cb4c..de710fd0 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -14,33 +14,40 @@ rtorrent_Test_Common = \ main.cc \ \ helpers/assert.h \ + helpers/mock_compare.h \ + helpers/mock_function.cc \ + helpers/mock_function.h \ helpers/progress_listener.cc \ helpers/progress_listener.h \ helpers/protectors.cc \ helpers/protectors.h \ + helpers/test_fixture.cc \ + helpers/test_fixture.h \ + helpers/test_main_thread.cc \ + helpers/test_main_thread.h \ + helpers/test_thread.cc \ + helpers/test_thread.h \ helpers/utils.h rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \ - rpc/command_test.cc \ - rpc/command_test.h \ - rpc/command_map_test.cc \ - rpc/command_map_test.h \ - rpc/jsonrpc_test.cc \ - rpc/jsonrpc_test.h \ - rpc/xmlrpc_test.cc \ - rpc/xmlrpc_test.h \ - rpc/command_slot_test.cc \ - rpc/command_slot_test.h \ - rpc/object_storage_test.cc \ - rpc/object_storage_test.h \ + rpc/test_command.cc \ + rpc/test_command.h \ + rpc/test_command_map.cc \ + rpc/test_command_map.h \ + rpc/test_jsonrpc.cc \ + rpc/test_jsonrpc.h \ + rpc/test_xmlrpc.cc \ + rpc/test_xmlrpc.h \ + rpc/test_command_slot.cc \ + rpc/test_command_slot.h \ + rpc/test_object_storage.cc \ + rpc/test_object_storage.h \ rpc/test_parse_options.cc \ - rpc/test_parse_options.h \ - src/command_dynamic_test.cc \ - src/command_dynamic_test.h + rpc/test_parse_options.h rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ - src/command_dynamic_test.cc \ - src/command_dynamic_test.h + src/test_command_dynamic.cc \ + src/test_command_dynamic.h rtorrent_Test_Rpc_CXXFLAGS = $(CPPUNIT_CFLAGS) rtorrent_Test_Rpc_LDFLAGS = $(CPPUNIT_LIBS) -ldl diff --git a/test/helpers/mock_compare.h b/test/helpers/mock_compare.h new file mode 100644 index 00000000..e84a6be8 --- /dev/null +++ b/test/helpers/mock_compare.h @@ -0,0 +1,96 @@ +#ifndef LIBTORRENT_HELPERS_MOCK_COMPARE_H +#define LIBTORRENT_HELPERS_MOCK_COMPARE_H + +#include +#include +#include +#include + +// Compare arguments to mock functions with what is expected. The lhs +// are the expected arguments, rhs are the ones called with. + +template +inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; } + +template +typename std::enable_if::type +mock_compare_tuple(const std::tuple& lhs, const std::tuple& rhs) { + return mock_compare_arg(std::get(lhs), std::get(rhs)) ? 0 : 1; +} + +template +typename std::enable_if<1 < I, int>::type +mock_compare_tuple(const std::tuple& lhs, const std::tuple& rhs) { + auto res = mock_compare_tuple(lhs, rhs); + + if (res != 0) + return res; + + return mock_compare_arg(std::get(lhs), std::get(rhs)) ? 0 : I; +} + +//template ::value, int>::type = 0> +template +struct mock_compare_map { + typedef std::map values_type; + + static T* begin_pointer() { return reinterpret_cast(0x1000); } + static T* end_pointer() { return reinterpret_cast(0x2000); } + + static bool is_key(const T* k) { + return k >= begin_pointer() && k < end_pointer(); + } + + static bool has_key(const T* k) { + return values.find(k) != values.end(); + } + + static bool has_value(const T* v) { + return std::find_if(values.begin(), values.end(), [v](typename values_type::value_type& kv) { return v == kv.second; }) != values.end(); + } + + static const T* get(const T* k) { + auto itr = values.find(k); + CPPUNIT_ASSERT_MESSAGE("mock_compare_map get failed, not inserted", itr != values.end()); + return itr->second; + } + + static values_type values; +}; + +template +typename mock_compare_map::values_type mock_compare_map::values; + +template +void mock_compare_add(T* v) { + mock_compare_map::add_value(v); +} + +// +// Specialize: +// + +template <> +inline bool mock_compare_arg(sockaddr* lhs, sockaddr* rhs) { + return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs); +} +template <> +inline bool mock_compare_arg(const sockaddr* lhs, const sockaddr* rhs) { + return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs); +} + +template <> +inline bool mock_compare_arg(torrent::Event* lhs, torrent::Event* rhs) { + if (mock_compare_map::is_key(lhs)) { + if (!mock_compare_map::has_value(rhs)) { + mock_compare_map::values[lhs] = rhs; + return true; + } + + return mock_compare_map::has_key(lhs) && mock_compare_map::get(lhs) == rhs; + } + + return lhs == rhs; +} + +#endif diff --git a/test/helpers/mock_function.cc b/test/helpers/mock_function.cc new file mode 100644 index 00000000..785b03b8 --- /dev/null +++ b/test/helpers/mock_function.cc @@ -0,0 +1,182 @@ +#include "config.h" + +#include "test/helpers/mock_function.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MOCK_CLEANUP_MAP(MOCK_FUNC) \ + CPPUNIT_ASSERT_MESSAGE("expected mock function calls not completed for '" #MOCK_FUNC "'", mock_cleanup_map(&MOCK_FUNC) || ignore_assert); +#define MOCK_LOG(log_fmt, ...) \ + lt_log_print(torrent::LOG_MOCK_CALLS, "%s: " log_fmt, __func__, __VA_ARGS__); + +void +mock_clear(bool ignore_assert) { + MOCK_CLEANUP_MAP(torrent::fd__accept); + MOCK_CLEANUP_MAP(torrent::fd__bind); + MOCK_CLEANUP_MAP(torrent::fd__close); + MOCK_CLEANUP_MAP(torrent::fd__connect); + MOCK_CLEANUP_MAP(torrent::fd__fcntl_int); + MOCK_CLEANUP_MAP(torrent::fd__listen); + MOCK_CLEANUP_MAP(torrent::fd__setsockopt_int); + MOCK_CLEANUP_MAP(torrent::fd__socket); + + MOCK_CLEANUP_MAP(torrent::poll_event_open); + MOCK_CLEANUP_MAP(torrent::poll_event_close); + MOCK_CLEANUP_MAP(torrent::poll_event_closed); + MOCK_CLEANUP_MAP(torrent::poll_event_insert_read); + MOCK_CLEANUP_MAP(torrent::poll_event_insert_write); + MOCK_CLEANUP_MAP(torrent::poll_event_insert_error); + MOCK_CLEANUP_MAP(torrent::poll_event_remove_read); + MOCK_CLEANUP_MAP(torrent::poll_event_remove_write); + MOCK_CLEANUP_MAP(torrent::poll_event_remove_error); + + MOCK_CLEANUP_MAP(torrent::random_uniform_uint16); + MOCK_CLEANUP_MAP(torrent::random_uniform_uint32); + + mock_compare_map::values.clear(); +}; + +void +mock_init() { + log_add_group_output(torrent::LOG_MOCK_CALLS, "test_output"); + mock_clear(true); +} + +void +mock_cleanup() { + mock_clear(false); +} + +void +mock_redirect_defaults([[maybe_unused]] mock_redirect_flags flags) { + mock_redirect(torrent::fd__close, std::function([](int fildes) { return ::close(fildes); })); + mock_redirect(torrent::fd__fcntl_int, std::function([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); })); + mock_redirect(torrent::fd__setsockopt_int, std::function([](int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); })); + mock_redirect(torrent::fd__socket, std::function([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); })); +} + +namespace torrent { + +// +// Mock functions for 'torrent/net/fd.h': +// + +int fd__accept(int socket, sockaddr *address, socklen_t *address_len) { + MOCK_LOG("entry socket:%i address:%s address_len:%u", + socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)(*address_len)); + auto ret = mock_call(__func__, &torrent::fd__accept, socket, address, address_len); + MOCK_LOG("exit socket:%i address:%s address_len:%u", + socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)(*address_len)); + return ret; +} + +int fd__bind(int socket, const sockaddr *address, socklen_t address_len) { + MOCK_LOG("socket:%i address:%s address_len:%u", + socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)address_len); + return mock_call(__func__, &torrent::fd__bind, socket, address, address_len); +} + +int fd__close(int fildes) { + MOCK_LOG("filedes:%i", fildes); + return mock_call(__func__, &torrent::fd__close, fildes); +} + +int fd__connect(int socket, const sockaddr *address, socklen_t address_len) { + MOCK_LOG("socket:%i address:%s address_len:%u", + socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)address_len); + return mock_call(__func__, &torrent::fd__connect, socket, address, address_len); +} + +int fd__fcntl_int(int fildes, int cmd, int arg) { + MOCK_LOG("filedes:%i cmd:%i arg:%i", fildes, cmd, arg); + return mock_call(__func__, &torrent::fd__fcntl_int, fildes, cmd, arg); +} + +int fd__listen(int socket, int backlog) { + MOCK_LOG("socket:%i backlog:%i", socket, backlog); + return mock_call(__func__, &torrent::fd__listen, socket, backlog); +} + +int fd__setsockopt_int(int socket, int level, int option_name, int option_value) { + MOCK_LOG("socket:%i level:%i option_name:%i option_value:%i", + socket, level, option_name, option_value); + return mock_call(__func__, &torrent::fd__setsockopt_int, socket, level, option_name, option_value); +} + +int fd__socket(int domain, int type, int protocol) { + MOCK_LOG("domain:%i type:%i protocol:%i", domain, type, protocol); + return mock_call(__func__, &torrent::fd__socket, domain, type, protocol); +} + +// +// Mock functions for 'torrent/event.h': +// + +void poll_event_open(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_open, event); +} + +void poll_event_close(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_close, event); +} + +void poll_event_closed(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_closed, event); +} + +void poll_event_insert_read(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_insert_read, event); +} + +void poll_event_insert_write(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_insert_write, event); +} + +void poll_event_insert_error(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_insert_error, event); +} + +void poll_event_remove_read(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_remove_read, event); +} + +void poll_event_remove_write(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_remove_write, event); +} + +void poll_event_remove_error(Event* event) { + MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); + return mock_call(__func__, &torrent::poll_event_remove_error, event); +} + +// +// Mock functions for 'torrent/utils/random.h': +// + +uint16_t random_uniform_uint16(uint16_t min, uint16_t max) { + MOCK_LOG("min:%" PRIu16 " max:%" PRIu16, min, max); + return mock_call(__func__, &torrent::random_uniform_uint16, min, max); +} + +uint32_t random_uniform_uint32(uint32_t min, uint32_t max) { + MOCK_LOG("min:%" PRIu32 " max:%" PRIu32, min, max); + return mock_call(__func__, &torrent::random_uniform_uint32, min, max); +} + +} diff --git a/test/helpers/mock_function.h b/test/helpers/mock_function.h new file mode 100644 index 00000000..830a63cb --- /dev/null +++ b/test/helpers/mock_function.h @@ -0,0 +1,176 @@ +#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 diff --git a/test/helpers/test_fixture.cc b/test/helpers/test_fixture.cc new file mode 100644 index 00000000..3d766ea0 --- /dev/null +++ b/test/helpers/test_fixture.cc @@ -0,0 +1,20 @@ +#include "config.h" + +#include "test_fixture.h" + +#include "torrent/utils/log.h" + +#include + +void +test_fixture::setUp() { + mock_init(); + + log_add_group_output(torrent::LOG_CONNECTION_BIND, "test_output"); + log_add_group_output(torrent::LOG_CONNECTION_FD, "test_output"); +} + +void +test_fixture::tearDown() { + mock_cleanup(); +} diff --git a/test/helpers/test_fixture.h b/test/helpers/test_fixture.h new file mode 100644 index 00000000..3511b19c --- /dev/null +++ b/test/helpers/test_fixture.h @@ -0,0 +1,14 @@ +#ifndef LIBTORRENT_HELPER_TEST_FIXTURE_H +#define LIBTORRENT_HELPER_TEST_FIXTURE_H + +#include + +#include "test/helpers/mock_function.h" + +class test_fixture : public CppUnit::TestFixture { +public: + void setUp(); + void tearDown(); +}; + +#endif diff --git a/test/helpers/test_main_thread.cc b/test/helpers/test_main_thread.cc new file mode 100644 index 00000000..7163ef15 --- /dev/null +++ b/test/helpers/test_main_thread.cc @@ -0,0 +1,52 @@ +#include "config.h" + +#include "test_main_thread.h" + +#include "globals.h" +#include "test/helpers/mock_function.h" +#include "torrent/exceptions.h" +#include "torrent/poll.h" +#include "torrent/net/resolver.h" +#include "torrent/utils/scheduler.h" + +std::unique_ptr +TestMainThread::create() { + if (torrent::Poll::slot_create_poll() == nullptr) + set_create_poll(); + + // Needs to be called before Thread is created. + mock_redirect_defaults(); + + auto thread = new TestMainThread(); + return std::unique_ptr(thread); +} + +TestMainThread::TestMainThread() {} + +TestMainThread::~TestMainThread() { + m_self = nullptr; +} + +void +TestMainThread::init_thread() { + if (!torrent::Poll::slot_create_poll()) + throw torrent::internal_error("ThreadMain::init_thread(): Poll::slot_create_poll() not valid."); + + m_poll = std::unique_ptr(torrent::Poll::slot_create_poll()()); + m_resolver = std::make_unique(); + m_state = STATE_INITIALIZED; + + //m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_MAIN - INSTRUMENTATION_POLLING_DO_POLL; + + init_thread_local(); +} + +void +TestMainThread::call_events() { + process_callbacks(); +} + +std::chrono::microseconds +TestMainThread::next_timeout() { + return std::chrono::microseconds(10min); +} diff --git a/test/helpers/test_main_thread.h b/test/helpers/test_main_thread.h new file mode 100644 index 00000000..512a58e1 --- /dev/null +++ b/test/helpers/test_main_thread.h @@ -0,0 +1,30 @@ +#ifndef TEST_HELPERS_TEST_MAIN_THREAD_H +#define TEST_HELPERS_TEST_MAIN_THREAD_H + +#include +#include +#include + +#include "test/helpers/test_thread.h" + +class TestMainThread : public torrent::utils::Thread { +public: + static std::unique_ptr create(); + + ~TestMainThread() override; + + const char* name() const override { return "rtorrent test main"; } + + void init_thread() override; + + void test_set_cached_time(std::chrono::microseconds t) { set_cached_time(365 * 24h + t); } + void test_process_events_without_cached_time() { process_events_without_cached_time(); } + +private: + TestMainThread(); + + void call_events() override; + std::chrono::microseconds next_timeout() override; +}; + +#endif // TEST_HELPERS_TEST_MAIN_THREAD_H diff --git a/test/helpers/test_thread.cc b/test/helpers/test_thread.cc new file mode 100644 index 00000000..709310eb --- /dev/null +++ b/test/helpers/test_thread.cc @@ -0,0 +1,102 @@ +#include "config.h" + +#include "test_thread.h" + +#include +#include +#include +#include + +#include "test/helpers/mock_function.h" + +const int test_thread::test_flag_pre_stop; +const int test_thread::test_flag_long_timeout; + +const int test_thread::test_flag_do_work; +const int test_thread::test_flag_pre_poke; +const int test_thread::test_flag_post_poke; + +// TODO: Remove PollSelect. + +torrent::Poll* +create_poll() { + torrent::Poll* poll = torrent::Poll::create(256); + + if (poll == nullptr) + throw torrent::internal_error("Unable to create poll object"); + + return poll; +} + +void +set_create_poll() { + torrent::Poll::slot_create_poll() = []() { + return create_poll(); + }; +} + +std::unique_ptr +test_thread::create() { + // Needs to be called before Thread is created. + mock_redirect_defaults(); + + auto thread = new test_thread(); + return std::unique_ptr(thread); +} + +test_thread::test_thread() : + m_test_state(TEST_NONE), + m_test_flags(0) { +} + +test_thread::~test_thread() { + if (is_active()) + stop_thread_wait(); + + m_self = nullptr; +} + +void +test_thread::init_thread() { + m_state = STATE_INITIALIZED; + m_test_state = TEST_PRE_START; + + m_poll = std::unique_ptr(create_poll()); +} + +void +test_thread::call_events() { + m_loop_count++; + + if ((m_test_flags & test_flag_pre_stop) && m_test_state == TEST_PRE_START && m_state == STATE_ACTIVE) + m_test_state = TEST_PRE_STOP; + + if ((m_flags & flag_do_shutdown)) { + if ((m_flags & flag_did_shutdown)) + throw torrent::internal_error("Already trigged shutdown."); + + m_flags |= flag_did_shutdown; + throw torrent::shutdown_exception(); + } + + if ((m_test_flags & test_flag_pre_poke)) { + } + + if ((m_test_flags & test_flag_do_work)) { + usleep(10 * 1000); // TODO: Don't just sleep, as that give up core. + m_test_flags &= ~test_flag_do_work; + } + + if ((m_test_flags & test_flag_post_poke)) { + } + + process_callbacks(); +} + +std::chrono::microseconds +test_thread::next_timeout() { + if ((m_test_flags & test_flag_long_timeout)) + return std::chrono::microseconds(10s); + else + return std::chrono::microseconds(100ms); +} diff --git a/test/helpers/test_thread.h b/test/helpers/test_thread.h new file mode 100644 index 00000000..37fdbe80 --- /dev/null +++ b/test/helpers/test_thread.h @@ -0,0 +1,73 @@ +#ifndef TEST_HELPERS_TEST_THREAD_H +#define TEST_HELPERS_TEST_THREAD_H + +#include +#include + +#include "torrent/common.h" +#include "torrent/utils/thread.h" + +class test_thread : public torrent::utils::Thread { +public: + enum test_state { + TEST_NONE, + TEST_PRE_START, + TEST_PRE_STOP, + TEST_STOP + }; + + static const int test_flag_pre_stop = 0x1; + static const int test_flag_long_timeout = 0x2; + + static const int test_flag_do_work = 0x100; + static const int test_flag_pre_poke = 0x200; + static const int test_flag_post_poke = 0x400; + + static std::unique_ptr create(); + + ~test_thread() override; + + int test_state() const { return m_test_state; } + + bool is_state(int state) const { return m_state == state; } + bool is_test_state(int state) const { return m_test_state == state; } + bool is_test_flags(int flags) const { return (m_test_flags & flags) == flags; } + bool is_not_test_flags(int flags) const { return !(m_test_flags & flags); } + + // Loop count increments twice each loop. + int loop_count() const { return m_loop_count; } + + const char* name() const override { return "test_thread"; } + + void init_thread() override; + + void set_pre_stop() { m_test_flags |= test_flag_pre_stop; } + void set_test_flag(int flags) { m_test_flags |= flags; } + +private: + test_thread(); + + void call_events() override; + std::chrono::microseconds next_timeout() override; + + std::atomic_int m_test_state; + std::atomic_int m_test_flags; + std::atomic_int m_loop_count{0}; +}; + +void set_create_poll(); + +// TODO: Need better cleanup here. +// TODO: Replace these with class that holds the threads and cleans them up on destruction. + +#define SETUP_THREAD_DISK() \ + auto thread_test = test_thread::create(); \ + thread_test->init_thread(); \ + torrent::ThreadDisk::create_thread(); \ + torrent::thread_disk()->init_thread(); \ + torrent::thread_disk()->start_thread(); + +#define CLEANUP_THREAD_DISK() \ + torrent::ThreadDisk::destroy_thread(); + +#endif diff --git a/test/rpc/command_test.h b/test/rpc/command_test.h deleted file mode 100644 index f9ca6725..00000000 --- a/test/rpc/command_test.h +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include "rpc/command.h" - -class CommandTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(CommandTest); - CPPUNIT_TEST(test_stack); - CPPUNIT_TEST(test_stack_double); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() { } - void tearDown() {} - - void test_stack(); - void test_stack_double(); - -private: -}; diff --git a/test/rpc/jsonrpc_test.h b/test/rpc/jsonrpc_test.h deleted file mode 100644 index ef9db02d..00000000 --- a/test/rpc/jsonrpc_test.h +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include "rpc/command_map.h" -#include "rpc/jsonrpc.h" - -class JsonrpcTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(JsonrpcTest); - CPPUNIT_TEST(test_basics); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp(); - void tearDown() {} - - void test_basics(); - -private: - rpc::JsonRpc m_jsonrpc; -}; diff --git a/test/rpc/command_test.cc b/test/rpc/test_command.cc similarity index 93% rename from test/rpc/command_test.cc rename to test/rpc/test_command.cc index e34867d2..969b68f7 100644 --- a/test/rpc/command_test.cc +++ b/test/rpc/test_command.cc @@ -1,8 +1,10 @@ #include "config.h" -#include "command_test.h" +#include "test/rpc/test_command.h" -CPPUNIT_TEST_SUITE_REGISTRATION(CommandTest); +#include "rpc/command.h" + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommand); bool command_stack_all_empty() { @@ -11,7 +13,7 @@ command_stack_all_empty() { } void -CommandTest::test_stack() { +TestCommand::test_stack() { torrent::Object::list_type args; rpc::command_base::stack_type stack; torrent::Object* last_stack; @@ -50,7 +52,7 @@ CommandTest::test_stack() { } void -CommandTest::test_stack_double() { +TestCommand::test_stack_double() { torrent::Object::list_type args; rpc::command_base::stack_type stack_first; rpc::command_base::stack_type stack_second; diff --git a/test/rpc/test_command.h b/test/rpc/test_command.h new file mode 100644 index 00000000..68ce4dcd --- /dev/null +++ b/test/rpc/test_command.h @@ -0,0 +1,14 @@ +#include "test/helpers/test_fixture.h" + +class TestCommand : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommand); + + CPPUNIT_TEST(test_stack); + CPPUNIT_TEST(test_stack_double); + + CPPUNIT_TEST_SUITE_END(); + +public: + void test_stack(); + void test_stack_double(); +}; diff --git a/test/rpc/command_map_test.cc b/test/rpc/test_command_map.cc similarity index 60% rename from test/rpc/command_map_test.cc rename to test/rpc/test_command_map.cc index 5388048e..d6b0c6b3 100644 --- a/test/rpc/command_map_test.cc +++ b/test/rpc/test_command_map.cc @@ -1,11 +1,11 @@ #include "config.h" +#include "test/rpc/test_command_map.h" + #include "command_helpers.h" #include "rpc/command_map.h" -#include "command_map_test.h" - -CPPUNIT_TEST_SUITE_REGISTRATION(CommandMapTest); +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandMap); #undef CMD2_A_FUNCTION @@ -13,13 +13,13 @@ CPPUNIT_TEST_SUITE_REGISTRATION(CommandMapTest); m_map.insert_slot::type>(key, slot, &rpc::function, \ rpc::CommandMap::flag_dont_delete | rpc::CommandMap::flag_public_rpc, NULL, NULL); -torrent::Object cmd_test_map_a(rpc::target_type t, const torrent::Object& obj) { return obj; } -torrent::Object cmd_test_map_b(rpc::target_type t, const torrent::Object& obj, uint64_t c) { return torrent::Object(c); } +torrent::Object cmd_test_map_a([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; } +torrent::Object cmd_test_map_b([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj, uint64_t c) { return torrent::Object(c); } -torrent::Object cmd_test_any_string(__UNUSED rpc::target_type target, const std::string& rawArgs) { return (int64_t)3; } +torrent::Object cmd_test_any_string([[maybe_unused]] rpc::target_type target, [[maybe_unused]] const std::string& rawArgs) { return (int64_t)3; } void -CommandMapTest::test_basics() { +TestCommandMap::test_basics() { CMD2_ANY("test_a", &cmd_test_map_a); CMD2_ANY("test_b", std::bind(&cmd_test_map_b, std::placeholders::_1, std::placeholders::_2, (uint64_t)2)); CMD2_ANY_STRING("any_string", &cmd_test_any_string); diff --git a/test/rpc/command_map_test.h b/test/rpc/test_command_map.h similarity index 66% rename from test/rpc/command_map_test.h rename to test/rpc/test_command_map.h index 34ab2943..9002278e 100644 --- a/test/rpc/command_map_test.h +++ b/test/rpc/test_command_map.h @@ -1,17 +1,18 @@ -#include +#include "test/helpers/test_fixture.h" #include "rpc/command_map.h" -class CommandMapTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(CommandMapTest); +class TestCommandMap : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommandMap); + CPPUNIT_TEST(test_basics); + CPPUNIT_TEST_SUITE_END(); public: static const int cmd_size = 256; void setUp() { m_commandItr = m_commands; } - void tearDown() {} void test_basics(); diff --git a/test/rpc/command_slot_test.cc b/test/rpc/test_command_slot.cc similarity index 53% rename from test/rpc/command_slot_test.cc rename to test/rpc/test_command_slot.cc index 012cf4f2..ee0122e4 100644 --- a/test/rpc/command_slot_test.cc +++ b/test/rpc/test_command_slot.cc @@ -1,26 +1,27 @@ #include "config.h" +#include "test/rpc/test_command_slot.h" + #include #include + #include "rpc/command_map.h" -#include "command_slot_test.h" +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandSlot); -CPPUNIT_TEST_SUITE_REGISTRATION(CommandSlotTest); +torrent::Object cmd_test_a([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; } +torrent::Object cmd_test_b([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj, uint64_t c) { return torrent::Object(c); } -torrent::Object cmd_test_a(rpc::target_type t, const torrent::Object& obj) { return obj; } -torrent::Object cmd_test_b(rpc::target_type t, const torrent::Object& obj, uint64_t c) { return torrent::Object(c); } +torrent::Object cmd_test_list([[maybe_unused]] rpc::target_type t, const torrent::Object::list_type& obj) { return torrent::Object(obj.front()); } -torrent::Object cmd_test_list(rpc::target_type t, const torrent::Object::list_type& obj) { return torrent::Object(obj.front()); } - -void cmd_test_convert_void(rpc::target_type t, const torrent::Object& obj) {} -int32_t cmd_test_convert_int32_t(rpc::target_type t, const torrent::Object& obj) { return 9; } -int64_t cmd_test_convert_int64_t(rpc::target_type t, const torrent::Object& obj) { return 10; } -std::string cmd_test_convert_string(rpc::target_type t, const torrent::Object& obj) { return "test_1"; } -const std::string& cmd_test_convert_const_string(rpc::target_type t, const torrent::Object& obj) { static const std::string ret = "test_2"; return ret; } +void cmd_test_convert_void([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) {} +int32_t cmd_test_convert_int32_t([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) { return 9; } +int64_t cmd_test_convert_int64_t([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) { return 10; } +std::string cmd_test_convert_string([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) { return "test_1"; } +const std::string& cmd_test_convert_const_string([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) { static const std::string ret = "test_2"; return ret; } void -CommandSlotTest::test_basics() { +TestCommandSlot::test_basics() { // rpc::command_base test_any; // test_any.set_function(&cmd_test_a); // CPPUNIT_ASSERT(rpc::command_base_call_any(&test_any, rpc::make_target(), (int64_t)1).as_value() == 1); @@ -33,13 +34,13 @@ CommandSlotTest::test_basics() { } void -CommandSlotTest::test_type_validity() { +TestCommandSlot::test_type_validity() { // CPPUNIT_ASSERT((rpc::command_base_is_type::value)); // CPPUNIT_ASSERT((rpc::command_base_is_type::value)); } void -CommandSlotTest::test_convert_return() { +TestCommandSlot::test_convert_return() { // rpc::command_base test_any; // test_any.set_function(&cmd_test_convert_string); diff --git a/test/rpc/command_slot_test.h b/test/rpc/test_command_slot.h similarity index 52% rename from test/rpc/command_slot_test.h rename to test/rpc/test_command_slot.h index d6910c99..febafde1 100644 --- a/test/rpc/command_slot_test.h +++ b/test/rpc/test_command_slot.h @@ -1,18 +1,15 @@ -#include +#include "test/helpers/test_fixture.h" -#include "rpc/command.h" +class TestCommandSlot : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommandSlot); -class CommandSlotTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(CommandSlotTest); CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_type_validity); CPPUNIT_TEST(test_convert_return); + CPPUNIT_TEST_SUITE_END(); public: - void setUp() {} - void tearDown() {} - void test_basics(); void test_type_validity(); void test_convert_return(); diff --git a/test/rpc/jsonrpc_test.cc b/test/rpc/test_jsonrpc.cc similarity index 95% rename from test/rpc/jsonrpc_test.cc rename to test/rpc/test_jsonrpc.cc index d7529ff0..c8940e09 100644 --- a/test/rpc/jsonrpc_test.cc +++ b/test/rpc/test_jsonrpc.cc @@ -1,14 +1,15 @@ #include "config.h" -#include -#include "command_helpers.h" -#include "rpc/command_map.h" +#include "test/rpc/test_jsonrpc.h" + +#include #include "control.h" #include "globals.h" -#include "jsonrpc_test.h" +#include "command_helpers.h" +#include "rpc/command_map.h" -CPPUNIT_TEST_SUITE_REGISTRATION(JsonrpcTest); +CPPUNIT_TEST_SUITE_REGISTRATION(TestJsonrpc); torrent::Object jsonrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; } @@ -117,19 +118,27 @@ std::vector> basic_jsonrpc_req }; void -JsonrpcTest::setUp() { +TestJsonrpc::setUp() { + m_test_main_thread = TestMainThread::create(); + m_test_main_thread->init_thread(); + m_jsonrpc = rpc::JsonRpc(); m_jsonrpc.initialize(); setlocale(LC_ALL, ""); - // cachedTime = rak::timer::current(); - control = new Control; + control = new Control; + if (rpc::commands.find("jsonrpc_reflect") == rpc::commands.end()) { CMD2_ANY("jsonrpc_reflect", &jsonrpc_cmd_test_reflect); } } void -JsonrpcTest::test_basics() { +TestJsonrpc::tearDown() { + m_test_main_thread.reset(); +} + +void +TestJsonrpc::test_basics() { for (auto& test : basic_jsonrpc_requests) { std::string output; m_jsonrpc.process(std::get<1>(test).c_str(), std::get<1>(test).size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; }); diff --git a/test/rpc/test_jsonrpc.h b/test/rpc/test_jsonrpc.h new file mode 100644 index 00000000..132f5ef5 --- /dev/null +++ b/test/rpc/test_jsonrpc.h @@ -0,0 +1,24 @@ +#include "test/helpers/test_fixture.h" +#include "test/helpers/test_main_thread.h" + +#include "rpc/command_map.h" +#include "rpc/jsonrpc.h" + +class TestJsonrpc : public test_fixture { + CPPUNIT_TEST_SUITE(TestJsonrpc); + + CPPUNIT_TEST(test_basics); + + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void test_basics(); + +private: + std::unique_ptr m_test_main_thread; + + rpc::JsonRpc m_jsonrpc; +}; diff --git a/test/rpc/object_storage_test.cc b/test/rpc/test_object_storage.cc similarity index 91% rename from test/rpc/object_storage_test.cc rename to test/rpc/test_object_storage.cc index c58666f0..aef2e143 100644 --- a/test/rpc/object_storage_test.cc +++ b/test/rpc/test_object_storage.cc @@ -1,16 +1,17 @@ #include "config.h" -#include "object_storage_test.h" +#include "test/rpc/test_object_storage.h" + #include "helpers/assert.h" -CPPUNIT_TEST_SUITE_REGISTRATION(ObjectStorageTest); +CPPUNIT_TEST_SUITE_REGISTRATION(TestObjectStorage); void -ObjectStorageTest::test_basics() { +TestObjectStorage::test_basics() { rpc::object_storage::iterator itr; CPPUNIT_ASSERT(m_storage.empty()); - + itr = m_storage.insert("test_1", torrent::Object("a"), rpc::object_storage::flag_string_type); CPPUNIT_ASSERT(itr != m_storage.end()); @@ -32,7 +33,7 @@ ObjectStorageTest::test_basics() { } void -ObjectStorageTest::test_conversions() { +TestObjectStorage::test_conversions() { CPPUNIT_ASSERT(m_storage.insert("test_1", torrent::Object("a"), rpc::object_storage::flag_string_type) != m_storage.end()); CPPUNIT_ASSERT(m_storage.insert_str(std::string("test_2"), torrent::Object("a"), rpc::object_storage::flag_string_type) != m_storage.end()); @@ -44,7 +45,7 @@ ObjectStorageTest::test_conversions() { } void -ObjectStorageTest::test_validate_keys() { +TestObjectStorage::test_validate_keys() { torrent::raw_string raw_string_4("test_4\0foo", 10); ASSERT_CATCH_INPUT_ERROR( { m_storage.insert(raw_string_4, torrent::Object("a"), rpc::object_storage::flag_string_type); } ); @@ -55,7 +56,7 @@ ObjectStorageTest::test_validate_keys() { // Test for various conversions of fixed_key_type. void -ObjectStorageTest::test_access() { +TestObjectStorage::test_access() { m_storage.insert("string_1", torrent::Object("gen_a"), rpc::object_storage::flag_string_type); m_storage.insert("value_1", int64_t(1), rpc::object_storage::flag_value_type); @@ -68,7 +69,7 @@ ObjectStorageTest::test_access() { // CPPUNIT_ASSERT(m_storage.set_c_str_value("value_1", "123").as_value() == 123); // CPPUNIT_ASSERT(m_storage.set_c_str_value("value_1", torrent::raw_string::from_c_str("321")).as_value() == 321); // CPPUNIT_ASSERT(m_storage.set_c_str_value("value_1", torrent::raw_bencode::from_c_str("i567e")).as_value() == 567); - + // ASSERT_CATCH_INPUT_ERROR( { m_storage.set_c_str_value("value_1", "e123"); } ); // Test string from raw and normal, list, etc. diff --git a/test/rpc/object_storage_test.h b/test/rpc/test_object_storage.h similarity index 65% rename from test/rpc/object_storage_test.h rename to test/rpc/test_object_storage.h index 629f95c3..1a147649 100644 --- a/test/rpc/object_storage_test.h +++ b/test/rpc/test_object_storage.h @@ -1,19 +1,18 @@ -#include +#include "test/helpers/test_fixture.h" #include "rpc/object_storage.h" -class ObjectStorageTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(ObjectStorageTest); +class TestObjectStorage : public test_fixture { + CPPUNIT_TEST_SUITE(TestObjectStorage); + CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_conversions); CPPUNIT_TEST(test_validate_keys); CPPUNIT_TEST(test_access); + CPPUNIT_TEST_SUITE_END(); public: - void setUp() { } - void tearDown() {} - void test_basics(); void test_conversions(); diff --git a/test/rpc/test_parse_options.cc b/test/rpc/test_parse_options.cc index 6f7ce289..74fc7ea3 100644 --- a/test/rpc/test_parse_options.cc +++ b/test/rpc/test_parse_options.cc @@ -1,8 +1,6 @@ #include "config.h" -#include "test_parse_options.h" - -#include "helpers/assert.h" +#include "test/rpc/test_parse_options.h" #include #include @@ -10,6 +8,7 @@ #include #include +#include "test/helpers/assert.h" #include "rpc/parse_options.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestParseOptions); @@ -37,7 +36,7 @@ flag_to_int(const std::string& flag) { for (auto f : flag_list) if (f.first == flag) return f.second; - + throw torrent::input_error("unknown flag"); } @@ -86,7 +85,7 @@ TestParseOptions::test_flag_error() { FLAG_ASSERT_ERROR(""); FLAG_ASSERT_ERROR("foo|bar"); FLAG_ASSERT_ERROR("foo|bar|baz"); - + FLAG_ASSERT_ERROR("foo |bar"); FLAG_ASSERT_ERROR("foo | bar| baz"); } diff --git a/test/rpc/test_parse_options.h b/test/rpc/test_parse_options.h index e0f14562..ac25948b 100644 --- a/test/rpc/test_parse_options.h +++ b/test/rpc/test_parse_options.h @@ -1,7 +1,8 @@ -#include +#include "test/helpers/test_fixture.h" -class TestParseOptions : public CppUnit::TestFixture { +class TestParseOptions : public test_fixture { CPPUNIT_TEST_SUITE(TestParseOptions); + CPPUNIT_TEST(test_flag_basic); CPPUNIT_TEST(test_flag_error); @@ -14,12 +15,10 @@ class TestParseOptions : public CppUnit::TestFixture { CPPUNIT_TEST(test_flag_libtorrent); CPPUNIT_TEST(test_flags_libtorrent); + CPPUNIT_TEST_SUITE_END(); public: - void setUp() { } - void tearDown() {} - void test_flag_basic(); void test_flag_error(); diff --git a/test/rpc/xmlrpc_test.cc b/test/rpc/test_xmlrpc.cc similarity index 95% rename from test/rpc/xmlrpc_test.cc rename to test/rpc/test_xmlrpc.cc index da35c121..ef37d8a9 100644 --- a/test/rpc/xmlrpc_test.cc +++ b/test/rpc/test_xmlrpc.cc @@ -1,14 +1,15 @@ #include "config.h" + +#include "test/rpc/test_xmlrpc.h" + #include +#include "control.h" +#include "globals.h" #include "command_helpers.h" #include "rpc/command_map.h" -#include "xmlrpc_test.h" -#include "control.h" -#include "globals.h" - -CPPUNIT_TEST_SUITE_REGISTRATION(XmlrpcTest); +CPPUNIT_TEST_SUITE_REGISTRATION(TestXmlrpc); torrent::Object xmlrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; } @@ -99,19 +100,27 @@ std::vector> basic_requests = }; void -XmlrpcTest::setUp() { +TestXmlrpc::setUp() { + m_test_main_thread = TestMainThread::create(); + m_test_main_thread->init_thread(); + m_xmlrpc = rpc::XmlRpc(); m_xmlrpc.initialize(); setlocale(LC_ALL, ""); - // cachedTime = rak::timer::current(); control = new Control; + if (rpc::commands.find("xmlrpc_reflect") == rpc::commands.end()) { CMD2_ANY("xmlrpc_reflect", &xmlrpc_cmd_test_reflect); } } void -XmlrpcTest::test_basics() { +TestXmlrpc::tearDown() { + m_test_main_thread.reset(); +} + +void +TestXmlrpc::test_basics() { for (auto& test : basic_requests) { std::string output; m_xmlrpc.process(std::get<1>(test).c_str(), std::get<1>(test).size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;}); @@ -120,7 +129,7 @@ XmlrpcTest::test_basics() { } void -XmlrpcTest::test_invalid_utf8() { +TestXmlrpc::test_invalid_utf8() { // Surprisingly, this call doesn't fail. TinyXML-2 technically expects // valid UTF-8, but doesn't check strings, and Object strings are // just a series of bytes so it reflects just fine. @@ -132,7 +141,7 @@ XmlrpcTest::test_invalid_utf8() { } void -XmlrpcTest::test_size_limit() { +TestXmlrpc::test_size_limit() { std::string input = "xmlrpc_reflect\xc3\x28"; std::string expected = "faultCode-509faultStringContent size exceeds maximum XML-RPC limit"; std::string output; @@ -140,9 +149,13 @@ XmlrpcTest::test_size_limit() { m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;}); CPPUNIT_ASSERT_EQUAL(expected, output); } + #else -void XmlrpcTest::test_invalid_utf8() {} -void XmlrpcTest::test_basics() {} -void XmlrpcTest::test_size_limit() {} -void XmlrpcTest::setUp() {} + +void TestXmlrpc::test_invalid_utf8() {} +void TestXmlrpc::test_basics() {} +void TestXmlrpc::test_size_limit() {} +void TestXmlrpc::setUp() {} +void TestXmlrpc::tearDown() {} + #endif diff --git a/test/rpc/xmlrpc_test.h b/test/rpc/test_xmlrpc.h similarity index 56% rename from test/rpc/xmlrpc_test.h rename to test/rpc/test_xmlrpc.h index df27d32f..6c4fbd22 100644 --- a/test/rpc/xmlrpc_test.h +++ b/test/rpc/test_xmlrpc.h @@ -1,29 +1,32 @@ -#include +#include "test/helpers/test_fixture.h" +#include "test/helpers/test_main_thread.h" #include "rpc/command_map.h" #include "rpc/xmlrpc.h" -class XmlrpcTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(XmlrpcTest); +class TestXmlrpc : public test_fixture { + CPPUNIT_TEST_SUITE(TestXmlrpc); + CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_invalid_utf8); CPPUNIT_TEST(test_size_limit); + CPPUNIT_TEST_SUITE_END(); public: static const int cmd_size = 256; void setUp(); - void tearDown() {} + void tearDown(); void test_basics(); void test_invalid_utf8(); void test_size_limit(); private: - rpc::XmlRpc m_xmlrpc; - - rpc::CommandMap m_map; + std::unique_ptr m_test_main_thread; + rpc::XmlRpc m_xmlrpc; + rpc::CommandMap m_map; rpc::command_base m_commands[cmd_size]; }; diff --git a/test/src/command_dynamic_test.h b/test/src/command_dynamic_test.h deleted file mode 100644 index 4af0c66b..00000000 --- a/test/src/command_dynamic_test.h +++ /dev/null @@ -1,20 +0,0 @@ -#include - -class CommandDynamicTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(CommandDynamicTest); - CPPUNIT_TEST(test_basics); - CPPUNIT_TEST(test_get_set); - CPPUNIT_TEST(test_old_style); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp(); - void tearDown() {} - - void test_basics(); - void test_get_set(); - - void test_old_style(); - -private: -}; diff --git a/test/src/command_dynamic_test.cc b/test/src/test_command_dynamic.cc similarity index 83% rename from test/src/command_dynamic_test.cc rename to test/src/test_command_dynamic.cc index f71216de..445b0533 100644 --- a/test/src/command_dynamic_test.cc +++ b/test/src/test_command_dynamic.cc @@ -1,22 +1,21 @@ #include "config.h" -#include +#include "test/src/test_command_dynamic.h" -#include "command_dynamic_test.h" - -#include "helpers/assert.h" - -#include "rpc/parse_commands.h" #include "control.h" #include "globals.h" +#include "rpc/parse_commands.h" -CPPUNIT_TEST_SUITE_REGISTRATION(CommandDynamicTest); +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandDynamic); void initialize_command_dynamic(); void initialize_command_ui(); void -CommandDynamicTest::setUp() { +TestCommandDynamic::setUp() { + m_test_main_thread = TestMainThread::create(); + m_test_main_thread->init_thread(); + if (rpc::commands.find("method.insert") == rpc::commands.end()) { setlocale(LC_ALL, ""); // cachedTime = rak::timer::current(); @@ -28,13 +27,18 @@ CommandDynamicTest::setUp() { } void -CommandDynamicTest::test_basics() { +TestCommandDynamic::tearDown() { + m_test_main_thread.reset(); +} + +void +TestCommandDynamic::test_basics() { rpc::commands.call_command("method.insert.value", rpc::create_object_list("test_basics.1", int64_t(1))); CPPUNIT_ASSERT(rpc::commands.call_command("test_basics.1", torrent::Object()).as_value() == 1); } void -CommandDynamicTest::test_get_set() { +TestCommandDynamic::test_get_set() { rpc::commands.call_command("method.insert.simple", rpc::create_object_list("test_get_set.1", "cat=1")); CPPUNIT_ASSERT(rpc::commands.call_command("test_get_set.1", torrent::Object()).as_string() == "1"); CPPUNIT_ASSERT(rpc::commands.call_command("method.get", "test_get_set.1").as_string() == "cat=1"); @@ -44,7 +48,7 @@ CommandDynamicTest::test_get_set() { } void -CommandDynamicTest::test_old_style() { +TestCommandDynamic::test_old_style() { rpc::commands.call_command("method.insert", rpc::create_object_list("test_old_style.1", "value", int64_t(1))); CPPUNIT_ASSERT(rpc::commands.call_command("test_old_style.1", torrent::Object()).as_value() == 1); diff --git a/test/src/test_command_dynamic.h b/test/src/test_command_dynamic.h new file mode 100644 index 00000000..2214d64c --- /dev/null +++ b/test/src/test_command_dynamic.h @@ -0,0 +1,24 @@ +#include "test/helpers/test_fixture.h" +#include "test/helpers/test_main_thread.h" + +class TestCommandDynamic : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommandDynamic); + + CPPUNIT_TEST(test_basics); + CPPUNIT_TEST(test_get_set); + CPPUNIT_TEST(test_old_style); + + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void test_basics(); + void test_get_set(); + + void test_old_style(); + +private: + std::unique_ptr m_test_main_thread; +};