From 9316d96240ff1bd4c98a823022655c44a04d1b20 Mon Sep 17 00:00:00 2001 From: David Gumberg Date: Wed, 1 Oct 2025 11:52:11 -0700 Subject: [PATCH] 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. --- src/test/sock_tests.cpp | 113 ++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp index 06d9f7d3ef6..56fdfaaf908 100644 --- a/src/test/sock_tests.cpp +++ b/src/test/sock_tests.cpp @@ -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(&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(&bound), &blen), 0); + BOOST_REQUIRE_EQUAL(blen, sizeof(bound)); + + BOOST_REQUIRE_EQUAL(sender.Connect(reinterpret_cast(&bound), sizeof(bound)), 0); + + std::unique_ptr 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()