Files
sockpp/tests/unit/test_socket.cpp

352 lines
9.0 KiB
C++

// test_socket.cpp
//
// Unit tests for the base `socket` class.
//
// --------------------------------------------------------------------------
// This file is part of the "sockpp" C++ socket library.
//
// Copyright (c) 2019 Frank Pagliughi
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// --------------------------------------------------------------------------
//
#include "sockpp/socket.h"
#include "sockpp/inet_address.h"
#include "catch2_version.h"
#include <string>
using namespace sockpp;
using namespace std::chrono;
/////////////////////////////////////////////////////////////////////////////
// Aux functions
TEST_CASE("test to_timeval", "aux") {
SECTION("concrete function") {
timeval tv = to_timeval(microseconds(500));
REQUIRE(tv.tv_sec == 0);
REQUIRE(tv.tv_usec == 500);
tv = to_timeval(microseconds(2500000));
REQUIRE(tv.tv_sec == 2);
REQUIRE(tv.tv_usec == 500000);
}
SECTION("template") {
timeval tv = to_timeval(milliseconds(1));
REQUIRE(tv.tv_sec == 0);
REQUIRE(tv.tv_usec == 1000);
tv = to_timeval(milliseconds(2500));
REQUIRE(tv.tv_sec == 2);
REQUIRE(tv.tv_usec == 500000);
tv = to_timeval(seconds(5));
REQUIRE(tv.tv_sec == 5);
REQUIRE(tv.tv_usec == 0);
}
}
/////////////////////////////////////////////////////////////////////////////
// socket class
constexpr in_port_t INET_TEST_PORT = 12346;
TEST_CASE("socket constructors", "[socket]") {
SECTION("default constructor") {
sockpp::socket sock;
REQUIRE(!sock);
REQUIRE(!sock.is_open());
REQUIRE(sock.handle() == INVALID_SOCKET);
REQUIRE(sock.last_error() == 0);
}
SECTION("handle constructor") {
constexpr auto HANDLE = socket_t(3);
sockpp::socket sock(HANDLE);
REQUIRE(sock);
REQUIRE(sock.is_open());
REQUIRE(sock.handle() == HANDLE);
REQUIRE(sock.last_error() == 0);
}
SECTION("move constructor") {
constexpr auto HANDLE = socket_t(3);
sockpp::socket org_sock(HANDLE);
sockpp::socket sock(std::move(org_sock));
// Make sure the new socket got the handle
REQUIRE(sock);
REQUIRE(sock.handle() == HANDLE);
REQUIRE(sock.last_error() == 0);
// Make sure the handle was moved out of the org_sock
REQUIRE(!org_sock);
REQUIRE(org_sock.handle() == INVALID_SOCKET);
}
}
// Test the socket error behavior
TEST_CASE("socket errors", "[socket]") {
SECTION("basic errors") {
sockpp::socket sock;
// Operations on an unopened socket should give an error
int reuse = 1;
socklen_t len = sizeof(int);
bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len);
// Socket should be in error state
REQUIRE(!ok);
REQUIRE(!sock);
int err = sock.last_error();
REQUIRE(err != 0);
// last_error() is sticky, unlike `errno`
REQUIRE(sock.last_error() == err);
// We can clear the error
sock.clear();
REQUIRE(sock.last_error() == 0);
// Test arbitrary clear value
sock.clear(42);
REQUIRE(sock.last_error() == 42);
REQUIRE(!sock);
}
SECTION("clear error") {
auto sock = sockpp::socket::create(AF_INET, SOCK_STREAM);
REQUIRE(sock);
sock.clear(42);
REQUIRE(!sock);
sock.clear();
REQUIRE(sock);
}
}
TEST_CASE("socket handles", "[socket]") {
constexpr auto HANDLE = socket_t(3);
SECTION("test release") {
sockpp::socket sock(HANDLE);
REQUIRE(sock.handle() == HANDLE);
REQUIRE(sock.release() == HANDLE);
// Make sure the handle was moved out of the sock
REQUIRE(!sock);
REQUIRE(sock.handle() == INVALID_SOCKET);
}
SECTION("test reset") {
sockpp::socket sock(HANDLE);
REQUIRE(sock.handle() == HANDLE);
sock.reset(); // Default reset acts like release w/o return
// Make sure the handle was moved out of the sock
REQUIRE(!sock);
REQUIRE(sock.handle() == INVALID_SOCKET);
// Now reset with a "valid" handle
sock.reset(HANDLE);
REQUIRE(sock);
REQUIRE(sock.handle() == HANDLE);
}
}
TEST_CASE("socket family", "[socket]") {
SECTION("uninitialized socket") {
// Uninitialized socket should have unspecified family
sockpp::socket sock;
REQUIRE(sock.family() == AF_UNSPEC);
}
SECTION("unbound socket") {
// Unbound socket should have creation family
auto sock = socket::create(AF_INET, SOCK_STREAM);
// Windows and *nix behave differently
#if defined(_WIN32)
REQUIRE(sock.family() == AF_UNSPEC);
#else
REQUIRE(sock.family() == AF_INET);
#endif
}
SECTION("bound socket") {
// Bound socket should have same family as
// address to which it's bound
auto sock = socket::create(AF_INET, SOCK_STREAM);
inet_address addr(INET_TEST_PORT);
int reuse = 1;
REQUIRE(sock.set_option(SOL_SOCKET, SO_REUSEADDR, reuse));
REQUIRE(sock.bind(addr));
REQUIRE(sock.family() == addr.family());
}
}
TEST_CASE("socket address", "[socket]") {
SECTION("uninitialized socket") {
// Uninitialized socket should have empty address
sockpp::socket sock;
REQUIRE(sock.address() == sock_address_any{});
}
// The address has the specified family but all zeros
SECTION("unbound socket") {
auto sock = socket::create(AF_INET, SOCK_STREAM);
auto addr = inet_address(sock.address());
// Windows and *nix behave differently for family
#if defined(_WIN32)
REQUIRE(sock.family() == AF_UNSPEC);
#else
REQUIRE(sock.family() == AF_INET);
#endif
REQUIRE(addr.address() == 0);
REQUIRE(addr.port() == 0);
}
SECTION("bound socket") {
// Bound socket should have same family as
// address to which it's bound
auto sock = socket::create(AF_INET, SOCK_STREAM);
const inet_address ADDR(INET_TEST_PORT);
int reuse = 1;
REQUIRE(sock.set_option(SOL_SOCKET, SO_REUSEADDR, reuse));
REQUIRE(sock.bind(ADDR));
REQUIRE(sock.address() == ADDR);
}
}
// Socket pair shouldn't work for TCP sockets on any known platform.
// So this should fail, but fail gracefully and retain the error
// in both sockets.
TEST_CASE("failed socket pair", "[socket]") {
sockpp::socket sock1, sock2;
std::tie(sock1, sock2) = std::move(socket::pair(AF_INET, SOCK_STREAM));
REQUIRE(!sock1);
REQUIRE(!sock2);
REQUIRE(sock1.last_error() != 0);
REQUIRE(sock1.last_error() == sock2.last_error());
}
// Test putting the socket into and out of non-blocking mode
TEST_CASE("socket non-blocking mode", "[socket]") {
auto sock = socket::create(AF_INET, SOCK_STREAM);
#if !defined(_WIN32)
REQUIRE(!sock.is_non_blocking());
#endif
REQUIRE(sock.set_non_blocking());
#if !defined(_WIN32)
REQUIRE(sock.is_non_blocking());
#endif
REQUIRE(sock.set_non_blocking(false));
#if !defined(_WIN32)
REQUIRE(!sock.is_non_blocking());
#endif
}
// --------------------------------------------------------------------------
// Test that the "last error" call to a socket gives the proper result
// for the current thread.
// Here we share a socket across two threads, force an error in one
// thread, and then check to make sure that the error did not propagate
// to the other thread.
//
#if 0
TEST_CASE("thread-safe last error", "[socket]") {
sockpp::socket sock;
int state = 0;
std::mutex m;
std::condition_variable cv;
std::thread thr([&] {
// Test #1
REQUIRE(sock.last_error() == 0);
{
// Wait for Test #2
std::unique_lock<std::mutex> lk(m);
state = 1;
cv.notify_one();
cv.wait(lk, [&state]{return state >= 2;});
}
// Test #3
REQUIRE(sock.last_error() == 0);
});
{
// Wait for Test #1
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&state]{return state >= 1;});
}
// Test #2
// Setting options on an un-opened socket should generate an error
int reuse = 1;
socklen_t len = sizeof(int);
bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len);
REQUIRE(!ok);
REQUIRE(sock.last_error() != 0);
{
std::unique_lock<std::mutex> lk(m);
state = 2;
cv.notify_one();
}
thr.join();
}
#endif