Files
bitcoin/src/test/sock_tests.cpp
Ava Chow 52c3381fa8 Merge bitcoin/bitcoin#33506: test: sock: Enable all socket tests on Windows
9316d96240 test: sock: Enable socket pair tests on Windows (David Gumberg)

Pull request description:

  Some `class Sock`  tests were previously disabled because Windows lacks [`socketpair(2)`](https://man7.org/linux/man-pages/man2/socketpair.2.html) which is used as a helper function in the socket tests, but is not strictly necessary or related to the `Socket` class under testing. This PR adds a `CreateSocketPair()` helper which creates a sender socket and receiver socket with a TCP connection, enabling these test cases for Windows. This also enables future tests that require more granular control over sockets than what `socketpair()` allows for, like using `setsockopt()` before connecting a socket.

  This change is generally an improvement, but is also broken out of a [branch](github.com/davidgumberg/bitcoin/tree/2025-09-02-0xB10C-prefill-rebase) that does compact block prefilling up to the available bytes in the connection's current TCP window (see [delving post](https://delvingbitcoin.org/t/stats-on-compact-block-reconstructions/1052/34)). Creating connected socket pairs is useful for added tests in that branch that validate querying the current TCP window state, and without this change those tests don't run on Windows.

ACKs for top commit:
  achow101:
    ACK 9316d96240
  sedited:
    ACK 9316d96240
  w0xlt:
    reACK 9316d96240

Tree-SHA512: 23ad7070555bb934b0eb18d114e918bd2d50657d20d2221d8c98cd0e558e27e32e5ef89e8074c108a90d7309e539318d23cea43d2ad8eb10e635b114f5f1a87d
2026-04-01 11:22:40 -07:00

185 lines
5.3 KiB
C++

// Copyright (c) 2021-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <common/system.h>
#include <compat/compat.h>
#include <test/util/common.h>
#include <test/util/setup_common.h>
#include <util/sock.h>
#include <util/threadinterrupt.h>
#include <boost/test/unit_test.hpp>
#include <cassert>
#include <thread>
using namespace std::chrono_literals;
BOOST_FIXTURE_TEST_SUITE(sock_tests, BasicTestingSetup)
static bool SocketIsClosed(const SOCKET& s)
{
// Notice that if another thread is running and creates its own socket after `s` has been
// closed, it may be assigned the same file descriptor number. In this case, our test will
// wrongly pretend that the socket is not closed.
int type;
socklen_t len = sizeof(type);
return getsockopt(s, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&type), &len) == SOCKET_ERROR;
}
static SOCKET CreateSocket()
{
const SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
BOOST_REQUIRE(s != static_cast<SOCKET>(SOCKET_ERROR));
return s;
}
BOOST_AUTO_TEST_CASE(constructor_and_destructor)
{
const SOCKET s = CreateSocket();
Sock* sock = new Sock(s);
BOOST_CHECK(*sock == s);
BOOST_CHECK(!SocketIsClosed(s));
delete sock;
BOOST_CHECK(SocketIsClosed(s));
}
BOOST_AUTO_TEST_CASE(move_constructor)
{
const SOCKET s = CreateSocket();
Sock* sock1 = new Sock(s);
Sock* sock2 = new Sock(std::move(*sock1));
delete sock1;
BOOST_CHECK(!SocketIsClosed(s));
BOOST_CHECK(*sock2 == s);
delete sock2;
BOOST_CHECK(SocketIsClosed(s));
}
BOOST_AUTO_TEST_CASE(move_assignment)
{
const SOCKET s1 = CreateSocket();
const SOCKET s2 = CreateSocket();
Sock* sock1 = new Sock(s1);
Sock* sock2 = new Sock(s2);
BOOST_CHECK(!SocketIsClosed(s1));
BOOST_CHECK(!SocketIsClosed(s2));
*sock2 = std::move(*sock1);
BOOST_CHECK(!SocketIsClosed(s1));
BOOST_CHECK(SocketIsClosed(s2));
BOOST_CHECK(*sock2 == s1);
delete sock1;
BOOST_CHECK(!SocketIsClosed(s1));
BOOST_CHECK(SocketIsClosed(s2));
BOOST_CHECK(*sock2 == s1);
delete sock2;
BOOST_CHECK(SocketIsClosed(s1));
BOOST_CHECK(SocketIsClosed(s2));
}
struct TcpSocketPair {
Sock sender;
Sock receiver;
TcpSocketPair()
: sender{Sock{CreateSocket()}},
receiver{Sock{CreateSocket()}}
{
connect_pair();
}
TcpSocketPair(const TcpSocketPair&) = delete;
TcpSocketPair& operator= (const TcpSocketPair&) = delete;
TcpSocketPair(TcpSocketPair&&) = default;
TcpSocketPair& operator= (TcpSocketPair&&) = default;
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)
{
TcpSocketPair socks{};
socks.send_and_receive();
// Sockets are still connected after being moved.
TcpSocketPair socks_moved = std::move(socks);
socks_moved.send_and_receive();
}
BOOST_AUTO_TEST_CASE(wait)
{
TcpSocketPair socks = TcpSocketPair{};
std::thread waiter([&socks]() { (void)socks.receiver.Wait(24h, Sock::RECV); });
BOOST_REQUIRE_EQUAL(socks.sender.Send("a", 1, 0), 1);
waiter.join();
}
BOOST_AUTO_TEST_CASE(recv_until_terminator_limit)
{
constexpr auto timeout = 1min; // High enough so that it is never hit.
CThreadInterrupt interrupt;
TcpSocketPair socks = TcpSocketPair{};
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)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(socks.sender.SendComplete("1234567", timeout, interrupt));
BOOST_REQUIRE_NO_THROW(socks.sender.SendComplete("89a\n", timeout, interrupt));
receiver.join();
}
BOOST_AUTO_TEST_SUITE_END()