summaryrefslogtreecommitdiffstats
path: root/src/lib/asiolink/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/asiolink/tests')
-rw-r--r--src/lib/asiolink/tests/Makefile.am8
-rw-r--r--src/lib/asiolink/tests/interval_timer_unittest.cc3
-rw-r--r--src/lib/asiolink/tests/tcp_acceptor_unittest.cc442
-rw-r--r--src/lib/asiolink/tests/tcp_socket_unittest.cc27
-rw-r--r--src/lib/asiolink/tests/unix_domain_socket_unittest.cc313
5 files changed, 780 insertions, 13 deletions
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 6e5592a566..4d6ce6b4e5 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -1,6 +1,7 @@
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/asiolink/tests\"
AM_CXXFLAGS = $(KEA_CXXFLAGS)
@@ -8,7 +9,7 @@ if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda test-socket
TESTS_ENVIRONMENT = \
$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
@@ -27,10 +28,13 @@ run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
run_unittests_SOURCES += dummy_io_callback_unittest.cc
+run_unittests_SOURCES += tcp_acceptor_unittest.cc
+run_unittests_SOURCES += unix_domain_socket_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
index 54da9f8935..957486e253 100644
--- a/src/lib/asiolink/tests/interval_timer_unittest.cc
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -154,8 +154,7 @@ TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
// expect throw if call back function is empty
EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
isc::InvalidParameter);
- // expect throw if interval is not greater than 0
- EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
+ // expect throw if interval is negative.
EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
}
diff --git a/src/lib/asiolink/tests/tcp_acceptor_unittest.cc b/src/lib/asiolink/tests/tcp_acceptor_unittest.cc
new file mode 100644
index 0000000000..a88a07a971
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_acceptor_unittest.cc
@@ -0,0 +1,442 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+ /// @brief Implements callback for the asynchronous operation on the socket.
+ ///
+ /// This callback merely checks if error has occurred and reports this
+ /// error. It does nothing in case of success.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of received data.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec) {
+ ADD_FAILURE() << "error occurred for a socket: " << ec.message();
+ }
+ }
+
+};
+
+/// @brief Entity which can connect to the TCP server endpoint and close the
+/// connection.
+class TCPClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ explicit TCPClient(IOService& io_service)
+ : io_service_(io_service.get_io_service()), socket_(io_service_) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TCPClient() {
+ close();
+ }
+
+ /// @brief Connect to the test server address and port.
+ ///
+ /// This method asynchronously connects to the server endpoint and uses the
+ /// connectHandler as a callback function.
+ void connect() {
+ boost::asio::ip::tcp::endpoint
+ endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ socket_.async_connect(endpoint,
+ boost::bind(&TCPClient::connectHandler, this,_1));
+ }
+
+ /// @brief Callback function for connect().
+ ///
+ /// This function stops the IO service upon error.
+ ///
+ /// @param ec Error code.
+ void connectHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return EINPROGRESS
+ // error code, but simply wait for the connection to get
+ // established before the handler is invoked. It turns out, however,
+ // that on some OSes the connect handler may receive this error code
+ // which doesn't necessarily indicate a problem. Making an attempt
+ // to write and read from this socket will typically succeed. So,
+ // we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ }
+ }
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ boost::asio::ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TCPClient.
+typedef boost::shared_ptr<TCPClient> TCPClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TCPAcceptor.
+typedef boost::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
+
+/// @brief TCPAcceptor using TCPAcceptorCallback.
+typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
+
+/// @brief Implements asynchronous TCP acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ /// @param acceptor Reference to the TCP acceptor on which asyncAccept
+ /// will be called.
+ /// @param callback Callback function for the asyncAccept.
+ explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
+ const TCPAcceptorCallback& callback)
+ : socket_(io_service), acceptor_(acceptor), callback_(callback) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes socket.
+ ~Acceptor() {
+ socket_.close();
+ }
+
+ /// @brief Asynchronous accept new connection.
+ void accept() {
+ acceptor_.asyncAccept(socket_, callback_);
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Socket into which connection is accepted.
+ TCPSocket<SocketCallback> socket_;
+
+ /// @brief Reference to the TCPAcceptor on which asyncAccept is called.
+ TestTCPAcceptor& acceptor_;
+
+ /// @brief Instance of the callback used for asyncAccept.
+ TCPAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TCPAcceptor.
+///
+/// This class provides means for creating new TCP connections, i.e. simulates
+/// clients connecting to the servers via TCPAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TCPAcceptorTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Besides initializing class members it also sets the test timer to guard
+ /// against endlessly running IO service when TCP connections are
+ /// unsuccessful.
+ TCPAcceptorTest()
+ : io_service_(), acceptor_(io_service_),
+ asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT),
+ endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+ clients_(), connections_num_(0), aborted_connections_num_(0),
+ max_connections_(1) {
+ test_timer_.setup(boost::bind(&TCPAcceptorTest::timeoutHandler, this),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ virtual ~TCPAcceptorTest() {
+ }
+
+ /// @brief Specifies how many new connections are expected before the IO
+ /// service is stopped.
+ ///
+ /// @param max_connections Connections limit.
+ void setMaxConnections(const unsigned int max_connections) {
+ max_connections_ = max_connections;
+ }
+
+ /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+ /// IP address and modifying the port.
+ ///
+ /// This convenience method is useful to create new endpoint from the
+ /// existing endpoint to test reusing IP address for multiple acceptors.
+ /// The returned endpoint has the same IP address but different port.
+ ///
+ /// @param endpoint Source endpoint.
+ ///
+ /// @return New endpoint with the port number increased by 1.
+ boost::asio::ip::tcp::endpoint
+ createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
+ boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
+ endpoint_copy.port(endpoint.port() + 1);
+ return (endpoint_copy);
+ }
+
+ /// @brief Opens TCP acceptor and sets 'reuse address' option.
+ void acceptorOpen() {
+ acceptor_.open(endpoint_);
+ acceptor_.setOption(TestTCPAcceptor::ReuseAddress(true));
+ }
+
+ /// @brief Starts accepting TCP connections.
+ ///
+ /// This method creates new Acceptor instance and calls accept() to start
+ /// accepting new connections. The instance of the Acceptor object is
+ /// retained in the connections_ list.
+ void accept() {
+ TCPAcceptorCallback cb = boost::bind(&TCPAcceptorTest::acceptHandler,
+ this, _1);
+ AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
+ connections_.push_back(conn);
+ connections_.back()->accept();
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TCPClient instance and retains it in the clients_
+ /// list.
+ void connect() {
+ TCPClientPtr client(new TCPClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->connect();
+ }
+
+ /// @brief Callback function for asynchronous accept calls.
+ ///
+ /// It stops the IO service upon error or when the number of accepted
+ /// connections reaches the max_connections_ value. Otherwise it calls
+ /// accept() to start accepting next connections.
+ ///
+ /// @param ec Error code.
+ void acceptHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while accepting connection: "
+ << ec.message();
+ } else {
+ ++aborted_connections_num_;
+ }
+ io_service_.stop();
+ }
+
+ // We have reached the maximum number of connections - end the test.
+ if (++connections_num_ >= max_connections_) {
+ io_service_.stop();
+ return;
+ }
+
+ accept();
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ }
+
+ /// @brief IO service.
+ IOService io_service_;
+
+ /// @brief TCPAcceptor under test.
+ TestTCPAcceptor acceptor_;
+
+ /// @brief Server endpoint.
+ boost::asio::ip::tcp::endpoint asio_endpoint_;
+
+ /// @brief asiolink server endpoint (uses asio_endpoint_).
+ TCPEndpoint endpoint_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief List of connections on the server side.
+ std::list<AcceptorPtr> connections_;
+
+ /// @brief List of client connections.
+ std::list<TCPClientPtr> clients_;
+
+ /// @brief Current number of established connections.
+ unsigned int connections_num_;
+
+ /// @brief Current number of aborted connections.
+ unsigned int aborted_connections_num_;
+
+ /// @brief Connections limit.
+ unsigned int max_connections_;
+};
+
+// Test TCPAcceptor::asyncAccept.
+TEST_F(TCPAcceptorTest, asyncAccept) {
+ // Establish up to 10 connections.
+ setMaxConnections(10);
+
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TCP connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Run the IO service until we have accepted 10 connections, an error
+ // or test timeout occurred.
+ io_service_.run();
+
+ // Make sure that all accepted connections have been recorded.
+ EXPECT_EQ(10, connections_num_);
+ EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
+TEST_F(TCPAcceptorTest, reuseAddress) {
+ // We need at least two acceptors using common address. Let's create the
+ // second endpoint which has the same address but different port.
+ boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+ TCPEndpoint endpoint2(asio_endpoint2);
+
+ // Create and open two acceptors.
+ TestTCPAcceptor acceptor1(io_service_);
+ TestTCPAcceptor acceptor2(io_service_);
+ ASSERT_NO_THROW(acceptor1.open(endpoint_));
+ ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+ // Set SO_REUSEADDR socket option so as acceptors can bind to the
+ /// same address.
+ ASSERT_NO_THROW(
+ acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(
+ acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+ ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+ // Create third acceptor, but don't set the SO_REUSEADDR. It should
+ // refuse to bind.
+ TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+ TestTCPAcceptor acceptor3(io_service_);
+ ASSERT_NO_THROW(acceptor3.open(endpoint3));
+ EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TCPAcceptorTest, getProtocol) {
+ EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TCPAcceptor::getNative returns valid socket descriptor.
+TEST_F(TCPAcceptorTest, getNative) {
+ // Initially the descriptor should be invalid (negative).
+ ASSERT_LT(acceptor_.getNative(), 0);
+ // Now open the socket and make sure the returned descriptor is now valid.
+ ASSERT_NO_THROW(acceptorOpen());
+ EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TCPAcceptor::close works properly.
+TEST_F(TCPAcceptorTest, close) {
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TCP connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Close the acceptor before connections are accepted.
+ acceptor_.close();
+
+ // Run the IO service.
+ io_service_.run();
+
+ // The connections should have been aborted.
+ EXPECT_EQ(1, connections_num_);
+ EXPECT_EQ(1, aborted_connections_num_);
+ EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
index 15bb779ce5..e247722099 100644
--- a/src/lib/asiolink/tests/tcp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,14 +22,15 @@
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
-#include <string>
+#include <algorithm>
#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
-#include <algorithm>
-#include <cstdlib>
-#include <cstddef>
+#include <string>
#include <vector>
using namespace boost::asio;
@@ -62,7 +63,7 @@ public:
NONE = 4 ///< "Not set" state
};
- /// \brief Minimim size of buffers
+ /// \brief Minimum size of buffers
enum {
MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count
};
@@ -149,7 +150,7 @@ public:
return (ptr_->expected_);
}
- /// \brief Get offset intodData
+ /// \brief Get offset into data
size_t& offset() {
return (ptr_->offset_);
}
@@ -355,7 +356,15 @@ TEST(TCPSocket, sequenceTest) {
EXPECT_EQ(0, server_cb.getCode());
EXPECT_EQ(TCPCallback::OPEN, client_cb.called());
- EXPECT_EQ(0, client_cb.getCode());
+
+ // On some operating system the async_connect may return EINPROGRESS.
+ // This doesn't necessarily indicate an error. In most cases trying
+ // to asynchronously write and read from the socket would work just
+ // fine.
+ if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+ ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+ << " as a result of async_connect, got " << client_cb.getCode();
+ }
// Step 2. Get the client to write to the server asynchronously. The
// server will loop reading the data synchronously.
@@ -418,7 +427,7 @@ TEST(TCPSocket, sequenceTest) {
// Run the callbacks. Several options are possible depending on how ASIO
// is implemented and whether the message gets fragmented:
//
- // 1) The send handler may complete immediately, regardess of whether the
+ // 1) The send handler may complete immediately, regardless of whether the
// data has been read by the client. (This is the most likely.)
// 2) The send handler may only run after all the data has been read by
// the client. (This could happen if the client's TCP buffers were too
diff --git a/src/lib/asiolink/tests/unix_domain_socket_unittest.cc b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc
new file mode 100644
index 0000000000..62c16c9f3a
--- /dev/null
+++ b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <gtest/gtest.h>
+#include <array>
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test unix socket file name.
+const std::string TEST_SOCKET = "test-socket";
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref UnixDomainSocket class.
+class UnixDomainSocketTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Removes unix socket descriptor before the test.
+ UnixDomainSocketTest() :
+ io_service_(),
+ test_socket_(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath())),
+ response_(),
+ read_buf_() {
+ test_socket_->startTimer(TEST_TIMEOUT);
+ removeUnixSocketFile();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes unix socket descriptor after the test.
+ virtual ~UnixDomainSocketTest() {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Returns socket file path.
+ ///
+ /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+ /// socket file is created in the location pointed to by this variable.
+ /// Otherwise, it is created in the build directory.
+ static std::string unixSocketFilePath() {
+ std::ostringstream s;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ s << std::string(env);
+ } else {
+ s << TEST_DATA_BUILDDIR;
+ }
+
+ s << "/" << TEST_SOCKET;
+ return (s.str());
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(remove(unixSocketFilePath().c_str()));
+ }
+
+ /// @brief Performs asynchronous receive on unix domain socket.
+ ///
+ /// This function performs partial read from the unix domain socket.
+ /// It uses @c read_buf_ or small size to ensure that the buffer fills
+ /// in before all that have been read. The partial responses are
+ /// appended to the @c response_ class member.
+ ///
+ /// If the response received so far is shorter than the expected
+ /// response, another partial read is scheduled.
+ ///
+ /// @param socket Reference to the unix domain socket.
+ /// @param expected_response Expected response.
+ void doReceive(UnixDomainSocket& socket,
+ const std::string& expected_response) {
+ socket.asyncReceive(&read_buf_[0], read_buf_.size(),
+ [this, &socket, expected_response]
+ (const boost::system::error_code& ec, size_t length) {
+ if (!ec) {
+ // Append partial response received and see if the
+ // size of the response received so far is still
+ // smaller than expected. If it is, schedule another
+ // partial read.
+ response_.append(&read_buf_[0], length);
+ if (expected_response.size() > response_.size()) {
+ doReceive(socket, expected_response);
+ }
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while asynchronously receiving"
+ " data via unix domain socket: " << ec.message();
+ }
+ });
+ }
+
+ /// @brief IO service used by the tests.
+ IOService io_service_;
+
+ /// @brief Server side unix socket used in these tests.
+ test::TestServerUnixSocketPtr test_socket_;
+
+ /// @brief String containing a response received with @c doReceive.
+ std::string response_;
+
+ /// @brief Read buffer used by @c doReceive.
+ std::array<char, 2> read_buf_;
+};
+
+// This test verifies that the client can send data over the unix
+// domain socket and receive a response.
+TEST_F(UnixDomainSocketTest, sendReceive) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+ ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+
+ // Send "foo".
+ const std::string outbound_data = "foo";
+ size_t sent_size = 0;
+ ASSERT_NO_THROW(sent_size = socket.write(outbound_data.c_str(),
+ outbound_data.size()));
+ // Make sure all data have been sent.
+ ASSERT_EQ(outbound_data.size(), sent_size);
+
+ // Run IO service to generate server's response.
+ while ((test_socket_->getResponseNum() < 1) &&
+ (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // Receive response from the socket.
+ std::array<char, 1024> read_buf;
+ size_t bytes_read = 0;
+ ASSERT_NO_THROW(bytes_read = socket.receive(&read_buf[0], read_buf.size()));
+ std::string response(&read_buf[0], bytes_read);
+
+ // The server should prepend "received" to the data we had sent.
+ EXPECT_EQ("received foo", response);
+}
+
+// This test verifies that the client can send the data over the unix
+// domain socket and receive a response asynchronously.
+TEST_F(UnixDomainSocketTest, asyncSendReceive) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+
+ // We're going to asynchronously connect to the server. The boolean value
+ // below will be modified by the connect handler function (lambda) invoked
+ // when the connection is established or if an error occurs.
+ bool connect_handler_invoked = false;
+ ASSERT_NO_THROW(socket.asyncConnect(unixSocketFilePath(),
+ [this, &connect_handler_invoked](const boost::system::error_code& ec) {
+ // Indicate that the handler has been called so as the loop below gets
+ // interrupted.
+ connect_handler_invoked = true;
+ // Operation aborted indicates that IO service has been stopped. This
+ // shouldn't happen here.
+ if (ec && (ec.value() != boost::asio::error::operation_aborted)) {
+ ADD_FAILURE() << "error occurred while asynchronously connecting"
+ " via unix domain socket: " << ec.message();
+ }
+ }
+ ));
+ // Run IO service until connect handler is invoked.
+ while (!connect_handler_invoked && (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // We are going to asynchronously send the 'foo' over the unix socket.
+ const std::string outbound_data = "foo";
+ size_t sent_size = 0;
+ ASSERT_NO_THROW(socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+ [this, &sent_size](const boost::system::error_code& ec, size_t length) {
+ // If we have been successful sending the data, record the number of
+ // bytes we have sent.
+ if (!ec) {
+ sent_size = length;
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while asynchronously sending the"
+ " data over unix domain socket: " << ec.message();
+ }
+ }
+ ));
+
+ // Run IO service to generate server's response.
+ while ((test_socket_->getResponseNum() < 1) &&
+ (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // There is no guarantee that all data have been sent so we only check that
+ // some data have been sent.
+ ASSERT_GT(sent_size, 0);
+
+ std::string expected_response = "received foo";
+ doReceive(socket, expected_response);
+
+ // Run IO service until we get the full response from the server.
+ while ((response_.size() < expected_response.size()) &&
+ !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Check that the entire response has been received and is correct.
+ EXPECT_EQ(expected_response, response_);
+}
+
+// This test verifies that UnixDomainSocketError exception is thrown
+// on attempt to connect, write or receive when the server socket
+// is not available.
+TEST_F(UnixDomainSocketTest, clientErrors) {
+ UnixDomainSocket socket(io_service_);
+ ASSERT_THROW(socket.connect(unixSocketFilePath()), UnixDomainSocketError);
+ const std::string outbound_data = "foo";
+ ASSERT_THROW(socket.write(outbound_data.c_str(), outbound_data.size()),
+ UnixDomainSocketError);
+ std::array<char, 1024> read_buf;
+ ASSERT_THROW(socket.receive(&read_buf[0], read_buf.size()),
+ UnixDomainSocketError);
+}
+
+// This test verifies that an error is returned on attempt to asynchronously
+// connect, write or receive when the server socket is not available.
+TEST_F(UnixDomainSocketTest, asyncClientErrors) {
+ UnixDomainSocket socket(io_service_);
+
+ // Asynchronous operations signal errors through boost::system::error_code
+ // object passed to the handler function. This object casts to boolean.
+ // In case of success the object casts to false. In case of an error it
+ // casts to true. The actual error codes can be retrieved by comparing the
+ // ec objects to predefined error objects. We don't check for the actual
+ // errors here, because it is not certain that the same error codes would
+ // be returned on various operating systems.
+
+ // In the following tests we use C++11 lambdas as callbacks.
+
+ // Connect
+ bool connect_handler_invoked = false;
+ socket.asyncConnect(unixSocketFilePath(),
+ [this, &connect_handler_invoked](const boost::system::error_code& ec) {
+ connect_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!connect_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Send
+ const std::string outbound_data = "foo";
+ bool send_handler_invoked = false;
+ socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+ [this, &send_handler_invoked]
+ (const boost::system::error_code& ec, size_t length) {
+ send_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!send_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Receive
+ bool receive_handler_invoked = false;
+ std::array<char, 1024> read_buf;
+ socket.asyncReceive(&read_buf[0], read_buf.size(),
+ [this, &receive_handler_invoked]
+ (const boost::system::error_code& ec, size_t length) {
+ receive_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!receive_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// Check that native socket descriptor is returned correctly when
+// the socket is connected.
+TEST_F(UnixDomainSocketTest, getNative) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+ ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+ ASSERT_GE(socket.getNative(), 0);
+}
+
+// Check that protocol returned is 0.
+TEST_F(UnixDomainSocketTest, getProtocol) {
+ UnixDomainSocket socket(io_service_);
+ EXPECT_EQ(0, socket.getProtocol());
+}
+
+}