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 <chrono>6
#include <string>7
#include <thread>8
#include <utility>10
#if !defined(_WIN32)11
#include <netinet/in.h>12
#include <netinet/tcp.h>13
#include <sys/socket.h>14
#endif16
#include <gtest/gtest.h>18
namespace sk = cheatah::socket;20
// A real loopback round-trip: a listener accepts one client, reads its request,21
// and replies; a client thread connects, sends, and reads the reply. Exercises22
// tcp_listen / local_port / accept / recv / sendall / tcp_connect / send / close.23
TEST(CheatahSocket, Loopback) {24
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 4);25
ASSERT_GE(lfd, 0) << sk::last_error();26
const long long port = sk::local_port(lfd);27
ASSERT_GT(port, 0);29
std::string client_reply;30
std::thread client([&] {31
const long long cfd = sk::tcp_connect("127.0.0.1", port);32
ASSERT_GE(cfd, 0);33
ASSERT_EQ(sk::sendall(cfd, "ping"), 0);34
client_reply = sk::recv(cfd, 64);35
sk::close(cfd);36
});38
const long long conn = sk::accept(lfd);39
ASSERT_GE(conn, 0) << sk::last_error();40
EXPECT_EQ(sk::recv(conn, 64), "ping");41
EXPECT_EQ(sk::sendall(conn, "pong"), 0);42
sk::close(conn);44
client.join();45
EXPECT_EQ(client_reply, "pong");46
sk::close(lfd);47
}49
// A larger, binary-ish payload to drive send()/sendall() through multiple writes50
// and confirm byte-exactness (including an embedded NUL).51
TEST(CheatahSocket, Sendall) {52
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);53
ASSERT_GE(lfd, 0);54
const long long port = sk::local_port(lfd);56
std::string payload(8000, 'x');57
payload[100] = '\0';58
payload[7999] = 'Z';60
std::thread client([&] {61
const long long cfd = sk::tcp_connect("127.0.0.1", port);62
ASSERT_GE(cfd, 0);63
EXPECT_GT(sk::send(cfd, "hi"), 0); // single send()64
EXPECT_EQ(sk::sendall(cfd, payload), 0);65
sk::close(cfd);66
});68
const long long conn = sk::accept(lfd);69
ASSERT_GE(conn, 0);70
std::string got;71
while (got.size() < payload.size() + 2) {72
const std::string chunk = sk::recv(conn, 4096);73
if (chunk.empty()) break;74
got += chunk;75
}76
EXPECT_EQ(got, "hi" + payload);77
sk::close(conn);78
client.join();79
sk::close(lfd);80
}82
#if !defined(_WIN32)83
// THE "it's the default for every user" guarantee: every connected stream socket — client AND84
// accepted server side — comes tuned for throughput (TCP_NODELAY on, an enlarged SO_RCVBUF)85
// WITHOUT any caller opt-in. This is the regression guard that a future refactor can't silently86
// drop the tuning that turns a 0.2 MB/s crawl into a full-speed download.87
TEST(CheatahSocket, ConnectedSocketsAreTunedByDefault) {88
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);89
ASSERT_GE(lfd, 0) << sk::last_error();90
const long long port = sk::local_port(lfd);92
long long cfd = -1;93
std::thread client([&] { cfd = sk::tcp_connect("127.0.0.1", port); });94
const long long conn = sk::accept(lfd);95
client.join();96
ASSERT_GE(cfd, 0) << sk::last_error();97
ASSERT_GE(conn, 0) << sk::last_error();99
const auto nodelay = [](long long fd) {100
int v = 0;101
socklen_t len = sizeof(v);102
EXPECT_EQ(::getsockopt(static_cast<int>(fd), IPPROTO_TCP, TCP_NODELAY, &v, &len), 0);103
return v;104
};105
const auto rcvbuf = [](long long fd) {106
int v = 0;107
socklen_t len = sizeof(v);108
EXPECT_EQ(::getsockopt(static_cast<int>(fd), SOL_SOCKET, SO_RCVBUF, &v, &len), 0);109
return v; // Linux reports 2× the requested value110
};111
// Nagle is OFF on both ends.112
EXPECT_NE(nodelay(cfd), 0) << "client socket left Nagle on";113
EXPECT_NE(nodelay(conn), 0) << "accepted socket left Nagle on";114
// The receive buffer is well above the kernel's stock default (131072 here) — proof the115
// window was opened. Floor chosen below the requested 4 MiB to tolerate rmem_max clamping.116
EXPECT_GE(rcvbuf(cfd), 512 * 1024) << "client SO_RCVBUF not enlarged";117
EXPECT_GE(rcvbuf(conn), 512 * 1024) << "accepted SO_RCVBUF not enlarged";119
sk::close(cfd);120
sk::close(conn);121
sk::close(lfd);122
}124
// A multi-megabyte loopback transfer read back byte-for-byte, and fast — the socket-layer125
// throughput regression guard. If a change reverted the tuning or crippled recv, a several-MB126
// loopback transfer would still be correct but this asserts it also stays quick.127
TEST(CheatahSocket, BulkTransferIsCorrectAndPrompt) {128
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);129
ASSERT_GE(lfd, 0);130
const long long port = sk::local_port(lfd);132
// 8 MiB with a position-dependent pattern so any misorder/truncation is caught.133
std::string payload(8 * 1024 * 1024, '\0');134
for (std::size_t i = 0; i < payload.size(); ++i) {135
payload[i] = static_cast<char>((i * 1103515245u + 12345u) >> 16);136
}138
std::thread server([&] {139
const long long conn = sk::accept(lfd);140
ASSERT_GE(conn, 0);141
EXPECT_EQ(sk::sendall(conn, payload), 0);142
sk::close(conn);143
});145
const auto t0 = std::chrono::steady_clock::now();146
const long long cfd = sk::tcp_connect("127.0.0.1", port);147
ASSERT_GE(cfd, 0);148
std::string got;149
got.reserve(payload.size());150
while (got.size() < payload.size()) {151
const std::string chunk = sk::recv(cfd, 65536);152
if (chunk.empty()) break;153
got += chunk;154
}155
const double secs = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();156
sk::close(cfd);157
server.join();158
sk::close(lfd);160
ASSERT_EQ(got.size(), payload.size());161
EXPECT_EQ(got, payload);162
// Loopback moves GB/s; 8 MiB in >5 s would mean the recv path regressed hard. Generous so CI163
// load never flakes it.164
EXPECT_LT(secs, 5.0) << "8 MiB loopback took " << secs << "s — recv path regressed";165
}166
#endif // !_WIN32168
// The low-level BSD path: socket -> set_reuseaddr -> bind -> listen -> local_port.169
TEST(CheatahSocket, ListenLowLevel) {170
const long long fd = sk::socket();171
ASSERT_GE(fd, 0);172
EXPECT_EQ(sk::set_reuseaddr(fd), 0);173
EXPECT_EQ(sk::bind(fd, "127.0.0.1", 0), 0);174
EXPECT_EQ(sk::listen(fd, 1), 0);175
EXPECT_GT(sk::local_port(fd), 0);176
EXPECT_EQ(sk::close(fd), 0);177
}179
// Connecting to a port nobody is listening on fails, and last_error() is set.180
TEST(CheatahSocket, ConnectRefused) {181
// Bind+listen to grab a free port, then close it so the port is free again.182
const long long probe = sk::tcp_listen("127.0.0.1", 0, 1);183
ASSERT_GE(probe, 0);184
const long long dead_port = sk::local_port(probe);185
sk::close(probe);187
EXPECT_LT(sk::tcp_connect("127.0.0.1", dead_port), 0);188
EXPECT_FALSE(sk::last_error().empty());189
}191
// Name resolution failure is reported as -1, not a crash.192
TEST(CheatahSocket, ResolveFailure) {193
const long long fd = sk::socket();194
ASSERT_GE(fd, 0);195
EXPECT_LT(sk::bind(fd, "no.such.host.invalid.", 0), 0);196
EXPECT_LT(sk::tcp_connect("no.such.host.invalid.", 80), 0);197
EXPECT_LT(sk::tcp_listen("no.such.host.invalid.", 0, 1), 0); // exercises tcp_listen's error path198
sk::close(fd);199
}201
// Operations on a bad fd return errors (and recv returns ""), never crash.202
TEST(CheatahSocket, BadFd) {203
EXPECT_EQ(sk::recv(-1, 64), "");204
EXPECT_EQ(sk::recv(5, 0), ""); // non-positive bufsize205
EXPECT_LT(sk::send(-1, "x"), 0);206
EXPECT_LT(sk::sendall(-1, "x"), 0);207
EXPECT_LT(sk::listen(-1, 1), 0);208
EXPECT_LT(sk::accept(-1), 0);209
EXPECT_LT(sk::local_port(-1), 0);210
EXPECT_LT(sk::connect(-1, "127.0.0.1", 80), 0);211
EXPECT_LT(sk::close(-1), 0);212
}214
// set_timeout (recv/send deadline) + shutdown (graceful SHUT_RDWR) on a live connection.215
// A loopback pair so both run on a real connected socket; shutdown wakes the peer's recv.216
TEST(CheatahSocket, TimeoutThenShutdown) {217
const long long lfd = sk::tcp_listen("127.0.0.1", 0, 1);218
ASSERT_GE(lfd, 0) << sk::last_error();219
const long long port = sk::local_port(lfd);220
ASSERT_GT(port, 0);221
std::thread client([&] {222
const long long c = sk::tcp_connect("127.0.0.1", port);223
if (c >= 0) { sk::recv(c, 1); sk::close(c); }224
});225
const long long conn = sk::accept(lfd);226
ASSERT_GE(conn, 0) << sk::last_error();227
EXPECT_EQ(sk::set_timeout(conn, 500), 0); // positive timeout -> SO_RCVTIMEO/SO_SNDTIMEO228
EXPECT_EQ(sk::shutdown(conn), 0); // graceful SHUT_RDWR229
sk::close(conn);230
client.join();231
sk::close(lfd);232
}234
// ---- owning RAII guards (Conn / Listener): the leak-proof, `with`-friendly API ----236
// A default-constructed Conn owns nothing: closed, fd == -1, and close() reports -1.237
TEST(CheatahSocket, ConnDefaultIsClosed) {238
sk::Conn c;239
EXPECT_FALSE(c.is_open());240
EXPECT_EQ(c.fd(), -1);241
EXPECT_EQ(c.close(), -1); // nothing to close242
// A failed open() also yields a closed Conn (no fd leaked on the error path).243
sk::Listener probe = sk::serve("127.0.0.1", 0, 1);244
const long long dead = probe.local_port();245
probe.close();246
sk::Conn bad = sk::open("127.0.0.1", dead);247
EXPECT_FALSE(bad.is_open());248
}250
// Likewise for a default-constructed Listener.251
TEST(CheatahSocket, ListenerDefaultIsClosed) {252
sk::Listener l;253
EXPECT_FALSE(l.is_open());254
EXPECT_EQ(l.fd(), -1);255
EXPECT_EQ(l.close(), -1);256
}258
// Explicit close() releases the fd and is idempotent; the destructor then does nothing.259
TEST(CheatahSocket, ConnGuardClosesOnScopeExit) {260
sk::Listener server = sk::serve("127.0.0.1", 0, 1);261
ASSERT_TRUE(server.is_open()) << sk::last_error();262
sk::Conn c = sk::open("127.0.0.1", server.local_port());263
ASSERT_TRUE(c.is_open()) << sk::last_error();264
EXPECT_GE(c.fd(), 0);265
EXPECT_EQ(c.close(), 0); // explicit close266
EXPECT_FALSE(c.is_open());267
EXPECT_EQ(c.close(), -1); // idempotent268
// `server` is closed by its destructor here — Valgrind confirms no fd leak.269
}271
// Move transfers fd ownership; the moved-from guard is left closed (never double-closed).272
TEST(CheatahSocket, ConnMoveTransfersOwnership) {273
sk::Listener server = sk::serve("127.0.0.1", 0, 2);274
ASSERT_TRUE(server.is_open());275
sk::Conn a = sk::open("127.0.0.1", server.local_port());276
ASSERT_TRUE(a.is_open());277
const long long fd = a.fd();278
sk::Conn b(std::move(a)); // move-construct279
EXPECT_FALSE(a.is_open());280
EXPECT_EQ(b.fd(), fd);281
// Move-assign onto an already-open guard closes the overwritten fd first.282
sk::Conn d = sk::open("127.0.0.1", server.local_port());283
ASSERT_TRUE(d.is_open());284
d = std::move(b);285
EXPECT_FALSE(b.is_open());286
EXPECT_EQ(d.fd(), fd);287
EXPECT_EQ(d.close(), 0);288
}290
// Full loopback through the guards: serve()/open()/accept() + Conn send/sendall/recv/291
// set_timeout/local_port/shutdown/close — the whole owning API on a live connection.292
TEST(CheatahSocket, ConnLoopback) {293
sk::Listener server = sk::serve("127.0.0.1", 0, 4);294
ASSERT_TRUE(server.is_open()) << sk::last_error();295
EXPECT_GE(server.fd(), 0);296
const long long port = server.local_port();297
ASSERT_GT(port, 0);299
std::string client_reply;300
std::thread client([&] {301
sk::Conn c = sk::open("127.0.0.1", port);302
ASSERT_TRUE(c.is_open());303
EXPECT_EQ(c.set_timeout(1000), 0);304
EXPECT_GT(c.local_port(), 0);305
EXPECT_GT(c.send("pi"), 0); // single send()306
EXPECT_EQ(c.sendall("ng"), 0); // looped sendall()307
client_reply = c.recv(64);308
// c is closed by its destructor at thread-scope exit.309
});311
sk::Conn conn = server.accept();312
ASSERT_TRUE(conn.is_open()) << sk::last_error();313
std::string got;314
while (got.size() < 4) {315
const std::string chunk = conn.recv(64);316
if (chunk.empty()) break;317
got += chunk;318
}319
EXPECT_EQ(got, "ping");320
EXPECT_EQ(conn.sendall("pong"), 0);321
EXPECT_EQ(conn.shutdown(), 0); // graceful half-close322
EXPECT_EQ(conn.close(), 0);324
client.join();325
EXPECT_EQ(client_reply, "pong");326
// `server` is closed by its destructor.327
}329
// Listener move semantics + idempotent close.330
TEST(CheatahSocket, ListenerLoopback) {331
sk::Listener a = sk::serve("127.0.0.1", 0, 1);332
ASSERT_TRUE(a.is_open()) << sk::last_error();333
const long long port = a.local_port();334
ASSERT_GT(port, 0);335
sk::Listener b(std::move(a)); // move-construct336
EXPECT_FALSE(a.is_open());337
EXPECT_EQ(b.local_port(), port);338
sk::Listener c;339
c = std::move(b); // move-assign onto a closed listener340
EXPECT_FALSE(b.is_open());341
EXPECT_GE(c.fd(), 0);342
EXPECT_EQ(c.close(), 0);343
EXPECT_EQ(c.close(), -1); // idempotent344
}