Source
stdlib/tests/socket_test.cpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
#include "socket.hpp"5
#include <cstddef>6
#include <chrono>7
#include <string>8
#include <thread>9
#include <utility>11
#if !defined(_WIN32)12
#include <netinet/in.h>13
#include <netinet/tcp.h>14
#include <sys/socket.h>15
#endif17
#include <gtest/gtest.h>19
namespace sk = cheatah::socket;21
// A real loopback round-trip: a listener accepts one client, reads its request,22
// and replies; a client thread connects, sends, and reads the reply. Exercises23
// tcp_listen / local_port / accept / recv / sendall / tcp_connect / send / close.24
TEST(CheatahSocket, Loopback) {25
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 4);26
ASSERT_GE(lfd, 0) << sk::last_error();27
const long long port = sk::local_port(lfd);28
ASSERT_GT(port, 0);30
std::string client_reply;31
std::thread client([&] {32
const long long cfd = sk::tcp_connect("127.0.0.1", port);33
ASSERT_GE(cfd, 0);34
ASSERT_EQ(sk::sendall(cfd, "ping"), 0);35
client_reply = sk::recv(cfd, 64);36
sk::close(cfd);37
});39
const long long conn = sk::accept(lfd);40
ASSERT_GE(conn, 0) << sk::last_error();41
EXPECT_EQ(sk::recv(conn, 64), "ping");42
EXPECT_EQ(sk::sendall(conn, "pong"), 0);43
sk::close(conn);45
client.join();46
EXPECT_EQ(client_reply, "pong");47
sk::close(lfd);48
}50
// A larger, binary-ish payload to drive send()/sendall() through multiple writes51
// and confirm byte-exactness (including an embedded NUL).52
TEST(CheatahSocket, Sendall) {53
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);54
ASSERT_GE(lfd, 0);55
const long long port = sk::local_port(lfd);57
std::string payload(8000, 'x');58
payload[100] = '\0';59
payload[7999] = 'Z';61
std::thread client([&] {62
const long long cfd = sk::tcp_connect("127.0.0.1", port);63
ASSERT_GE(cfd, 0);64
EXPECT_GT(sk::send(cfd, "hi"), 0); // single send()65
EXPECT_EQ(sk::sendall(cfd, payload), 0);66
sk::close(cfd);67
});69
const long long conn = sk::accept(lfd);70
ASSERT_GE(conn, 0);71
std::string got;72
while (got.size() < payload.size() + 2) {73
const std::string chunk = sk::recv(conn, 4096);74
if (chunk.empty()) break;75
got += chunk;76
}77
EXPECT_EQ(got, "hi" + payload);78
sk::close(conn);79
client.join();80
sk::close(lfd);81
}83
#if !defined(_WIN32)84
// THE "it's the default for every user" guarantee: every connected stream socket — client AND85
// accepted server side — comes tuned for throughput (TCP_NODELAY on, an enlarged SO_RCVBUF)86
// WITHOUT any caller opt-in. This is the regression guard that a future refactor can't silently87
// drop the tuning that turns a 0.2 MB/s crawl into a full-speed download.88
TEST(CheatahSocket, ConnectedSocketsAreTunedByDefault) {89
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);90
ASSERT_GE(lfd, 0) << sk::last_error();91
const long long port = sk::local_port(lfd);93
long long cfd = -1;94
std::thread client([&] { cfd = sk::tcp_connect("127.0.0.1", port); });95
const long long conn = sk::accept(lfd);96
client.join();97
ASSERT_GE(cfd, 0) << sk::last_error();98
ASSERT_GE(conn, 0) << sk::last_error();100
const auto nodelay = [](long long fd) {101
int v = 0;102
socklen_t len = sizeof(v);103
EXPECT_EQ(::getsockopt(static_cast<int>(fd), IPPROTO_TCP, TCP_NODELAY, &v, &len), 0);104
return v;105
};106
const auto rcvbuf = [](long long fd) {107
int v = 0;108
socklen_t len = sizeof(v);109
EXPECT_EQ(::getsockopt(static_cast<int>(fd), SOL_SOCKET, SO_RCVBUF, &v, &len), 0);110
return v; // Linux reports 2× the requested value111
};112
// Nagle is OFF on both ends.113
EXPECT_NE(nodelay(cfd), 0) << "client socket left Nagle on";114
EXPECT_NE(nodelay(conn), 0) << "accepted socket left Nagle on";115
// The receive buffer is well above the kernel's stock default (131072 here) — proof the116
// window was opened. Floor chosen below the requested 4 MiB to tolerate rmem_max clamping.117
EXPECT_GE(rcvbuf(cfd), 512 * 1024) << "client SO_RCVBUF not enlarged";118
EXPECT_GE(rcvbuf(conn), 512 * 1024) << "accepted SO_RCVBUF not enlarged";120
sk::close(cfd);121
sk::close(conn);122
sk::close(lfd);123
}125
// A multi-megabyte loopback transfer read back byte-for-byte, and fast — the socket-layer126
// throughput regression guard. If a change reverted the tuning or crippled recv, a several-MB127
// loopback transfer would still be correct but this asserts it also stays quick.128
TEST(CheatahSocket, BulkTransferIsCorrectAndPrompt) {129
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);130
ASSERT_GE(lfd, 0);131
const long long port = sk::local_port(lfd);133
// 8 MiB with a position-dependent pattern so any misorder/truncation is caught.134
std::string payload(std::size_t{8} * 1024 * 1024, '\0');135
for (std::size_t i = 0; i < payload.size(); ++i) {136
payload[i] = static_cast<char>((i * 1103515245u + 12345u) >> 16);137
}139
std::thread server([&] {140
const long long conn = sk::accept(lfd);141
ASSERT_GE(conn, 0);142
EXPECT_EQ(sk::sendall(conn, payload), 0);143
sk::close(conn);144
});146
const auto t0 = std::chrono::steady_clock::now();147
const long long cfd = sk::tcp_connect("127.0.0.1", port);148
ASSERT_GE(cfd, 0);149
std::string got;150
got.reserve(payload.size());151
while (got.size() < payload.size()) {152
const std::string chunk = sk::recv(cfd, 65536);153
if (chunk.empty()) break;154
got += chunk;155
}156
const double secs = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();157
sk::close(cfd);158
server.join();159
sk::close(lfd);161
ASSERT_EQ(got.size(), payload.size());162
EXPECT_EQ(got, payload);163
// Loopback moves GB/s; 8 MiB in >5 s would mean the recv path regressed hard. Generous so CI164
// load never flakes it.165
EXPECT_LT(secs, 5.0) << "8 MiB loopback took " << secs << "s — recv path regressed";166
}167
#endif // !_WIN32169
// The low-level BSD path: socket -> set_reuseaddr -> bind -> listen -> local_port.170
TEST(CheatahSocket, ListenLowLevel) {171
const long long fd = sk::socket();172
ASSERT_GE(fd, 0);173
EXPECT_EQ(sk::set_reuseaddr(fd), 0);174
EXPECT_EQ(sk::bind(fd, "127.0.0.1", 0), 0);175
EXPECT_EQ(sk::listen(fd, 1), 0);176
EXPECT_GT(sk::local_port(fd), 0);177
EXPECT_EQ(sk::close(fd), 0);178
}180
// Connecting to a port nobody is listening on fails, and last_error() is set.181
// A datagram round trip on loopback: a bound receiver, an unbound sender, the sender's address182
// reported back, a receive window that expires empty, and a packet larger than the buffer truncated183
// — every property the datagram contract promises, none of the stream's.184
TEST(CheatahSocket, UdpLoopback) {185
const long long rx = sk::udp_socket();186
ASSERT_GE(rx, 0) << sk::last_error();187
ASSERT_EQ(sk::bind(rx, "127.0.0.1", 0), 0) << sk::last_error();188
const long long port = sk::local_port(rx);189
ASSERT_GT(port, 0);190
ASSERT_EQ(sk::set_timeout(rx, 500), 0);191
const long long tx = sk::udp_socket();192
ASSERT_GE(tx, 0);193
EXPECT_EQ(sk::sendto(tx, "127.0.0.1", port, "presence 1"), 10);194
std::string from;195
long long from_port = 0;196
EXPECT_EQ(sk::recvfrom(rx, 64, from, from_port), "presence 1");197
EXPECT_EQ(from, "127.0.0.1");198
EXPECT_GT(from_port, 0);199
// Nothing sent: the window passes and the receive is empty, not an error.200
EXPECT_EQ(sk::recvfrom(rx, 64, from, from_port), "");201
EXPECT_EQ(from_port, 0);202
// A packet larger than the buffer is truncated to it — the datagram contract, stated in the doc.203
EXPECT_EQ(sk::sendto(tx, "127.0.0.1", port, std::string(100, 'x')), 100);204
EXPECT_EQ(sk::recvfrom(rx, 16, from, from_port).size(), 16u);205
EXPECT_EQ(sk::close(tx), 0);206
EXPECT_EQ(sk::close(rx), 0);207
}209
TEST(CheatahSocket, ConnectRefused) {210
// Bind+listen to grab a free port, then close it so the port is free again.211
const long long probe = sk::tcp_listen("127.0.0.1", 0, 1);212
ASSERT_GE(probe, 0);213
const long long dead_port = sk::local_port(probe);214
sk::close(probe);216
EXPECT_LT(sk::tcp_connect("127.0.0.1", dead_port), 0);217
EXPECT_FALSE(sk::last_error().empty());218
}220
// Name resolution failure is reported as -1, not a crash.221
TEST(CheatahSocket, ResolveFailure) {222
const long long fd = sk::socket();223
ASSERT_GE(fd, 0);224
EXPECT_LT(sk::bind(fd, "no.such.host.invalid.", 0), 0);225
EXPECT_LT(sk::tcp_connect("no.such.host.invalid.", 80), 0);226
EXPECT_LT(sk::tcp_listen("no.such.host.invalid.", 0, 1), 0); // exercises tcp_listen's error path227
sk::close(fd);228
}230
// Operations on a bad fd return errors (and recv returns ""), never crash.231
TEST(CheatahSocket, BadFd) {232
EXPECT_EQ(sk::recv(-1, 64), "");233
EXPECT_EQ(sk::recv(5, 0), ""); // non-positive bufsize234
EXPECT_LT(sk::send(-1, "x"), 0);235
EXPECT_LT(sk::sendall(-1, "x"), 0);236
EXPECT_LT(sk::listen(-1, 1), 0);237
EXPECT_LT(sk::accept(-1), 0);238
EXPECT_LT(sk::local_port(-1), 0);239
EXPECT_LT(sk::connect(-1, "127.0.0.1", 80), 0);240
EXPECT_LT(sk::close(-1), 0);241
}243
// set_timeout (recv/send deadline) + shutdown (graceful SHUT_RDWR) on a live connection.244
// A loopback pair so both run on a real connected socket; shutdown wakes the peer's recv.245
TEST(CheatahSocket, TimeoutThenShutdown) {246
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);247
ASSERT_GE(lfd, 0) << sk::last_error();248
const long long port = sk::local_port(lfd);249
ASSERT_GT(port, 0);250
std::thread client([&] {251
const long long c = sk::tcp_connect("127.0.0.1", port);252
if (c >= 0) { sk::recv(c, 1); sk::close(c); }253
});254
const long long conn = sk::accept(lfd);255
ASSERT_GE(conn, 0) << sk::last_error();256
EXPECT_EQ(sk::set_timeout(conn, 500), 0); // positive timeout -> SO_RCVTIMEO/SO_SNDTIMEO257
EXPECT_EQ(sk::shutdown(conn), 0); // graceful SHUT_RDWR258
sk::close(conn);259
client.join();260
sk::close(lfd);261
}263
// ---- owning RAII guards (Conn / Listener): the leak-proof, `with`-friendly API ----265
// A default-constructed Conn owns nothing: closed, fd == -1, and close() reports -1.266
TEST(CheatahSocket, ConnDefaultIsClosed) {267
sk::Conn c;268
EXPECT_FALSE(c.is_open());269
EXPECT_EQ(c.fd(), -1);270
EXPECT_EQ(c.close(), -1); // nothing to close271
// A failed open() also yields a closed Conn (no fd leaked on the error path).272
sk::Listener probe = sk::serve("127.0.0.1", 0, 1);273
const long long dead = probe.local_port();274
probe.close();275
sk::Conn bad = sk::open("127.0.0.1", dead);276
EXPECT_FALSE(bad.is_open());277
}279
// Likewise for a default-constructed Listener.280
TEST(CheatahSocket, ListenerDefaultIsClosed) {281
sk::Listener l;282
EXPECT_FALSE(l.is_open());283
EXPECT_EQ(l.fd(), -1);284
EXPECT_EQ(l.close(), -1);285
}287
// Explicit close() releases the fd and is idempotent; the destructor then does nothing.288
TEST(CheatahSocket, ConnGuardClosesOnScopeExit) {289
sk::Listener server = sk::serve("127.0.0.1", 0, 1);290
ASSERT_TRUE(server.is_open()) << sk::last_error();291
sk::Conn c = sk::open("127.0.0.1", server.local_port());292
ASSERT_TRUE(c.is_open()) << sk::last_error();293
EXPECT_GE(c.fd(), 0);294
EXPECT_EQ(c.close(), 0); // explicit close295
EXPECT_FALSE(c.is_open());296
EXPECT_EQ(c.close(), -1); // idempotent297
// `server` is closed by its destructor here — Valgrind confirms no fd leak.298
}300
// Move transfers fd ownership; the moved-from guard is left closed (never double-closed).301
TEST(CheatahSocket, ConnMoveTransfersOwnership) {302
sk::Listener server = sk::serve("127.0.0.1", 0, 2);303
ASSERT_TRUE(server.is_open());304
sk::Conn a = sk::open("127.0.0.1", server.local_port());305
ASSERT_TRUE(a.is_open());306
const long long fd = a.fd();307
sk::Conn b(std::move(a)); // move-construct308
EXPECT_FALSE(a.is_open()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move): moved-from state is the assertion309
EXPECT_EQ(b.fd(), fd);310
// Move-assign onto an already-open guard closes the overwritten fd first.311
sk::Conn d = sk::open("127.0.0.1", server.local_port());312
ASSERT_TRUE(d.is_open());313
d = std::move(b);314
EXPECT_FALSE(b.is_open()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move): moved-from state is the assertion315
EXPECT_EQ(d.fd(), fd);316
EXPECT_EQ(d.close(), 0);317
}319
// Full loopback through the guards: serve()/open()/accept() + Conn send/sendall/recv/320
// set_timeout/local_port/shutdown/close — the whole owning API on a live connection.321
TEST(CheatahSocket, ConnLoopback) {322
sk::Listener server = sk::serve("127.0.0.1", 0, 4);323
ASSERT_TRUE(server.is_open()) << sk::last_error();324
EXPECT_GE(server.fd(), 0);325
const long long port = server.local_port();326
ASSERT_GT(port, 0);328
std::string client_reply;329
std::thread client([&] {330
sk::Conn c = sk::open("127.0.0.1", port);331
ASSERT_TRUE(c.is_open());332
EXPECT_EQ(c.set_timeout(1000), 0);333
EXPECT_GT(c.local_port(), 0);334
EXPECT_GT(c.send("pi"), 0); // single send()335
EXPECT_EQ(c.sendall("ng"), 0); // looped sendall()336
client_reply = c.recv(64);337
// c is closed by its destructor at thread-scope exit.338
});340
sk::Conn conn = server.accept();341
ASSERT_TRUE(conn.is_open()) << sk::last_error();342
std::string got;343
while (got.size() < 4) {344
const std::string chunk = conn.recv(64);345
if (chunk.empty()) break;346
got += chunk;347
}348
EXPECT_EQ(got, "ping");349
EXPECT_EQ(conn.sendall("pong"), 0);350
EXPECT_EQ(conn.shutdown(), 0); // graceful half-close351
EXPECT_EQ(conn.close(), 0);353
client.join();354
EXPECT_EQ(client_reply, "pong");355
// `server` is closed by its destructor.356
}358
// Listener move semantics + idempotent close.359
TEST(CheatahSocket, ListenerLoopback) {360
sk::Listener a = sk::serve("127.0.0.1", 0, 1);361
ASSERT_TRUE(a.is_open()) << sk::last_error();362
const long long port = a.local_port();363
ASSERT_GT(port, 0);364
sk::Listener b(std::move(a)); // move-construct365
EXPECT_FALSE(a.is_open()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move): moved-from state is the assertion366
EXPECT_EQ(b.local_port(), port);367
sk::Listener c;368
c = std::move(b); // move-assign onto a closed listener369
EXPECT_FALSE(b.is_open()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move): moved-from state is the assertion370
EXPECT_GE(c.fd(), 0);371
EXPECT_EQ(c.close(), 0);372
EXPECT_EQ(c.close(), -1); // idempotent373
}