test: sock: Enable socket pair tests on Windows

Adds a helper struct `socket_pair` for constructing and connecting TCP
sockets, this replaces socketpair() which is not available on Windows.
Making the socket pair TCP sockets instead of Unix sockets, and
separating socket creation from socket connection also enables more
detailed tests to be added in the future.
This commit is contained in:
David Gumberg
2025-10-01 11:52:11 -07:00
parent acc7f2a433
commit 9316d96240

View File

@@ -81,61 +81,75 @@ BOOST_AUTO_TEST_CASE(move_assignment)
BOOST_CHECK(SocketIsClosed(s2));
}
#ifndef WIN32 // Windows does not have socketpair(2).
struct TcpSocketPair {
Sock sender;
Sock receiver;
static void CreateSocketPair(int s[2])
{
BOOST_REQUIRE_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, s), 0);
}
TcpSocketPair()
: sender{Sock{CreateSocket()}},
receiver{Sock{CreateSocket()}}
{
connect_pair();
}
static void SendAndRecvMessage(const Sock& sender, const Sock& receiver)
{
const char* msg = "abcd";
constexpr ssize_t msg_len = 4;
char recv_buf[10];
TcpSocketPair(const TcpSocketPair&) = delete;
TcpSocketPair& operator= (const TcpSocketPair&) = delete;
TcpSocketPair(TcpSocketPair&&) = default;
TcpSocketPair& operator= (TcpSocketPair&&) = default;
BOOST_CHECK_EQUAL(sender.Send(msg, msg_len, 0), msg_len);
BOOST_CHECK_EQUAL(receiver.Recv(recv_buf, sizeof(recv_buf), 0), msg_len);
BOOST_CHECK_EQUAL(strncmp(msg, recv_buf, msg_len), 0);
}
void connect_pair()
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
BOOST_REQUIRE_EQUAL(receiver.Bind(reinterpret_cast<sockaddr*>(&addr), sizeof(addr)), 0);
BOOST_REQUIRE_EQUAL(receiver.Listen(1), 0);
// Get the address of the listener.
sockaddr_in bound{};
socklen_t blen = sizeof(bound);
BOOST_REQUIRE_EQUAL(receiver.GetSockName(reinterpret_cast<sockaddr*>(&bound), &blen), 0);
BOOST_REQUIRE_EQUAL(blen, sizeof(bound));
BOOST_REQUIRE_EQUAL(sender.Connect(reinterpret_cast<sockaddr*>(&bound), sizeof(bound)), 0);
std::unique_ptr<Sock> accepted = receiver.Accept(nullptr, nullptr);
BOOST_REQUIRE(accepted != nullptr);
receiver = std::move(*accepted);
}
void send_and_receive()
{
const char* msg = "abcd";
constexpr ssize_t msg_len = 4;
char recv_buf[10];
BOOST_CHECK_EQUAL(sender.Send(msg, msg_len, 0), msg_len);
BOOST_CHECK_EQUAL(receiver.Recv(recv_buf, sizeof(recv_buf), 0), msg_len);
BOOST_CHECK_EQUAL(strncmp(msg, recv_buf, msg_len), 0);
}
};
BOOST_AUTO_TEST_CASE(send_and_receive)
{
int s[2];
CreateSocketPair(s);
TcpSocketPair socks{};
socks.send_and_receive();
Sock* sock0 = new Sock(s[0]);
Sock* sock1 = new Sock(s[1]);
SendAndRecvMessage(*sock0, *sock1);
Sock* sock0moved = new Sock(std::move(*sock0));
Sock* sock1moved = new Sock(INVALID_SOCKET);
*sock1moved = std::move(*sock1);
delete sock0;
delete sock1;
SendAndRecvMessage(*sock1moved, *sock0moved);
delete sock0moved;
delete sock1moved;
BOOST_CHECK(SocketIsClosed(s[0]));
BOOST_CHECK(SocketIsClosed(s[1]));
// Sockets are still connected after being moved.
TcpSocketPair socks_moved = std::move(socks);
socks_moved.send_and_receive();
}
BOOST_AUTO_TEST_CASE(wait)
{
int s[2];
CreateSocketPair(s);
TcpSocketPair socks = TcpSocketPair{};
Sock sock0(s[0]);
Sock sock1(s[1]);
std::thread waiter([&socks]() { (void)socks.receiver.Wait(24h, Sock::RECV); });
std::thread waiter([&sock0]() { (void)sock0.Wait(24h, Sock::RECV); });
BOOST_REQUIRE_EQUAL(sock1.Send("a", 1, 0), 1);
BOOST_REQUIRE_EQUAL(socks.sender.Send("a", 1, 0), 1);
waiter.join();
}
@@ -144,31 +158,26 @@ BOOST_AUTO_TEST_CASE(recv_until_terminator_limit)
{
constexpr auto timeout = 1min; // High enough so that it is never hit.
CThreadInterrupt interrupt;
int s[2];
CreateSocketPair(s);
Sock sock_send(s[0]);
Sock sock_recv(s[1]);
TcpSocketPair socks = TcpSocketPair{};
std::thread receiver([&sock_recv, &timeout, &interrupt]() {
std::thread receiver([&socks, &timeout, &interrupt]() {
constexpr size_t max_data{10};
bool threw_as_expected{false};
// BOOST_CHECK_EXCEPTION() writes to some variables shared with the main thread which
// creates a data race. So mimic it manually.
try {
(void)sock_recv.RecvUntilTerminator('\n', timeout, interrupt, max_data);
(void)socks.receiver.RecvUntilTerminator('\n', timeout, interrupt, max_data);
} catch (const std::runtime_error& e) {
threw_as_expected = HasReason("too many bytes without a terminator")(e);
}
assert(threw_as_expected);
});
BOOST_REQUIRE_NO_THROW(sock_send.SendComplete("1234567", timeout, interrupt));
BOOST_REQUIRE_NO_THROW(sock_send.SendComplete("89a\n", timeout, interrupt));
BOOST_REQUIRE_NO_THROW(socks.sender.SendComplete("1234567", timeout, interrupt));
BOOST_REQUIRE_NO_THROW(socks.sender.SendComplete("89a\n", timeout, interrupt));
receiver.join();
}
#endif /* WIN32 */
BOOST_AUTO_TEST_SUITE_END()