Expand BSD sockets poll tests and remove legacy-poll code (#7414)

* Move integ test to corredt place and re-enable tests

* Depend on `poll.h` (removes support for non-POSIX systems)

* Only build tests for each arch

* Move back to unit tests (poll is mocked)

* Better error message for valgrind not found

* Simplify dependency injection for BSD sockets poll test

* Improve test readability for BSD net poll

* Split out 2-in-1 test for `isAnyAddr`

* Stub out sleep function

* Improve coverage for pollSocket

* Use gmock ON_CALL instead of manual mock

* Remove unused function signature

* Use conventional deps struct instead of std functional

* Add test for socket data FD set to -1

* Add assertation for adding unblock pipe

* Use older style array alloc

* Less precision around `getNetworkDataForThread` value

* Use `ssize_t` for `ignore`

* Remove unused var

* Update ChangeLog
This commit is contained in:
Nick Bolton 2024-07-29 18:04:17 +01:00 committed by GitHub
parent cdb6dcd597
commit b02077550c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 301 additions and 283 deletions

View file

@ -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

View file

@ -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)

View file

@ -49,9 +49,6 @@
/* Define to 1 if you have the <ostream> 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}

View file

@ -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:

View file

@ -22,18 +22,20 @@
#include "arch/unix/ArchMultithreadPosix.h"
#include "arch/unix/XArchUnix.h"
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <netdb.h>
#include <netinet/in.h>
#if !defined(TCP_NODELAY)
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if !defined(TCP_NODELAY)
#include <netinet/tcp.h>
#endif
#if !HAVE_INET_ATON
#include <stdio.h>
@ -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<struct pollfd[]> ArchNetworkBSD::Deps::makePollFD(nfds_t n) {
// C++20 supports std::make_shared<struct pollfd[]>(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<struct pollfd[]>(
new struct pollfd[n], std::default_delete<struct pollfd[]>());
}
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<int>(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<int>(timeout);
timeout2.tv_usec = static_cast<int>(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) {

View file

@ -20,10 +20,12 @@
#include "arch/IArchMultithread.h"
#include "arch/IArchNetwork.h"
#include <memory>
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#else
@ -40,25 +42,16 @@ struct sockaddr_storage {
typedef int socklen_t;
#endif
#if HAVE_POLL
#include <poll.h>
#else
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#endif
#define ARCH_NETWORK ArchNetworkBSD
#define TYPED_ADDR(type_, addr_) (reinterpret_cast<type_ *>(&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<type_ *>(&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<struct pollfd[]> 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<ArchNetAddress> 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<ArchNetAddress> 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;
};

View file

@ -27,19 +27,8 @@
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_POLL
#include <poll.h>
#else
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#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<int>(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<int>(dtimeout);
timeout.tv_usec = static_cast<int>(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;
}

View file

@ -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)

View file

@ -15,53 +15,188 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// 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 <array>
#include "lib/arch/XArch.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <memory>
#include <netinet/in.h>
#include <sys/types.h>
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<IArchNetwork::PollEntry>;
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<PollFD>, makePollFD, (nfds_t), (override));
MOCK_METHOD(ssize_t, read, (int, void *, size_t), (override));
MOCK_METHOD(void, testCancelThread, (), (override));
std::shared_ptr<PollFD> 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<MockDeps> 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<int>(entries.size()), 1);
};
try {
std::array<IArchNetwork::PollEntry, 2> 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<MockDeps> 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<int>(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<MockDeps> deps;
ArchNetworkBSD networkBSD(deps);
PollEntries entries{{nullptr, 0, 0}};
networkBSD.pollSocket(entries.data(), static_cast<int>(entries.size()), 1);
EXPECT_EQ(deps.m_pollFD[0].fd, -1);
}
TEST(ArchNetworkBSDTests, pollSocket_socketSet_fdWasSet) {
NiceMock<MockDeps> deps;
ArchNetworkBSD networkBSD(deps);
ArchSocketImpl socket{1, 0};
PollEntries entries{{&socket, 0, 0}};
networkBSD.pollSocket(entries.data(), static_cast<int>(entries.size()), 1);
EXPECT_EQ(deps.m_pollFD[0].fd, 1);
}
TEST(ArchNetworkBSDTests, pollSocket_eventHasPollInBit_bitWasSet) {
NiceMock<MockDeps> deps;
ArchNetworkBSD networkBSD(deps);
ArchSocketImpl socket{1, 0};
PollEntries entries{{&socket, IArchNetwork::kPOLLIN, 0}};
networkBSD.pollSocket(entries.data(), static_cast<int>(entries.size()), 1);
EXPECT_EQ(deps.m_pollFD[0].events, POLLIN);
}
TEST(ArchNetworkBSDTests, pollSocket_eventHasPollOutBit_bitWasSet) {
NiceMock<MockDeps> deps;
ArchNetworkBSD networkBSD(deps);
ArchSocketImpl socket{1, 0};
PollEntries entries{{&socket, IArchNetwork::kPOLLOUT, 0}};
networkBSD.pollSocket(entries.data(), static_cast<int>(entries.size()), 1);
EXPECT_EQ(deps.m_pollFD[0].events, POLLOUT);
}
TEST(ArchNetworkBSDTests, pollSocket_nullSocket_unblockPipeAppended) {
NiceMock<MockDeps> deps;
ArchNetworkBSD networkBSD(deps);
PollEntries entries{{nullptr, 0, 0}};
networkBSD.pollSocket(entries.data(), static_cast<int>(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<MockDeps> 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<int>(entries.size()), 1);
}
TEST(ArchNetworkBSDTests, pollSocket_interruptSystemCall_testCancelThread) {
NiceMock<MockDeps> 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<int>(entries.size()), 1);
}
TEST(ArchNetworkBSDTests, isAnyAddr_goodAddress_returnsTrue) {
MockDeps deps;
ArchNetworkBSD networkBSD(deps);
std::unique_ptr<ArchNetAddressImpl> 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<ArchNetAddressImpl> 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);
}