mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-07-10 12:09:34 +02:00
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
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:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user