Better error message on socket (#1477)
Build and Deploy on local RHEL9 / build (push) Successful in 2m1s
Build on RHEL9 docker image / build (push) Successful in 4m31s
Build and Deploy on local RHEL8 / build (push) Successful in 5m6s
Build on RHEL8 docker image / build (push) Successful in 5m34s
Run Simulator Tests on local RHEL9 / build (push) Successful in 18m38s
Run Simulator Tests on local RHEL8 / build (push) Successful in 22m6s

* checking errno

* improved socket error message

* improved message

* decode error code

* format

* clearer msg

* report write errors, handle SIGPIPE

* retry and cleaned up timeout

* removed read/write in DataSocket
This commit is contained in:
Erik Fröjdh
2026-06-30 15:29:58 +02:00
committed by GitHub
co-authored by GitHub
parent 4395e7b5f6
commit ffec82076d
9 changed files with 475 additions and 62 deletions
+1
View File
@@ -21,6 +21,7 @@ class ClientSocket : public DataSocket {
private:
void readReply(int &ret, void *retval, size_t retval_size);
[[noreturn]] void throwError(const std::string &msg) const;
struct sockaddr_in serverAddr {};
std::string socketType;
};
+2 -2
View File
@@ -77,8 +77,6 @@ class DataSocket {
std::string Receive(size_t length);
int read(void *buffer, size_t size);
int write(void *buffer, size_t size);
int setTimeOut(int t_seconds);
int setReceiveTimeout(int us);
void close();
@@ -88,6 +86,8 @@ class DataSocket {
private:
int sockfd_ = -1;
int fnum_{0};
std::string_view errno_name(int e);
};
}; // namespace sls
+2
View File
@@ -96,4 +96,6 @@ bool replace_first(std::string *s, const std::string &substr,
std::pair<std::string, uint16_t> ParseHostPort(const std::string &s);
std::string to_lower(const std::string &s);
} // namespace sls
+26 -25
View File
@@ -5,9 +5,11 @@
#include "sls/sls_detector_defs.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/sls_detector_funcs.h"
#include "sls/string_utils.h"
#include <arpa/inet.h>
#include <cassert>
#include <cstring>
#include <fmt/format.h>
#include <iostream>
#include <stdexcept>
#include <unistd.h>
@@ -24,9 +26,10 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host,
hints.ai_flags |= AI_CANONNAME;
if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0) {
std::string msg = "ClientSocket cannot decode host:" + host +
" on port " + std::to_string(port) + "\n";
throw SocketError(msg);
auto msg = fmt::format("Cannot resolve {} hostname: '{}'", to_lower(socketType), host);
throwError(msg);
}
// TODO! Erik, results could have multiple entries do we need to loop
@@ -40,10 +43,11 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host,
if (::connect(getSocketId(), (struct sockaddr *)&serverAddr,
sizeof(serverAddr)) != 0) {
freeaddrinfo(result);
std::string msg = "ClientSocket: Cannot connect to " + socketType +
":" + host + " on port " + std::to_string(port) +
"\n";
throw SocketError(msg);
auto msg = fmt::format(
"Cannot connect to {} on {}:{}\n",
to_lower(socketType), host, port);
throwError(msg);
}
freeaddrinfo(result);
}
@@ -54,10 +58,18 @@ ClientSocket::ClientSocket(std::string sType, struct sockaddr_in addr)
if (::connect(getSocketId(), (struct sockaddr *)&addr, sizeof(addr)) != 0) {
char address[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, address, INET_ADDRSTRLEN);
std::string msg = "ClientSocket: Cannot connect to " + socketType +
":" + address + " on port " +
std::to_string(addr.sin_port) + "\n";
throw SocketError(msg);
auto msg = fmt::format("Cannot connect to {} on {}:{}", to_lower(socketType), address, addr.sin_port);
throwError(msg);
}
}
void ClientSocket::throwError(const std::string &msg) const {
if (socketType == "Receiver") {
throw ReceiverError(msg);
} else if (socketType == "Detector") {
throw DetectorError(msg);
} else {
throw GuiError(msg);
}
}
@@ -80,26 +92,15 @@ void ClientSocket::readReply(int &ret, void *retval, size_t retval_size) {
std::string mess = readErrorMessage();
// Do we need to know hostname here?
// In that case save it???
if (socketType == "Receiver") {
throw ReceiverError("Receiver returned: " + std::string(mess));
} else if (socketType == "Detector") {
throw DetectorError("Detector returned: " + std::string(mess));
} else {
throw GuiError(mess);
}
throwError(socketType + " returned: " + mess);
}
// get retval
Receive(retval, retval_size);
}
// debugging
catch (SocketError &e) {
if (socketType == "Receiver") {
throw ReceiverError("Receiver returned: " + std::string(e.what()));
} else if (socketType == "Detector") {
throw DetectorError("Detector returned: " + std::string(e.what()));
} else {
throw GuiError(e.what());
}
auto msg = fmt::format("While reading reply from {} {}", to_lower(socketType), e.what());
throwError(msg);
}
}
+116 -22
View File
@@ -1,12 +1,14 @@
// SPDX-License-Identifier: LGPL-3.0-or-other
// Copyright (C) 2021 Contributors to the SLS Detector Package
#include "sls/DataSocket.h"
#include "sls/Timer.h"
#include "sls/logger.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/sls_detector_funcs.h"
#include <algorithm>
#include <arpa/inet.h>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <iostream>
@@ -21,6 +23,14 @@ namespace sls {
DataSocket::DataSocket(int socketId) : sockfd_(socketId) {
int value = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
#ifdef SO_NOSIGPIPE
// macOS/BSD: suppress SIGPIPE when sending to a peer that closed the
// connection, so a failed send returns an error instead of killing the
// process. On Linux we instead pass MSG_NOSIGNAL to send() (see Send()).
int nosigpipe = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe,
sizeof(nosigpipe));
#endif
}
DataSocket::~DataSocket() {
@@ -36,6 +46,7 @@ DataSocket::~DataSocket() {
void DataSocket::swap(DataSocket &other) noexcept {
std::swap(sockfd_, other.sockfd_);
std::swap(fnum_, other.fnum_);
}
DataSocket::DataSocket(DataSocket &&move) noexcept { move.swap(*this); }
@@ -48,23 +59,34 @@ void DataSocket::setFnum(const int fnum) { fnum_ = fnum; }
int DataSocket::Receive(void *buffer, size_t size) {
// TODO!(Erik) Add sleep? how many reties?
int bytes_expected = static_cast<int>(size); // signed size
int bytes_read = 0;
ssize_t bytes_expected = static_cast<ssize_t>(size);
ssize_t bytes_read = 0;
ssize_t this_read = 0; // last read result, kept for diagnostics
Timer timer;
while (bytes_read < bytes_expected) {
auto this_read =
this_read =
::read(getSocketId(), reinterpret_cast<char *>(buffer) + bytes_read,
bytes_expected - bytes_read);
if (this_read < 0 && errno == EINTR)
continue; // interrupted by a signal, retry
if (this_read <= 0)
break;
bytes_read += this_read;
}
if (bytes_read == bytes_expected) {
return bytes_read;
return static_cast<int>(bytes_read);
} else {
int err = errno; // capture before any other call can clobber it
std::ostringstream ss;
ss << "TCP socket read " << bytes_read << " bytes instead of "
<< bytes_expected << " bytes ("
<< getFunctionNameFromEnum(static_cast<detFuncs>(fnum_)) << ')';
if (this_read == 0)
ss << ": connection closed by peer (EOF)";
else if (this_read < 0)
ss << ": read error: " << std::strerror(err) << " ("
<< errno_name(err) << ")";
ss << " after " << timer.elapsed_ms() << " ms";
throw SocketError(ss.str());
}
}
@@ -78,38 +100,51 @@ std::string DataSocket::Receive(size_t length) {
return buff;
}
int DataSocket::Send(const void *buffer, size_t size) {
int bytes_sent = 0;
int data_size = static_cast<int>(size); // signed size
while (bytes_sent < (data_size)) {
auto this_send = ::write(getSocketId(), buffer, size);
ssize_t bytes_expected = static_cast<ssize_t>(size);
ssize_t bytes_sent = 0;
ssize_t this_send = 0; // last write result, kept for diagnostics
// Linux: avoid SIGPIPE on a broken connection by using send() with
// MSG_NOSIGNAL. macOS/BSD lack the flag and use SO_NOSIGPIPE instead
// (set in the constructor).
#ifdef MSG_NOSIGNAL
const int send_flags = MSG_NOSIGNAL;
#else
const int send_flags = 0;
#endif
Timer timer;
while (bytes_sent < bytes_expected) {
this_send = ::send(
getSocketId(),
reinterpret_cast<const char *>(buffer) + bytes_sent,
bytes_expected - bytes_sent, send_flags);
if (this_send < 0 && errno == EINTR)
continue; // interrupted by a signal, retry
if (this_send <= 0)
break;
bytes_sent += this_send;
}
if (bytes_sent != data_size) {
if (bytes_sent == bytes_expected) {
return static_cast<int>(bytes_sent);
} else {
int err = errno; // capture before any other call can clobber it
std::ostringstream ss;
ss << "TCP socket sent " << bytes_sent << " bytes instead of "
<< data_size << " bytes ("
<< bytes_expected << " bytes ("
<< getFunctionNameFromEnum(static_cast<detFuncs>(fnum_)) << ')';
if (this_send < 0)
ss << ": write error: " << std::strerror(err) << " ("
<< errno_name(err) << ")";
ss << " after " << timer.elapsed_ms() << " ms";
throw SocketError(ss.str());
}
return bytes_sent;
}
int DataSocket::Send(const std::string &s) { return Send(&s[0], s.size()); }
int DataSocket::write(void *buffer, size_t size) {
return ::write(getSocketId(), buffer, size);
}
int DataSocket::read(void *buffer, size_t size) {
return ::read(getSocketId(), buffer, size);
}
int DataSocket::setReceiveTimeout(int us) {
timeval t{};
t.tv_sec = 0;
t.tv_usec = us;
t.tv_sec = us / 1000000;
t.tv_usec = us % 1000000;
return ::setsockopt(getSocketId(), SOL_SOCKET, SO_RCVTIMEO, &t,
sizeof(struct timeval));
}
@@ -140,7 +175,9 @@ int DataSocket::setTimeOut(int t_seconds) {
void DataSocket::close() {
if (sockfd_ > 0) {
if (::close(sockfd_)) {
throw SocketError("could not close socket");
std::stringstream ss;
ss << "could not close socket (fd: " << sockfd_ << ")";
throw SocketError(ss.str());
}
sockfd_ = -1;
} else {
@@ -155,4 +192,61 @@ void DataSocket::shutDownSocket() {
void DataSocket::shutdown() { ::shutdown(sockfd_, SHUT_RDWR); }
std::string_view DataSocket::errno_name(int e) {
switch (e) {
#ifdef EACCES
case EACCES:
return "EACCES";
#endif
#ifdef EAGAIN
case EAGAIN:
return "EAGAIN";
#endif
#ifdef EBADF
case EBADF:
return "EBADF";
#endif
#ifdef ECONNABORTED
case ECONNABORTED:
return "ECONNABORTED";
#endif
#ifdef ECONNREFUSED
case ECONNREFUSED:
return "ECONNREFUSED";
#endif
#ifdef ECONNRESET
case ECONNRESET:
return "ECONNRESET";
#endif
#ifdef EINPROGRESS
case EINPROGRESS:
return "EINPROGRESS";
#endif
#ifdef EINTR
case EINTR:
return "EINTR";
#endif
#ifdef EINVAL
case EINVAL:
return "EINVAL";
#endif
#ifdef EPIPE
case EPIPE:
return "EPIPE";
#endif
#ifdef ETIMEDOUT
case ETIMEDOUT:
return "ETIMEDOUT";
#endif
#ifdef EWOULDBLOCK
#if EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
return "EWOULDBLOCK";
#endif
#endif
default:
return "UNKNOWN_ERRNO";
}
}
} // namespace sls
+3 -3
View File
@@ -10,16 +10,16 @@ namespace sls {
int ServerInterface::sendResult(int ret, void *retval, int retvalSize,
char *mess) {
write(&ret, sizeof(ret));
Send(&ret, sizeof(ret));
if (ret == defs::FAIL) {
if (mess != nullptr) {
write(mess, MAX_STR_LENGTH);
Send(mess, MAX_STR_LENGTH);
} else {
LOG(logERROR) << "No error message provided for this "
"failure. Will mess up TCP\n";
}
} else {
write(retval, retvalSize);
Send(retval, retvalSize);
}
return ret;
}
+7
View File
@@ -75,4 +75,11 @@ std::pair<std::string, uint16_t> ParseHostPort(const std::string &s) {
return std::make_pair(host, port);
}
std::string to_lower(const std::string &s) {
std::string result = s;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
}; // namespace sls
+291 -10
View File
@@ -3,37 +3,117 @@
#include "catch.hpp"
#include "sls/ClientSocket.h"
#include "sls/ServerSocket.h"
#include "sls/Timer.h"
#include "sls/sls_detector_defs.h"
#include "sls/sls_detector_exceptions.h"
#include "sls/sls_detector_funcs.h"
#include <atomic>
#include <chrono>
#include <future>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/time.h>
#include <thread>
#include <unistd.h>
namespace sls {
std::vector<char> server() {
std::cout << "starting server\n";
auto server = ServerSocket(1950);
// One configurable test server: accept a connection, read a 100-byte request,
// reply with `bytes_to_send` bytes (first two set to 'O','K'), optionally wait
// `hold` so the client can time out, then close. Returns the received request.
std::vector<char> echo_server(uint16_t port, size_t bytes_to_send,
std::chrono::milliseconds hold) {
std::cout << "starting server on port " << port << '\n';
auto server = ServerSocket(port);
auto s = server.accept();
std::vector<char> buffer(100, '\0');
s.Receive(buffer.data(), buffer.size());
std::cout << "ServerReceived: " << std::string(buffer.begin(), buffer.end())
<< '\n';
std::vector<char> to_send(100, '\0');
to_send[0] = 'O';
to_send[1] = 'K';
s.Send(to_send.data(), to_send.size());
if (port==1960){
struct linger ling = {
.l_onoff = 1,
.l_linger = 0
};
auto fd = s.getSocketId();
setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof ling);
::close(fd);
return buffer;
}
if (bytes_to_send > 0) {
std::vector<char> to_send(bytes_to_send, '\0');
to_send[0] = 'O';
to_send[1] = 'K';
s.Send(to_send.data(), to_send.size());
}
std::this_thread::sleep_for(hold);
s.close();
return buffer;
}
// Minimal command server speaking the same protocol as sendCommandThenRead:
// accept a connection, read the function number and a single int argument,
// then reply via ServerInterface::sendResult with OK and (arg * 2). Returns
// the {fnum, arg} pair the server received so the test can verify them.
std::pair<int, int> command_server(uint16_t port) {
auto server = ServerSocket(port);
auto s = server.accept();
int fnum = -1;
int arg = 0;
s.Receive(&fnum, sizeof(fnum));
s.Receive(&arg, sizeof(arg));
int retval = arg * 2;
s.sendResult(slsDetectorDefs::OK, &retval, sizeof(retval));
s.close();
return {fnum, arg};
}
// Command server that replies with a truncated message: it reads the request,
// sends the OK return code, but then sends fewer retval bytes than the client
// expects before closing, so the client's Receive hits EOF mid-read.
void short_reply_server(uint16_t port, size_t retval_bytes_to_send) {
auto server = ServerSocket(port);
auto s = server.accept();
int fnum = -1;
int arg = 0;
s.Receive(&fnum, sizeof(fnum));
s.Receive(&arg, sizeof(arg));
int ret = slsDetectorDefs::OK;
s.Send(&ret, sizeof(ret));
if (retval_bytes_to_send > 0) {
std::vector<char> partial(retval_bytes_to_send, '\0');
s.Send(partial.data(), partial.size());
}
s.close();
}
// Server that accepts a connection but never reads from it, so a client
// trying to send more than fits in the kernel buffers will stall. A small
// receive buffer keeps the amount the test must send modest. Stays open until
// the client signals it is done (or a safety timeout) so it never closes
// mid-transfer and races the client's Send.
void non_reading_server(uint16_t port, std::atomic<bool> *client_done) {
auto server = ServerSocket(port);
auto s = server.accept();
int rcvbuf = 1024;
setsockopt(s.getSocketId(), SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
// Intentionally never Receive(): let the client's send path back up.
Timer t;
while (!client_done->load() && t.elapsed_ms() < 10000)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
s.close();
}
TEST_CASE("The server recive the same message as we send", "[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
auto s = std::async(std::launch::async, server);
auto s = std::async(std::launch::async, echo_server, 1950, 100,
std::chrono::milliseconds(0));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1950);
client.Send(sent_message.data(), sent_message.size());
@@ -48,6 +128,207 @@ TEST_CASE("The server recive the same message as we send", "[support]") {
TEST_CASE("throws on no server", "[support]") {
CHECK_THROWS(DetectorSocket("localhost", 1950));
CHECK_THROWS(ReceiverSocket("localhost", 1950));
CHECK_THROWS(GuiSocket("localhost", 1950));
}
TEST_CASE("Receiving a too short message throws and reports EOF", "[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
// Server replies with only 10 of the 100 expected bytes, then closes.
auto s = std::async(std::launch::async, echo_server, 1951, 10,
std::chrono::milliseconds(0));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1951);
client.Send(sent_message.data(), sent_message.size());
client.setFnum(F_GET_SERVER_VERSION);
// The server only sends 10 of the 100 expected bytes and then closes the
// connection, so Receive must throw a SocketError reporting the EOF.
std::string error_message;
try {
client.Receive(received_message.data(), received_message.size());
FAIL("Receive should have thrown on a too short message");
} catch (const SocketError &e) {
error_message = e.what();
}
client.close();
s.get();
CHECK_THAT(error_message,
Catch::Matchers::Contains("read 10 bytes instead of 100 bytes"));
CHECK_THAT(error_message,
Catch::Matchers::Contains("connection closed by peer (EOF)"));
}
TEST_CASE("Receiving with a socket error throws and reports the error",
"[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
// Server stays silent (sends nothing) but keeps the connection open long
// enough for the client to time out, so the read fails with an error.
auto s = std::async(std::launch::async, echo_server, 1952, 0,
std::chrono::milliseconds(500));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1952);
client.Send(sent_message.data(), sent_message.size());
// Force read() to fail with EAGAIN/EWOULDBLOCK instead of returning EOF by
// setting a short receive timeout while the server stays silent.
client.setReceiveTimeout(100000); // 100 ms
std::string error_message;
try {
client.Receive(received_message.data(), received_message.size());
FAIL("Receive should have thrown on a socket error");
} catch (const SocketError &e) {
error_message = e.what();
}
client.close();
s.get();
CHECK_THAT(error_message,
Catch::Matchers::Contains("read 0 bytes instead of 100 bytes"));
CHECK_THAT(error_message, Catch::Matchers::Contains("read error:"));
}
TEST_CASE("Socket crash?", "[support]") {
std::vector<char> received_message(100, '\0');
std::vector<char> sent_message(100, '\0');
const char m[]{"some message"};
std::copy(std::begin(m), std::end(m), sent_message.data());
auto s = std::async(std::launch::async, echo_server, 1960, 100,
std::chrono::milliseconds(0));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", 1960);
client.Send(sent_message.data(), sent_message.size());
REQUIRE_THROWS(client.Receive(received_message.data(), received_message.size()));
//Now try to send more
// client.Send(sent_message.data(), sent_message.size());
}
TEST_CASE("ClientSocket throws on invalid hostname", "[support]") {
CHECK_THROWS(ReceiverSocket("invalidhostname", 1950));
CHECK_THROWS(DetectorSocket("invalidhostname", 1950));
CHECK_THROWS(GuiSocket("invalidhostname", 1950));
}
TEST_CASE("Using DetectorSocket to talk to a Server Socket", "[support]") {
constexpr uint16_t port = 1961;
constexpr int fnum = F_GET_DETECTOR_TYPE;
constexpr int arg = 21;
auto s = std::async(std::launch::async, command_server, port);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", port);
int retval = 0;
int ret =
client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval,
sizeof(retval));
client.close();
auto server_received = s.get();
// Client got OK and the expected return value back from the server
CHECK(ret == slsDetectorDefs::OK);
CHECK(retval == arg * 2);
// Server received the function number and argument we sent
CHECK(server_received.first == fnum);
CHECK(server_received.second == arg);
// close() resets the underlying fd
CHECK(client.getSocketId() == -1);
}
TEST_CASE("ServerSocket replies with a too short message", "[support]") {
constexpr uint16_t port = 1962;
constexpr int fnum = F_GET_DETECTOR_TYPE;
constexpr int arg = 21;
// Server sends the OK return code, then only 1 of the 4 expected retval
// bytes before closing.
auto s = std::async(std::launch::async, short_reply_server, port, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", port);
int retval = 0;
std::string error_message;
try {
client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval,
sizeof(retval));
FAIL("sendCommandThenRead should have thrown on a too short message");
} catch (const DetectorError &e) {
error_message = e.what();
}
client.close();
s.get();
// The client read only 1 of the 4 expected retval bytes and then hit EOF.
CHECK_THAT(error_message,
Catch::Matchers::Contains("read 1 bytes instead of 4 bytes"));
CHECK_THAT(error_message,
Catch::Matchers::Contains("connection closed by peer (EOF)"));
}
TEST_CASE("Client cannot send the expected number of bytes", "[support]") {
constexpr uint16_t port = 1963;
// Server accepts but never reads; it stays open until we tell it the
// client is done, so it cannot close mid-transfer.
std::atomic<bool> client_done{false};
auto s = std::async(std::launch::async, non_reading_server, port,
&client_done);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto client = DetectorSocket("localhost", port);
// Shrink the send buffer and add a short send timeout so the write stalls
// and returns before all the data is sent.
int sndbuf = 4096;
setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDBUF, &sndbuf,
sizeof(sndbuf));
struct timeval tv {};
tv.tv_sec = 0;
tv.tv_usec = 300000; // 300 ms
setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
// Much more than fits in the (shrunken) send + receive buffers, so the
// send cannot complete while the server refuses to read.
std::vector<char> big_message(8 * 1024 * 1024, '\0');
std::string error_message;
try {
client.Send(big_message.data(), big_message.size());
FAIL("Send should have thrown when it could not send all bytes");
} catch (const SocketError &e) {
error_message = e.what();
}
client_done = true;
client.close();
s.get();
// Fewer bytes were sent than expected, reported as a write error (the
// send timed out with EAGAIN/EWOULDBLOCK).
CHECK_THAT(error_message, Catch::Matchers::Contains("bytes instead of"));
CHECK_THAT(error_message, Catch::Matchers::Contains("write error:"));
}
} // namespace sls
+27
View File
@@ -123,6 +123,33 @@ TEST_CASE("port missing") {
REQUIRE(res.second == 0);
}
TEST_CASE("to_lower converts uppercase to lowercase") {
REQUIRE(to_lower("HELLO") == "hello");
REQUIRE(to_lower("Hello World") == "hello world");
}
TEST_CASE("to_lower leaves an already lowercase string unchanged") {
REQUIRE(to_lower("already lower") == "already lower");
}
TEST_CASE("to_lower only affects alphabetic characters") {
REQUIRE(to_lower("ABC123!?_-XYZ") == "abc123!?_-xyz");
}
TEST_CASE("to_lower on an empty string returns an empty string") {
REQUIRE(to_lower("").empty());
}
TEST_CASE("to_lower does not modify the original string") {
std::string original = "MixedCase";
auto result = to_lower(original);
REQUIRE(result == "mixedcase");
// the source string must be untouched
REQUIRE(original == "MixedCase");
}
// TEST_CASE("concat things not being strings")
} // namespace sls