diff --git a/ChangeLog b/ChangeLog index f0081e2aa..a38a8aab2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -60,6 +60,7 @@ Enhancements: - #7407 Implement safer memory use, improve dev env, fixed GUI bugs - #7412 Reduce GUI compile time by building a GUI library - #7413 Improve UI design and reduce over-use of `#ifdef` +- #7414 Expand BSD sockets poll tests and remove legacy-poll code # 1.14.6 diff --git a/cmake/Libraries.cmake b/cmake/Libraries.cmake index 1715cb5de..e3a487c6f 100644 --- a/cmake/Libraries.cmake +++ b/cmake/Libraries.cmake @@ -53,7 +53,6 @@ macro(configure_unix_libs) check_function_exists(getpwuid_r HAVE_GETPWUID_R) check_function_exists(gmtime_r HAVE_GMTIME_R) check_function_exists(nanosleep HAVE_NANOSLEEP) - check_function_exists(poll HAVE_POLL) check_function_exists(sigwait HAVE_POSIX_SIGWAIT) check_function_exists(strftime HAVE_STRFTIME) check_function_exists(vsnprintf HAVE_VSNPRINTF) diff --git a/res/config.h.in b/res/config.h.in index 9a64c5323..218906f36 100644 --- a/res/config.h.in +++ b/res/config.h.in @@ -49,9 +49,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_OSTREAM ${HAVE_OSTREAM} -/* Define if you have the `poll` function. */ -#cmakedefine HAVE_POLL ${HAVE_POLL} - /* Define if you have a POSIX `sigwait` function. */ #cmakedefine HAVE_POSIX_SIGWAIT ${HAVE_POSIX_SIGWAIT} diff --git a/scripts/tests.py b/scripts/tests.py index 3e8c1ee3c..4fecb17d6 100755 --- a/scripts/tests.py +++ b/scripts/tests.py @@ -6,6 +6,9 @@ env.ensure_in_venv(__file__) import argparse, os, sys import lib.cmd_utils as cmd_utils +import lib.colors as colors + +valgrind_bin = "valgrind" def main(): @@ -39,7 +42,11 @@ def main(): command = [binary] if args.valgrind: - command = ["valgrind"] + command + if not cmd_utils.has_command(valgrind_bin): + print(f"{colors.ERROR_TEXT} {valgrind_bin} not found") + sys.exit(1) + + command = [valgrind_bin] + command result = cmd_utils.run(command, print_cmd=True, check=False) if not args.ignore_return_code: diff --git a/src/lib/arch/unix/ArchNetworkBSD.cpp b/src/lib/arch/unix/ArchNetworkBSD.cpp index 657aaac05..5465bbeef 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.cpp +++ b/src/lib/arch/unix/ArchNetworkBSD.cpp @@ -22,18 +22,20 @@ #include "arch/unix/ArchMultithreadPosix.h" #include "arch/unix/XArchUnix.h" -#if HAVE_UNISTD_H -#include -#endif -#include -#include -#if !defined(TCP_NODELAY) -#include -#endif #include #include #include #include +#include +#include + +#if HAVE_UNISTD_H +#include +#endif + +#if !defined(TCP_NODELAY) +#include +#endif #if !HAVE_INET_ATON #include @@ -44,8 +46,11 @@ static const int s_family[] = { PF_INET, PF_INET6, }; + static const int s_type[] = {SOCK_DGRAM, SOCK_STREAM}; +ArchNetworkBSD::Deps ArchNetworkBSD::s_deps; + #if !HAVE_INET_ATON // parse dotted quad addresses. we don't bother with the weird BSD'ism // of handling octal and hex and partial forms. @@ -67,12 +72,35 @@ static in_addr_t inet_aton(const char *cp, struct in_addr *inp) { #endif // -// ArchNetworkBSD +// ArchNetworkBSD::Deps // -ArchNetworkBSD::Connectors ArchNetworkBSD::s_connectors; +void ArchNetworkBSD::Deps::sleep(double seconds) { + // + ARCH->sleep(seconds); +} -ArchNetworkBSD::ArchNetworkBSD() = default; +int ArchNetworkBSD::Deps::poll(struct pollfd *fds, nfds_t nfds, int timeout) { + return ::poll(fds, nfds, timeout); +} + +std::shared_ptr ArchNetworkBSD::Deps::makePollFD(nfds_t n) { + // C++20 supports std::make_shared(n) but this is not + // implemented on the compiler that comes with Ubuntu 22 and a few other + // distros, so use the manual new and delete until we drop those distros. + return std::shared_ptr( + new struct pollfd[n], std::default_delete()); +} + +ssize_t ArchNetworkBSD::Deps::read(int fd, void *buf, size_t len) { + return ::read(fd, buf, len); +} + +void ArchNetworkBSD::Deps::testCancelThread() { ARCH->testCancelThread(); } + +// +// ArchNetworkBSD +// ArchNetworkBSD::~ArchNetworkBSD() { if (m_mutex) @@ -240,21 +268,20 @@ bool ArchNetworkBSD::connectSocket(ArchSocket s, ArchNetAddress addr) { return true; } -#if HAVE_POLL - int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { assert(pe != NULL || num == 0); // return if nothing to do if (num == 0) { if (timeout > 0.0) { - ARCH->sleep(timeout); + m_deps.sleep(timeout); } return 0; } // allocate space for translated query - auto *pfd = new struct pollfd[1 + num]; + auto pfdPtr = m_deps.makePollFD(1 + num); + auto *pfd = pfdPtr.get(); // translate query for (int i = 0; i < num; ++i) { @@ -272,7 +299,9 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { // add the unblock pipe const int *unblockPipe = getUnblockPipe(); if (unblockPipe != nullptr) { + assert(n < (1 + num)); pfd[n].fd = unblockPipe[0]; + pfd[n].events = POLLIN; ++n; } @@ -281,16 +310,14 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { int t = (timeout < 0.0) ? -1 : static_cast(1000.0 * timeout); // do the poll - n = s_connectors.poll_impl(pfd, n, t); + n = m_deps.poll(pfd, n, t); // reset the unblock pipe if (n > 0 && unblockPipe != nullptr && (pfd[num].revents & POLLIN) != 0) { // the unblock event was signalled. flush the pipe. char dummy[100]; - int ignore; - do { - ignore = read(unblockPipe[0], dummy, sizeof(dummy)); + m_deps.read(unblockPipe[0], dummy, sizeof(dummy)); } while (errno != EAGAIN); // don't count this unblock pipe in return value @@ -301,13 +328,11 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { if (n == -1) { if (errno == EINTR) { // interrupted system call - ARCH->testCancelThread(); - delete[] pfd; + m_deps.testCancelThread(); return 0; } - delete[] pfd; throwError(errno); - return -1; + return -1; // unreachable } // translate back @@ -327,131 +352,9 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { } } - delete[] pfd; return n; } -#else - -int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { - int i, n; - - // prepare sets for select - n = 0; - fd_set readSet, writeSet, errSet; - fd_set *readSetP = NULL; - fd_set *writeSetP = NULL; - fd_set *errSetP = NULL; - FD_ZERO(&readSet); - FD_ZERO(&writeSet); - FD_ZERO(&errSet); - for (i = 0; i < num; ++i) { - // reset return flags - pe[i].m_revents = 0; - - // set invalid flag if socket is bogus then go to next socket - if (pe[i].m_socket == NULL) { - pe[i].m_revents |= kPOLLNVAL; - continue; - } - - int fdi = pe[i].m_socket->m_fd; - if (pe[i].m_events & kPOLLIN) { - FD_SET(pe[i].m_socket->m_fd, &readSet); - readSetP = &readSet; - if (fdi > n) { - n = fdi; - } - } - if (pe[i].m_events & kPOLLOUT) { - FD_SET(pe[i].m_socket->m_fd, &writeSet); - writeSetP = &writeSet; - if (fdi > n) { - n = fdi; - } - } - if (true) { - FD_SET(pe[i].m_socket->m_fd, &errSet); - errSetP = &errSet; - if (fdi > n) { - n = fdi; - } - } - } - - // add the unblock pipe - const int *unblockPipe = getUnblockPipe(); - if (unblockPipe != NULL) { - FD_SET(unblockPipe[0], &readSet); - readSetP = &readSet; - if (unblockPipe[0] > n) { - n = unblockPipe[0]; - } - } - - // if there are no sockets then don't block forever - if (n == 0 && timeout < 0.0) { - timeout = 0.0; - } - - // prepare timeout for select - struct timeval timeout2; - struct timeval *timeout2P; - if (timeout < 0.0) { - timeout2P = NULL; - } else { - timeout2P = &timeout2; - timeout2.tv_sec = static_cast(timeout); - timeout2.tv_usec = static_cast(1.0e+6 * (timeout - timeout2.tv_sec)); - } - - // do the select - n = select( - (SELECT_TYPE_ARG1)n + 1, SELECT_TYPE_ARG234 readSetP, - SELECT_TYPE_ARG234 writeSetP, SELECT_TYPE_ARG234 errSetP, - SELECT_TYPE_ARG5 timeout2P); - - // reset the unblock pipe - if (n > 0 && unblockPipe != NULL && FD_ISSET(unblockPipe[0], &readSet)) { - // the unblock event was signalled. flush the pipe. - char dummy[100]; - do { - read(unblockPipe[0], dummy, sizeof(dummy)); - } while (errno != EAGAIN); - } - - // handle results - if (n == -1) { - if (errno == EINTR) { - // interrupted system call - ARCH->testCancelThread(); - return 0; - } - throwError(errno); - } - n = 0; - for (i = 0; i < num; ++i) { - if (pe[i].m_socket != NULL) { - if (FD_ISSET(pe[i].m_socket->m_fd, &readSet)) { - pe[i].m_revents |= kPOLLIN; - } - if (FD_ISSET(pe[i].m_socket->m_fd, &writeSet)) { - pe[i].m_revents |= kPOLLOUT; - } - if (FD_ISSET(pe[i].m_socket->m_fd, &errSet)) { - pe[i].m_revents |= kPOLLERR; - } - } - if (pe[i].m_revents != 0) { - ++n; - } - } - - return n; -} - -#endif - void ArchNetworkBSD::unblockPollSocket(ArchThread thread) { const int *unblockPipe = getUnblockPipeForThread(thread); if (unblockPipe != nullptr) { diff --git a/src/lib/arch/unix/ArchNetworkBSD.h b/src/lib/arch/unix/ArchNetworkBSD.h index 21c12f313..a930ef97e 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.h +++ b/src/lib/arch/unix/ArchNetworkBSD.h @@ -20,10 +20,12 @@ #include "arch/IArchMultithread.h" #include "arch/IArchNetwork.h" +#include #if HAVE_SYS_TYPES_H #include #endif + #if HAVE_SYS_SOCKET_H #include #else @@ -40,25 +42,16 @@ struct sockaddr_storage { typedef int socklen_t; #endif -#if HAVE_POLL #include -#else -#if HAVE_SYS_SELECT_H -#include -#endif -#if HAVE_SYS_TIME_H -#include -#endif -#endif + +#define ARCH_NETWORK ArchNetworkBSD +#define TYPED_ADDR(type_, addr_) (reinterpret_cast(&addr_->m_addr)) // old systems may use char* for [gs]etsockopt()'s optval argument. // this should be void on modern systems but char is forwards // compatible so we always use it. typedef char optval_t; -#define ARCH_NETWORK ArchNetworkBSD -#define TYPED_ADDR(type_, addr_) (reinterpret_cast(&addr_->m_addr)) - class ArchSocketImpl { public: int m_fd; @@ -77,57 +70,55 @@ public: //! Berkeley (BSD) sockets implementation of IArchNetwork class ArchNetworkBSD : public IArchNetwork { public: - ArchNetworkBSD(); + struct Deps { + virtual ~Deps() = default; + virtual void sleep(double); + virtual int poll(struct pollfd *, nfds_t, int); + virtual std::shared_ptr makePollFD(nfds_t); + virtual ssize_t read(int, void *, size_t); + virtual void testCancelThread(); + }; + + explicit ArchNetworkBSD() : m_deps(s_deps) {} + explicit ArchNetworkBSD(Deps &deps) : m_deps(deps) {} ArchNetworkBSD(ArchNetworkBSD const &) = delete; ArchNetworkBSD(ArchNetworkBSD &&) = delete; - virtual ~ArchNetworkBSD(); + ~ArchNetworkBSD() override; ArchNetworkBSD &operator=(ArchNetworkBSD const &) = delete; ArchNetworkBSD &operator=(ArchNetworkBSD &&) = delete; - virtual void init(); + void init() override; // IArchNetwork overrides - virtual ArchSocket newSocket(EAddressFamily, ESocketType); - virtual ArchSocket copySocket(ArchSocket s); - virtual void closeSocket(ArchSocket s); - virtual void closeSocketForRead(ArchSocket s); - virtual void closeSocketForWrite(ArchSocket s); - virtual void bindSocket(ArchSocket s, ArchNetAddress addr); - virtual void listenOnSocket(ArchSocket s); - virtual ArchSocket acceptSocket(ArchSocket s, ArchNetAddress *addr); - virtual bool connectSocket(ArchSocket s, ArchNetAddress name); - virtual int pollSocket(PollEntry[], int num, double timeout); - virtual void unblockPollSocket(ArchThread thread); - virtual size_t readSocket(ArchSocket s, void *buf, size_t len); - virtual size_t writeSocket(ArchSocket s, const void *buf, size_t len); - virtual void throwErrorOnSocket(ArchSocket); - virtual bool setNoDelayOnSocket(ArchSocket, bool noDelay); - virtual bool setReuseAddrOnSocket(ArchSocket, bool reuse); - virtual std::string getHostName(); - virtual ArchNetAddress newAnyAddr(EAddressFamily); - virtual ArchNetAddress copyAddr(ArchNetAddress); - virtual std::vector nameToAddr(const std::string &); - virtual void closeAddr(ArchNetAddress); - virtual std::string addrToName(ArchNetAddress); - virtual std::string addrToString(ArchNetAddress); - virtual EAddressFamily getAddrFamily(ArchNetAddress); - virtual void setAddrPort(ArchNetAddress, int port); - virtual int getAddrPort(ArchNetAddress); - virtual bool isAnyAddr(ArchNetAddress); - virtual bool isEqualAddr(ArchNetAddress, ArchNetAddress); - - struct Connectors { -#if HAVE_POLL - int (*poll_impl)(struct pollfd *, nfds_t, int); -#endif // HAVE_POLL - Connectors() { -#if HAVE_POLL - poll_impl = poll; -#endif // HAVE_POLL - } - }; - static Connectors s_connectors; + ArchSocket newSocket(EAddressFamily, ESocketType) override; + ArchSocket copySocket(ArchSocket s) override; + void closeSocket(ArchSocket s) override; + void closeSocketForRead(ArchSocket s) override; + void closeSocketForWrite(ArchSocket s) override; + void bindSocket(ArchSocket s, ArchNetAddress addr) override; + void listenOnSocket(ArchSocket s) override; + ArchSocket acceptSocket(ArchSocket s, ArchNetAddress *addr) override; + bool connectSocket(ArchSocket s, ArchNetAddress name) override; + int pollSocket(PollEntry[], int num, double timeout) override; + void unblockPollSocket(ArchThread thread) override; + size_t readSocket(ArchSocket s, void *buf, size_t len) override; + size_t writeSocket(ArchSocket s, const void *buf, size_t len) override; + void throwErrorOnSocket(ArchSocket) override; + bool setNoDelayOnSocket(ArchSocket, bool noDelay) override; + bool setReuseAddrOnSocket(ArchSocket, bool reuse) override; + std::string getHostName() override; + ArchNetAddress newAnyAddr(EAddressFamily) override; + ArchNetAddress copyAddr(ArchNetAddress) override; + std::vector nameToAddr(const std::string &) override; + void closeAddr(ArchNetAddress) override; + std::string addrToName(ArchNetAddress) override; + std::string addrToString(ArchNetAddress) override; + EAddressFamily getAddrFamily(ArchNetAddress) override; + void setAddrPort(ArchNetAddress, int port) override; + int getAddrPort(ArchNetAddress) override; + bool isAnyAddr(ArchNetAddress) override; + bool isEqualAddr(ArchNetAddress, ArchNetAddress) override; private: const int *getUnblockPipe(); @@ -138,4 +129,6 @@ private: private: ArchMutex m_mutex{}; + Deps &m_deps; + static Deps s_deps; }; diff --git a/src/lib/platform/XWindowsEventQueueBuffer.cpp b/src/lib/platform/XWindowsEventQueueBuffer.cpp index bef9a6472..ad40817d0 100644 --- a/src/lib/platform/XWindowsEventQueueBuffer.cpp +++ b/src/lib/platform/XWindowsEventQueueBuffer.cpp @@ -27,19 +27,8 @@ #if HAVE_UNISTD_H #include #endif -#if HAVE_POLL + #include -#else -#if HAVE_SYS_SELECT_H -#include -#endif -#if HAVE_SYS_TIME_H -#include -#endif -#if HAVE_SYS_TYPES_H -#include -#endif -#endif // // EventQueueTimer @@ -112,7 +101,6 @@ void XWindowsEventQueueBuffer::waitForEvent(double dtimeout) { // use poll() to wait for a message from the X server or for timeout. // this is a good deal more efficient than polling and sleeping. -#if HAVE_POLL struct pollfd pfds[2]; pfds[0].fd = ConnectionNumber(m_display); pfds[0].events = POLLIN; @@ -121,29 +109,7 @@ void XWindowsEventQueueBuffer::waitForEvent(double dtimeout) { int timeout = (dtimeout < 0.0) ? -1 : static_cast(1000.0 * dtimeout); int remaining = timeout; int retval = 0; -#else - struct timeval timeout; - struct timeval *timeoutPtr; - if (dtimeout < 0.0) { - timeoutPtr = NULL; - } else { - timeout.tv_sec = static_cast(dtimeout); - timeout.tv_usec = static_cast(1.0e+6 * (dtimeout - timeout.tv_sec)); - timeoutPtr = &timeout; - } - // initialize file descriptor sets - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(ConnectionNumber(m_display), &rfds); - FD_SET(m_pipefd[0], &rfds); - int nfds; - if (ConnectionNumber(m_display) > m_pipefd[0]) { - nfds = ConnectionNumber(m_display) + 1; - } else { - nfds = m_pipefd[0] + 1; - } -#endif // It's possible that the X server has queued events locally // in xlib's event buffer and not pushed on to the fd. Hence we // can't simply monitor the fd as we may never be woken up. @@ -159,7 +125,7 @@ void XWindowsEventQueueBuffer::waitForEvent(double dtimeout) { while (((dtimeout < 0.0) || (remaining > 0)) && getPendingCountLocked() == 0 && retval == 0) { -#if HAVE_POLL + retval = poll(pfds, 2, TIMEOUT_DELAY); // 16ms = 60hz, but we make it > to // play nicely with the cpu if (pfds[1].revents & POLLIN) { @@ -170,14 +136,6 @@ void XWindowsEventQueueBuffer::waitForEvent(double dtimeout) { // todo: handle read response } } -#else - retval = select( - nfds, SELECT_TYPE_ARG234 & rfds, SELECT_TYPE_ARG234 NULL, - SELECT_TYPE_ARG234 NULL, SELECT_TYPE_ARG5 TIMEOUT_DELAY); - if (FD_SET(m_pipefd[0], &rfds)) { - read(m_pipefd[0], buf, 15); - } -#endif remaining -= TIMEOUT_DELAY; } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 3f528e18f..cfd009e65 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -68,11 +68,12 @@ macro(set_sources) list(APPEND sources ${CMAKE_BINARY_DIR}/src/version.rc) endif() - append_platform_sources() + replace_platform_sources() + replace_arch_sources() endmacro() -macro(append_platform_sources) +macro(replace_platform_sources) set(platform_dir ${CMAKE_CURRENT_SOURCE_DIR}/platform) @@ -99,6 +100,30 @@ macro(append_platform_sources) endmacro() +macro(replace_arch_sources) + + set(arch_dir ${CMAKE_CURRENT_SOURCE_DIR}/arch) + + # Remove arch files so that specific arch files can be added later. + # This is a bit weird, but it's simpler to include everything, remove all + # arch files, then only include the archs we need. + file(GLOB_RECURSE all_arch_files ${arch_dir}/*) + list(REMOVE_ITEM headers ${all_arch_files}) + list(REMOVE_ITEM sources ${all_arch_files}) + + if(WIN32) + file(GLOB arch_sources ${arch_dir}/win32/*.cpp) + file(GLOB arch_headers ${arch_dir}/win32/*.h) + elseif(UNIX) + file(GLOB arch_sources ${arch_dir}/unix/*.cpp) + file(GLOB arch_headers ${arch_dir}/unix/*.h) + endif() + + list(APPEND sources ${arch_sources}) + list(APPEND headers ${arch_headers}) + +endmacro() + macro(config_test_deps) set(gtest_dir ${gtest_base_dir}/googletest) diff --git a/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp b/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp index 0c5378f7a..3145479c0 100644 --- a/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp +++ b/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp @@ -15,53 +15,188 @@ * along with this program. If not, see . */ -// TODO: mock out sockets on integ tests masquerading as unit tests -#if 0 - -#ifndef _WIN32 -#include "lib/arch/XArch.h" +#include "arch/IArchNetwork.h" #include "lib/arch/unix/ArchNetworkBSD.h" -#include +#include "lib/arch/XArch.h" + +#include #include #include #include #include -TEST(ArchNetworkBSDTests, pollSocket_errs_EACCES) { - ArchNetworkBSD networkBSD; - ArchNetworkBSD::s_connectors.poll_impl = [](struct pollfd *, nfds_t, int) { +using ::testing::_; +using ::testing::NiceMock; +using PollEntries = std::vector; +using PollFD = struct pollfd[]; + +struct MockDeps : public ArchNetworkBSD::Deps { + MockDeps() { + ON_CALL(*this, makePollFD(_)).WillByDefault([this](nfds_t n) { + m_pollFD = ArchNetworkBSD::Deps::makePollFD(n); + return m_pollFD; + }); + } + + MOCK_METHOD(void, sleep, (double), (override)); + MOCK_METHOD(int, poll, (struct pollfd *, nfds_t, int), (override)); + MOCK_METHOD(std::shared_ptr, makePollFD, (nfds_t), (override)); + MOCK_METHOD(ssize_t, read, (int, void *, size_t), (override)); + MOCK_METHOD(void, testCancelThread, (), (override)); + + std::shared_ptr m_pollFD; +}; + +TEST(ArchNetworkBSDTests, pollSocket_zeroEntries_callsSleep) { + MockDeps deps; + ArchNetworkBSD networkBSD(deps); + + EXPECT_CALL(deps, sleep(1)).Times(1); + auto result = networkBSD.pollSocket(nullptr, 0, 1); + + EXPECT_EQ(result, 0); +} + +TEST(ArchNetworkBSDTests, pollSocket_mockAccessError_throws) { + NiceMock deps; + ON_CALL(deps, poll(_, _, _)).WillByDefault([]() { errno = EACCES; return -1; + }); + ArchNetworkBSD networkBSD(deps); + PollEntries entries{{nullptr, 0, 0}}; + + const auto f = [&] { + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); }; - try { - std::array pe{{nullptr, 0, 0}}; - networkBSD.pollSocket(pe.data(), pe.size(), 1); - FAIL() << "Expected to throw"; - } catch (XArchNetworkAccess const &err) { - EXPECT_STREQ(err.what(), "Permission denied"); - } catch (std::runtime_error const &baseerr) { - FAIL() << "Expected to throw XArchNetworkAccess but got " << baseerr.what(); - } + + EXPECT_THROW({ f(); }, XArchNetworkAccess); } -TEST(ArchNetworkBSDTests, isAnyAddr_IP6) { - ArchNetworkBSD networkBSD; +TEST(ArchNetworkBSDTests, pollSocket_pfdHasRevents_copiedToEntries) { + NiceMock deps; + ON_CALL(deps, poll(_, _, _)).WillByDefault([](auto pfd, auto, auto) { + pfd[0].revents = POLLIN | POLLOUT | POLLERR | POLLNVAL; + return 0; + }); + ArchNetworkBSD networkBSD(deps); + PollEntries entries{{nullptr, 0, 0}}; + + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); + + const auto expect = IArchNetwork::kPOLLIN | IArchNetwork::kPOLLOUT | + IArchNetwork::kPOLLERR | IArchNetwork::kPOLLNVAL; + EXPECT_EQ(entries[0].m_revents, expect); +} + +TEST(ArchNetworkBSDTests, pollSocket_nullSocket_fdIsNegativeOne) { + NiceMock deps; + ArchNetworkBSD networkBSD(deps); + PollEntries entries{{nullptr, 0, 0}}; + + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); + + EXPECT_EQ(deps.m_pollFD[0].fd, -1); +} + +TEST(ArchNetworkBSDTests, pollSocket_socketSet_fdWasSet) { + NiceMock deps; + ArchNetworkBSD networkBSD(deps); + ArchSocketImpl socket{1, 0}; + PollEntries entries{{&socket, 0, 0}}; + + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); + + EXPECT_EQ(deps.m_pollFD[0].fd, 1); +} + +TEST(ArchNetworkBSDTests, pollSocket_eventHasPollInBit_bitWasSet) { + NiceMock deps; + ArchNetworkBSD networkBSD(deps); + ArchSocketImpl socket{1, 0}; + PollEntries entries{{&socket, IArchNetwork::kPOLLIN, 0}}; + + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); + + EXPECT_EQ(deps.m_pollFD[0].events, POLLIN); +} + +TEST(ArchNetworkBSDTests, pollSocket_eventHasPollOutBit_bitWasSet) { + NiceMock deps; + ArchNetworkBSD networkBSD(deps); + ArchSocketImpl socket{1, 0}; + PollEntries entries{{&socket, IArchNetwork::kPOLLOUT, 0}}; + + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); + + EXPECT_EQ(deps.m_pollFD[0].events, POLLOUT); +} + +TEST(ArchNetworkBSDTests, pollSocket_nullSocket_unblockPipeAppended) { + NiceMock deps; + ArchNetworkBSD networkBSD(deps); + PollEntries entries{{nullptr, 0, 0}}; + + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); + + // interesting: unblock pipe fd comes from `getNetworkDataForThread` which + // seems to differ depending on linux distro. + EXPECT_GT(deps.m_pollFD[1].fd, -1); +} + +TEST(ArchNetworkBSDTests, pollSocket_unblockPipeReventsError_readCalled) { + const auto unblockPipeIndex = 1; + NiceMock deps; + ON_CALL(deps, poll(_, _, _)).WillByDefault([](auto pfd, auto, auto) { + pfd[unblockPipeIndex].revents = POLLIN; + return 1; + }); + ON_CALL(deps, read(_, _, _)).WillByDefault([]() { + errno = EAGAIN; + return 0; + }); + ArchNetworkBSD networkBSD(deps); + PollEntries entries{{nullptr, 0, 0}}; + + EXPECT_CALL(deps, read(_, _, _)).Times(1); + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); +} + +TEST(ArchNetworkBSDTests, pollSocket_interruptSystemCall_testCancelThread) { + NiceMock deps; + ON_CALL(deps, poll(_, _, _)).WillByDefault([]() { + errno = EINTR; + return -1; + }); + ArchNetworkBSD networkBSD(deps); + PollEntries entries{{nullptr, 0, 0}}; + + EXPECT_CALL(deps, testCancelThread()).Times(1); + networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); +} + +TEST(ArchNetworkBSDTests, isAnyAddr_goodAddress_returnsTrue) { + MockDeps deps; + ArchNetworkBSD networkBSD(deps); std::unique_ptr addr; addr.reset(networkBSD.newAnyAddr(IArchNetwork::kINET6)); - EXPECT_TRUE(networkBSD.isAnyAddr(addr.get())); - auto scratch = (char *)&addr->m_addr; - scratch[2] = 'b'; - scratch[3] = 'a'; - scratch[4] = 'd'; - scratch[5] = 'a'; - scratch[6] = 'd'; - scratch[7] = 'd'; - scratch[8] = 'r'; - EXPECT_FALSE(networkBSD.isAnyAddr(addr.get())); + auto result = networkBSD.isAnyAddr(addr.get()); + + EXPECT_TRUE(result); } -#endif // #ifdnef _WIN32 +TEST(ArchNetworkBSDTests, isAnyAddr_badAddress_returnsFalse) { + MockDeps deps; + ArchNetworkBSD networkBSD(deps); + std::unique_ptr addr; + addr.reset(networkBSD.newAnyAddr(IArchNetwork::kINET6)); + auto scratch = (char *)&addr->m_addr; + std::string badAddr = "badaddr"; + std::ranges::copy(badAddr, scratch + 2); -#endif + auto result = networkBSD.isAnyAddr(addr.get()); + + EXPECT_FALSE(result); +}