|
@@ -1,253 +0,0 @@
|
|
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
|
|
|
-//
|
|
|
|
-// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
-// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
-// copyright notice and this permission notice appear in all copies.
|
|
|
|
-//
|
|
|
|
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
|
|
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
-// PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
-
|
|
|
|
-#include <asiolink/local_socket.h>
|
|
|
|
-#include <asiolink/io_error.h>
|
|
|
|
-
|
|
|
|
-#include <gtest/gtest.h>
|
|
|
|
-
|
|
|
|
-#include <boost/noncopyable.hpp>
|
|
|
|
-#include <boost/scoped_ptr.hpp>
|
|
|
|
-#include <boost/bind.hpp>
|
|
|
|
-
|
|
|
|
-#include <csignal>
|
|
|
|
-#include <cstring>
|
|
|
|
-#include <vector>
|
|
|
|
-
|
|
|
|
-#include <sys/socket.h>
|
|
|
|
-#include <stdint.h>
|
|
|
|
-#include <unistd.h> // for alarm(3)
|
|
|
|
-
|
|
|
|
-using namespace isc::asiolink;
|
|
|
|
-
|
|
|
|
-namespace {
|
|
|
|
-
|
|
|
|
-// duration (in seconds) until we break possible hangup; value is an
|
|
|
|
-// arbitrary choice.
|
|
|
|
-const unsigned IO_TIMEOUT = 10;
|
|
|
|
-
|
|
|
|
-// A simple RAII wrapper for a file descriptor so test sockets are safely
|
|
|
|
-// closed in each test.
|
|
|
|
-class ScopedSocket : boost::noncopyable {
|
|
|
|
-public:
|
|
|
|
- ScopedSocket() : fd_(-1) {}
|
|
|
|
- ~ScopedSocket() {
|
|
|
|
- if (fd_ >= 0) {
|
|
|
|
- EXPECT_EQ(0, ::close(fd_));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- void set(int fd) {
|
|
|
|
- assert(fd_ == -1);
|
|
|
|
- fd_ = fd;
|
|
|
|
- }
|
|
|
|
- int get() { return (fd_); }
|
|
|
|
- int release() {
|
|
|
|
- const int ret = fd_;
|
|
|
|
- fd_ = -1;
|
|
|
|
- return (ret);
|
|
|
|
- }
|
|
|
|
-private:
|
|
|
|
- int fd_;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-class LocalSocketTest : public ::testing::Test {
|
|
|
|
-protected:
|
|
|
|
- LocalSocketTest() {
|
|
|
|
- int sock_pair[2];
|
|
|
|
- EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair));
|
|
|
|
- sock_pair_[0].set(sock_pair[0]);
|
|
|
|
- sock_pair_[1].set(sock_pair[1]);
|
|
|
|
-
|
|
|
|
- // For tests using actual I/O we use a timer to prevent hangup
|
|
|
|
- // due to a bug. Set up the signal handler for the timer here.
|
|
|
|
- g_io_service_ = &io_service_;
|
|
|
|
- prev_handler_ = std::signal(SIGALRM, stopIOService);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ~LocalSocketTest() {
|
|
|
|
- alarm(0);
|
|
|
|
- // reset the global to NULL to detect any invalid access to freed
|
|
|
|
- // io_service (this shouldn't happen, so we don't change stopIOService
|
|
|
|
- // itself)
|
|
|
|
- g_io_service_ = NULL;
|
|
|
|
- std::signal(SIGALRM, prev_handler_);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Common set of tests for async read
|
|
|
|
- void checkAsyncRead(size_t data_len);
|
|
|
|
-
|
|
|
|
- IOService io_service_;
|
|
|
|
- ScopedSocket sock_pair_[2];
|
|
|
|
- std::vector<uint8_t> read_buf_;
|
|
|
|
-private:
|
|
|
|
- static IOService* g_io_service_; // will be set to &io_service_
|
|
|
|
- void (*prev_handler_)(int);
|
|
|
|
-
|
|
|
|
- // SIGALRM handler to prevent hangup. This must be a static method
|
|
|
|
- // so it can be passed to std::signal().
|
|
|
|
- static void stopIOService(int) {
|
|
|
|
- g_io_service_->stop();
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-IOService* LocalSocketTest::g_io_service_ = NULL;
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, construct) {
|
|
|
|
- const int fd = sock_pair_[0].release();
|
|
|
|
- LocalSocket sock(io_service_, fd);
|
|
|
|
- EXPECT_EQ(fd, sock.getNative());
|
|
|
|
- EXPECT_EQ(AF_UNIX, sock.getProtocol());
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, constructError) {
|
|
|
|
- // try to construct a LocalSocket object with a closed socket. It should
|
|
|
|
- // fail.
|
|
|
|
- const int fd = sock_pair_[0].release();
|
|
|
|
- EXPECT_EQ(0, close(fd));
|
|
|
|
- EXPECT_THROW(LocalSocket(io_service_, fd), IOError);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, autoClose) {
|
|
|
|
- // Confirm that passed FD will be closed on destruction of LocalSocket
|
|
|
|
- const int fd = sock_pair_[0].release();
|
|
|
|
- {
|
|
|
|
- LocalSocket sock(io_service_, fd);
|
|
|
|
- }
|
|
|
|
- // fd should have been closed, so close() should fail (we assume there's
|
|
|
|
- // no other open() call since then)
|
|
|
|
- EXPECT_EQ(-1, ::close(fd));
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void
|
|
|
|
-callback(const std::string& error, IOService* io_service, bool* called,
|
|
|
|
- bool expect_error)
|
|
|
|
-{
|
|
|
|
- if (expect_error) {
|
|
|
|
- EXPECT_NE("", error);
|
|
|
|
- } else {
|
|
|
|
- EXPECT_EQ("", error);
|
|
|
|
- }
|
|
|
|
- *called = true;
|
|
|
|
- io_service->stop();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void
|
|
|
|
-LocalSocketTest::checkAsyncRead(size_t data_len) {
|
|
|
|
- LocalSocket sock(io_service_, sock_pair_[0].release());
|
|
|
|
- bool callback_called = false;
|
|
|
|
- read_buf_.resize(data_len);
|
|
|
|
- sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
|
|
|
|
- false), &read_buf_[0], data_len);
|
|
|
|
-
|
|
|
|
- std::vector<uint8_t> expected_data(data_len);
|
|
|
|
- for (size_t i = 0; i < data_len; ++i) {
|
|
|
|
- expected_data[i] = i & 0xff;
|
|
|
|
- }
|
|
|
|
- alarm(IO_TIMEOUT);
|
|
|
|
- // If write blocks, it will eventually fail due to signal interruption.
|
|
|
|
- // Since io_service has been stopped already, run() would immediately
|
|
|
|
- // return and test should complete (with failure). But to make very sure
|
|
|
|
- // it never cause hangup we rather return from the test at the point of
|
|
|
|
- // failure of write. In either case it signals a failure and need for
|
|
|
|
- // a fix.
|
|
|
|
- ASSERT_EQ(data_len, write(sock_pair_[1].get(), &expected_data[0],
|
|
|
|
- data_len));
|
|
|
|
- io_service_.run();
|
|
|
|
- EXPECT_TRUE(callback_called);
|
|
|
|
- EXPECT_EQ(0, std::memcmp(&expected_data[0], &read_buf_[0], data_len));
|
|
|
|
-
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, asyncRead) {
|
|
|
|
- // A simple case of asynchronous read: wait for 1 byte and successfully
|
|
|
|
- // read it in the run() loop.
|
|
|
|
- checkAsyncRead(1);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, asyncLargeRead) {
|
|
|
|
- // Similar to the previous case, but for moderately larger data.
|
|
|
|
- // (for the moment) we don't expect to use this interface with much
|
|
|
|
- // larger data that could cause blocking write.
|
|
|
|
- checkAsyncRead(1024);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, asyncPartialRead) {
|
|
|
|
- alarm(IO_TIMEOUT);
|
|
|
|
-
|
|
|
|
- // specify reading 4 bytes of data, and send 3 bytes. It shouldn't cause
|
|
|
|
- // callback. while we actually don't use the buffer, we'll initialize it
|
|
|
|
- // to make valgrind happy.
|
|
|
|
- char recv_buf[4];
|
|
|
|
- std::memset(recv_buf, 0, sizeof(recv_buf));
|
|
|
|
- bool callback_called = false;
|
|
|
|
- LocalSocket sock(io_service_, sock_pair_[0].release());
|
|
|
|
- sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
|
|
|
|
- false), recv_buf, sizeof(recv_buf));
|
|
|
|
- EXPECT_EQ(3, write(sock_pair_[1].get(), recv_buf, 3));
|
|
|
|
-
|
|
|
|
- // open another pair of sockets so we can stop the IO service after run.
|
|
|
|
- int socks[2];
|
|
|
|
- char ch = 0;
|
|
|
|
- EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
|
|
|
|
- ScopedSocket aux_sockpair[2];
|
|
|
|
- aux_sockpair[0].set(socks[0]);
|
|
|
|
- aux_sockpair[1].set(socks[1]);
|
|
|
|
- LocalSocket aux_sock(io_service_, aux_sockpair[0].get());
|
|
|
|
- aux_sockpair[0].release(); // on successful construction we should release
|
|
|
|
- bool aux_callback_called = false;
|
|
|
|
- aux_sock.asyncRead(boost::bind(&callback, _1, &io_service_,
|
|
|
|
- &aux_callback_called, false), &ch, 1);
|
|
|
|
- EXPECT_EQ(1, write(aux_sockpair[1].get(), &ch, 1));
|
|
|
|
-
|
|
|
|
- // run the IO service, it will soon be stopped via the auxiliary callback.
|
|
|
|
- // the main callback shouldn't be called.
|
|
|
|
- io_service_.run();
|
|
|
|
- EXPECT_FALSE(callback_called);
|
|
|
|
- EXPECT_TRUE(aux_callback_called);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, asyncReadError) {
|
|
|
|
- const int sock_fd = sock_pair_[0].release();
|
|
|
|
- LocalSocket sock(io_service_, sock_fd);
|
|
|
|
- bool callback_called = false;
|
|
|
|
- read_buf_.resize(1);
|
|
|
|
- read_buf_.at(0) = 53; // dummy data to check it later
|
|
|
|
- const char ch = 35; // send different data to the read socket with data
|
|
|
|
- EXPECT_EQ(1, write(sock_pair_[1].get(), &ch, 1));
|
|
|
|
- close(sock_fd); // invalidate the read socket
|
|
|
|
- // we'll get callback with an error (e.g. 'bad file descriptor)
|
|
|
|
- sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
|
|
|
|
- true), &read_buf_[0], 1);
|
|
|
|
-
|
|
|
|
- io_service_.run();
|
|
|
|
- EXPECT_TRUE(callback_called);
|
|
|
|
- EXPECT_EQ(53, read_buf_.at(0));
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-TEST_F(LocalSocketTest, asyncReadThenDestroy) {
|
|
|
|
- // destroy the socket before running the IO service. we'll still get
|
|
|
|
- // callback with an error.
|
|
|
|
- boost::scoped_ptr<LocalSocket> sock(
|
|
|
|
- new LocalSocket(io_service_, sock_pair_[0].release()));
|
|
|
|
- read_buf_.resize(1);
|
|
|
|
- bool callback_called = false;
|
|
|
|
- sock->asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
|
|
|
|
- true), &read_buf_[0], 1);
|
|
|
|
- sock.reset();
|
|
|
|
-
|
|
|
|
- io_service_.run();
|
|
|
|
- EXPECT_TRUE(callback_called);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-}
|
|
|