BIND 10 trac569, updated. a9ac4ee37388d93c0adceffa424599b5affc5c3a [trac569] split up unittests as well
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Feb 14 13:17:06 UTC 2011
The branch, trac569 has been updated
via a9ac4ee37388d93c0adceffa424599b5affc5c3a (commit)
from dfb68c22293f3e84aae8e81865525dd043f26e0d (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit a9ac4ee37388d93c0adceffa424599b5affc5c3a
Author: Jelte Jansen <jelte at isc.org>
Date: Mon Feb 14 14:16:39 2011 +0100
[trac569] split up unittests as well
Though most of the tests fell into recursive_query_unittest.cc
-----------------------------------------------------------------------
Summary of changes:
src/lib/asiolink/Makefile.am | 2 +-
src/lib/asiolink/internal/Makefile.am | 1 -
src/lib/asiolink/internal/tests/Makefile.am | 37 -
src/lib/asiolink/internal/tests/run_unittests.cc | 21 -
src/lib/asiolink/tests/Makefile.am | 8 +-
src/lib/asiolink/tests/asiolink_unittest.cc | 1239 --------------------
src/lib/asiolink/tests/interval_timer_unittest.cc | 292 +++++
src/lib/asiolink/tests/io_service_unittest.cc | 115 ++
src/lib/asiolink/tests/ioaddress_unittest.cc | 57 +
src/lib/asiolink/tests/ioendpoint_unittest.cc | 67 ++
.../tests/iosocket_unittest.cc} | 19 +-
src/lib/asiolink/tests/recursive_query_unittest.cc | 791 +++++++++++++
.../udp_query_unittest.cc} | 2 +-
13 files changed, 1341 insertions(+), 1310 deletions(-)
delete mode 100644 src/lib/asiolink/internal/Makefile.am
delete mode 100644 src/lib/asiolink/internal/tests/Makefile.am
delete mode 100644 src/lib/asiolink/internal/tests/run_unittests.cc
delete mode 100644 src/lib/asiolink/tests/asiolink_unittest.cc
create mode 100644 src/lib/asiolink/tests/interval_timer_unittest.cc
create mode 100644 src/lib/asiolink/tests/io_service_unittest.cc
create mode 100644 src/lib/asiolink/tests/ioaddress_unittest.cc
create mode 100644 src/lib/asiolink/tests/ioendpoint_unittest.cc
copy src/lib/{log/message_exception.cc => asiolink/tests/iosocket_unittest.cc} (66%)
create mode 100644 src/lib/asiolink/tests/recursive_query_unittest.cc
rename src/lib/asiolink/{internal/tests/udpdns_unittest.cc => tests/udp_query_unittest.cc} (98%)
-----------------------------------------------------------------------
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index e1590fd..f5e8806 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests internal
+SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
diff --git a/src/lib/asiolink/internal/Makefile.am b/src/lib/asiolink/internal/Makefile.am
deleted file mode 100644
index 3c6155b..0000000
--- a/src/lib/asiolink/internal/Makefile.am
+++ /dev/null
@@ -1 +0,0 @@
-SUBDIRS = tests
diff --git a/src/lib/asiolink/internal/tests/Makefile.am b/src/lib/asiolink/internal/tests/Makefile.am
deleted file mode 100644
index 449cab7..0000000
--- a/src/lib/asiolink/internal/tests/Makefile.am
+++ /dev/null
@@ -1,37 +0,0 @@
-AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
-
-AM_CXXFLAGS = $(B10_CXXFLAGS)
-
-if USE_STATIC_LINK
-AM_LDFLAGS = -static
-endif
-
-CLEANFILES = *.gcno *.gcda
-
-TESTS =
-if HAVE_GTEST
-TESTS += run_unittests
-run_unittests_SOURCES = udpdns_unittest.cc
-run_unittests_SOURCES += run_unittests.cc
-run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
-# B10_CXXFLAGS)
-run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-run_unittests_CXXFLAGS += -Wno-unused-parameter
-endif
-if USE_CLANGPP
-# We need to disable -Werror for any test that uses internal definitions of
-# ASIO when using clang++
-run_unittests_CXXFLAGS += -Wno-error
-endif
-endif
-
-noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/internal/tests/run_unittests.cc b/src/lib/asiolink/internal/tests/run_unittests.cc
deleted file mode 100644
index 5f1195d..0000000
--- a/src/lib/asiolink/internal/tests/run_unittests.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2010 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 <gtest/gtest.h>
-
-int
-main(int argc, char* argv[]) {
- ::testing::InitGoogleTest(&argc, argv);
- return (RUN_ALL_TESTS());
-}
diff --git a/src/lib/asiolink/internal/tests/udpdns_unittest.cc b/src/lib/asiolink/internal/tests/udpdns_unittest.cc
deleted file mode 100644
index fe52244..0000000
--- a/src/lib/asiolink/internal/tests/udpdns_unittest.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2010 CZ.NIC
-//
-// 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 <gtest/gtest.h>
-#include <asio.hpp>
-#include <boost/bind.hpp>
-#include <cstdlib>
-
-#include <dns/question.h>
-
-#include <asiolink/udp_query.h>
-
-using namespace asio;
-using namespace isc::dns;
-using asio::ip::udp;
-
-namespace {
-
-const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
-const uint16_t TEST_PORT(5301);
-// FIXME Shouldn't we send something that is real message?
-const char TEST_DATA[] = "TEST DATA";
-
-// Test fixture for the asiolink::UDPQuery.
-class UDPQueryTest : public ::testing::Test,
- public asiolink::UDPQuery::Callback
-{
- public:
- // Expected result of the callback
- asiolink::UDPQuery::Result expected_;
- // Did the callback run already?
- bool run_;
- // We use an io_service to run the query
- io_service service_;
- // Something to ask
- Question question_;
- // Buffer where the UDPQuery will store response
- OutputBufferPtr buffer_;
- // The query we are testing
- asiolink::UDPQuery query_;
-
- UDPQueryTest() :
- run_(false),
- question_(Name("example.net"), RRClass::IN(), RRType::A()),
- buffer_(new OutputBuffer(512)),
- query_(service_, question_, asiolink::IOAddress(TEST_HOST),
- TEST_PORT, buffer_, this, 100)
- { }
-
- // This is the callback's (), so it can be called.
- void operator()(asiolink::UDPQuery::Result result) {
- // We check the query returns the correct result
- EXPECT_EQ(expected_, result);
- // Check it is called only once
- EXPECT_FALSE(run_);
- // And mark the callback was called
- run_ = true;
- }
- // A response handler, pretending to be remote DNS server
- void respond(udp::endpoint* remote, udp::socket* socket) {
- // Some data came, just send something back.
- socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA),
- *remote);
- socket->close();
- }
-};
-
-/*
- * Test that when we run the query and stop it after it was run,
- * it returns "stopped" correctly.
- *
- * That is why stop() is posted to the service_ as well instead
- * of calling it.
- */
-TEST_F(UDPQueryTest, stop) {
- expected_ = asiolink::UDPQuery::STOPPED;
- // Post the query
- service_.post(query_);
- // Post query_.stop() (yes, the boost::bind thing is just
- // query_.stop()).
- service_.post(boost::bind(&asiolink::UDPQuery::stop, query_,
- asiolink::UDPQuery::STOPPED));
- // Run both of them
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that when we queue the query to service_ and call stop()
- * before it gets executed, it acts sanely as well (eg. has the
- * same result as running stop() after - calls the callback).
- */
-TEST_F(UDPQueryTest, prematureStop) {
- expected_ = asiolink::UDPQuery::STOPPED;
- // Stop before it is started
- query_.stop();
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that it will timeout when no answer will arrive.
- */
-TEST_F(UDPQueryTest, timeout) {
- expected_ = asiolink::UDPQuery::TIME_OUT;
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
-}
-
-/*
- * Test that it will succeed when we fake an answer and
- * stores the same data we send.
- *
- * This is done through a real socket on loopback address.
- */
-TEST_F(UDPQueryTest, receive) {
- expected_ = asiolink::UDPQuery::SUCCESS;
- udp::socket socket(service_, udp::v4());
- socket.set_option(socket_base::reuse_address(true));
- socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
- char inbuff[512];
- udp::endpoint remote;
- socket.async_receive_from(asio::buffer(inbuff, 512), remote, boost::bind(
- &UDPQueryTest::respond, this, &remote, &socket));
- service_.post(query_);
- service_.run();
- EXPECT_TRUE(run_);
- ASSERT_EQ(sizeof TEST_DATA, buffer_->getLength());
- EXPECT_EQ(0, memcmp(TEST_DATA, buffer_->getData(), sizeof TEST_DATA));
-}
-
-}
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index e301fb2..fe87050 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -17,7 +17,13 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
-run_unittests_SOURCES += asiolink_unittest.cc
+run_unittests_SOURCES += udp_query_unittest.cc
+run_unittests_SOURCES += ioaddress_unittest.cc
+run_unittests_SOURCES += ioendpoint_unittest.cc
+run_unittests_SOURCES += iosocket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += interval_timer_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
diff --git a/src/lib/asiolink/tests/asiolink_unittest.cc b/src/lib/asiolink/tests/asiolink_unittest.cc
deleted file mode 100644
index b3ecaf0..0000000
--- a/src/lib/asiolink/tests/asiolink_unittest.cc
+++ /dev/null
@@ -1,1239 +0,0 @@
-// Copyright (C) 2010 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 <config.h>
-
-#include <sys/socket.h>
-#include <sys/time.h>
-
-#include <string.h>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <gtest/gtest.h>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/rcode.h>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-
-// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
-// In particular, we must not include asio.hpp in this file.
-// The asiolink module is primarily intended to be a wrapper that hide the
-// details of the underlying implementations. We need to test the wrapper
-// level behaviors. In addition, some compilers reject to compile this file
-// if we include asio.hpp unless we specify a special compiler option.
-// If we need to test something at the level of underlying ASIO and need
-// their definition, that test should go to asiolink/internal/tests.
-#include <asiolink/asiolink.h>
-#include <asiolink/iosocket.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace asiolink;
-using namespace isc::dns;
-
-namespace {
-const char* const TEST_SERVER_PORT = "53535";
-const char* const TEST_CLIENT_PORT = "53536";
-const char* const TEST_IPV6_ADDR = "::1";
-const char* const TEST_IPV4_ADDR = "127.0.0.1";
-// This data is intended to be valid as a DNS/TCP-like message: the first
-// two octets encode the length of the rest of the data. This is crucial
-// for the tests below.
-const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
-// TODO: Consider this margin
-const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
- boost::posix_time::milliseconds(50);
-
-TEST(IOAddressTest, fromText) {
- IOAddress io_address_v4("192.0.2.1");
- EXPECT_EQ("192.0.2.1", io_address_v4.toText());
-
- IOAddress io_address_v6("2001:db8::1234");
- EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
-
- // bogus IPv4 address-like input
- EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
-
- // bogus IPv4 address-like input: out-of-range octet
- EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
-
- // bogus IPv6 address-like input
- EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
-
- // bogus IPv6 address-like input
- EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
-}
-
-TEST(IOAddressTest, Equality) {
- EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
- EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
-
- EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
- EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
-
- EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
- EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
-
- EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
- EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
-
- EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
- EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
-}
-
-TEST(IOEndpointTest, createUDPv4) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
- EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
- EXPECT_EQ(5300, ep->getPort());
- EXPECT_EQ(AF_INET, ep->getFamily());
- EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createTCPv4) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
- EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
- EXPECT_EQ(5301, ep->getPort());
- EXPECT_EQ(AF_INET, ep->getFamily());
- EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createUDPv6) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
- EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
- EXPECT_EQ(5302, ep->getPort());
- EXPECT_EQ(AF_INET6, ep->getFamily());
- EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createTCPv6) {
- const IOEndpoint* ep;
- ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
- EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
- EXPECT_EQ(5303, ep->getPort());
- EXPECT_EQ(AF_INET6, ep->getFamily());
- EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
- EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
-}
-
-TEST(IOEndpointTest, createIPProto) {
- EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
- 5300)->getAddress().toText(),
- IOError);
-}
-
-TEST(IOSocketTest, dummySockets) {
- EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
- EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
- EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
- EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
-}
-
-TEST(IOServiceTest, badPort) {
- IOService io_service;
- EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
- EXPECT_THROW(DNSService(io_service, *"5300.0", true, false, NULL, NULL, NULL), IOError);
- EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
- EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
-}
-
-TEST(IOServiceTest, badAddress) {
- IOService io_service;
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
-}
-
-TEST(IOServiceTest, unavailableAddress) {
- IOService io_service;
- // These addresses should generally be unavailable as a valid local
- // address, although there's no guarantee in theory.
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
-
- // Some OSes would simply reject binding attempt for an AF_INET6 socket
- // to an IPv4-mapped IPv6 address. Even if those that allow it, since
- // the corresponding IPv4 address is the same as the one used in the
- // AF_INET socket case above, it should at least show the same result
- // as the previous one.
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
-}
-
-TEST(IOServiceTest, duplicateBind_v6) {
- // In each sub test case, second attempt should fail due to duplicate bind
- IOService io_service;
-
- // IPv6, "any" address
- DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
- delete dns_service;
-
-}
-
-TEST(IOServiceTest, duplicateBind_v6_address) {
- // In each sub test case, second attempt should fail due to duplicate bind
- IOService io_service;
-
- // IPv6, specific address
- DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
- delete dns_service;
-
-}
-
-TEST(IOServiceTest, duplicateBind_v4) {
- // In each sub test case, second attempt should fail due to duplicate bind
- IOService io_service;
-
- // IPv4, "any" address
- DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
- delete dns_service;
-
-}
-
-TEST(IOServiceTest, duplicateBind_v4_address) {
- // In each sub test case, second attempt should fail due to duplicate bind
- IOService io_service;
-
- // IPv4, specific address
- DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
- delete dns_service;
-}
-
-// Disabled because IPv4-mapped addresses don't seem to be working with
-// the IOService constructor
-TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
- IOService io_service;
- // Duplicate bind on IPv4-mapped IPv6 address
- DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
- delete dns_service;
-
- // XXX:
- // Currently, this throws an "invalid argument" exception. I have
- // not been able to get IPv4-mapped addresses to work.
- dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
- EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
- delete dns_service;
-}
-
-// This function returns an addrinfo structure for use by tests, using
-// different addresses and ports depending on whether we're testing
-// IPv4 or v6, TCP or UDP, and client or server operation.
-struct addrinfo*
-resolveAddress(const int family, const int protocol, const bool client) {
- const char* const addr = (family == AF_INET6) ?
- TEST_IPV6_ADDR : TEST_IPV4_ADDR;
- const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
- hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
- hints.ai_protocol = protocol;
- hints.ai_flags = AI_NUMERICSERV;
-
- struct addrinfo* res;
- const int error = getaddrinfo(addr, port, &hints, &res);
- if (error != 0) {
- isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
- }
-
- return (res);
-}
-
-// This fixture is a framework for various types of network operations
-// using the ASIO interfaces. Each test case creates an IOService object,
-// opens a local "client" socket for testing, sends data via the local socket
-// to the service that would run in the IOService object.
-// A mock callback function (an ASIOCallBack object) is registered with the
-// IOService object, so the test code should be able to examine the data
-// received on the server side. It then checks the received data matches
-// expected parameters.
-// If initialization parameters of the IOService should be modified, the test
-// case can do it using the setDNSService() method.
-// Note: the set of tests in ASIOLinkTest use actual network services and may
-// involve undesirable side effects such as blocking.
-class ASIOLinkTest : public ::testing::Test {
-protected:
- ASIOLinkTest();
- ~ASIOLinkTest() {
- if (res_ != NULL) {
- freeaddrinfo(res_);
- }
- if (sock_ != -1) {
- close(sock_);
- }
- delete dns_service_;
- delete callback_;
- delete io_service_;
- }
-
- // Send a test UDP packet to a mock server
- void sendUDP(const int family) {
- res_ = resolveAddress(family, IPPROTO_UDP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
- res_->ai_addr, res_->ai_addrlen);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected sendto result: " << cc);
- }
- io_service_->run();
- }
-
- // Send a test TCP packet to a mock server
- void sendTCP(const int family) {
- res_ = resolveAddress(family, IPPROTO_TCP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to connect to the test server");
- }
- const int cc = send(sock_, test_data, sizeof(test_data), 0);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected send result: " << cc);
- }
- io_service_->run();
- }
-
- // Receive a UDP packet from a mock server; used for testing
- // recursive lookup. The caller must place a RecursiveQuery
- // on the IO Service queue before running this routine.
- void recvUDP(const int family, void* buffer, size_t& size) {
- res_ = resolveAddress(family, IPPROTO_UDP, true);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
-
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "bind failed: " << strerror(errno));
- }
-
- // The IO service queue should have a RecursiveQuery object scheduled
- // to run at this point. This call will cause it to begin an
- // async send, then return.
- io_service_->run_one();
-
- // ... and this one will block until the send has completed
- io_service_->run_one();
-
- // Now we attempt to recv() whatever was sent.
- // XXX: there's no guarantee the receiving socket can immediately get
- // the packet. Normally we can perform blocking recv to wait for it,
- // but in theory it's even possible that the packet is lost.
- // In order to prevent the test from hanging in such a worst case
- // we add an ad hoc timeout.
- const struct timeval timeo = { 10, 0 };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
- sizeof(timeo))) {
- if (errno == ENOPROTOOPT) {
- // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
- // with the error of ENOPROTOOPT. Since this is a workaround
- // for rare error cases anyway, we simply switch to the
- // "don't wait" mode. If we still find an error in recv()
- // can happen often we'll consider a more complete solution.
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- const int ret = recv(sock_, buffer, size, recv_options);
- if (ret < 0) {
- isc_throw(IOError, "recvfrom failed: " << strerror(errno));
- }
-
- // Pass the message size back via the size parameter
- size = ret;
- }
-
-
- // Set up an IO Service queue using the specified address
- void setDNSService(const char& address) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
- }
-
- // Set up an IO Service queue using the "any" address, on IPv4 if
- // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
- void setDNSService(const bool use_ipv4, const bool use_ipv6) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
- NULL, NULL);
- }
-
- // Set up empty DNS Service
- // Set up an IO Service queue without any addresses
- void setDNSService() {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
- }
-
- // Run a simple server test, on either IPv4 or IPv6, and over either
- // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
- // start the IO Service queue. The UDPServer or TCPServer that was
- // created by setIOService() will receive the test packet and issue a
- // callback, which enables us to check that the data it received
- // matches what we sent.
- void doTest(const int family, const int protocol) {
- if (protocol == IPPROTO_UDP) {
- sendUDP(family);
- } else {
- sendTCP(family);
- }
-
- // There doesn't seem to be an effective test for the validity of
- // 'native'.
- // One thing we are sure is it must be different from our local socket.
- EXPECT_NE(sock_, callback_native_);
- EXPECT_EQ(protocol, callback_protocol_);
- EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
- callback_address_);
-
- const uint8_t* expected_data =
- protocol == IPPROTO_UDP ? test_data : test_data + 2;
- const size_t expected_datasize =
- protocol == IPPROTO_UDP ? sizeof(test_data) :
- sizeof(test_data) - 2;
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
- callback_data_.size(),
- expected_data, expected_datasize);
- }
-
-protected:
- // This is a nonfunctional mockup of a DNSServer object. Its purpose
- // is to resume after a recursive query or other asynchronous call
- // has completed.
- class MockServer : public DNSServer {
- public:
- explicit MockServer(IOService& io_service,
- SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL) :
- io_(io_service),
- message_(new Message(Message::PARSE)),
- answer_message_(new Message(Message::RENDER)),
- respbuf_(new OutputBuffer(0)),
- checkin_(checkin), lookup_(lookup), answer_(answer)
- {}
-
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0)
- {}
-
- void resume(const bool) {
- // should never be called in our tests
- }
-
- DNSServer* clone() {
- MockServer* s = new MockServer(*this);
- return (s);
- }
-
- inline void asyncLookup() {
- if (lookup_) {
- (*lookup_)(*io_message_, message_, answer_message_,
- respbuf_, this);
- }
- }
-
- protected:
- IOService& io_;
- bool done_;
-
- private:
- // Currently unused; these will be used for testing
- // asynchronous lookup calls via the asyncLookup() method
- boost::shared_ptr<asiolink::IOMessage> io_message_;
- isc::dns::MessagePtr message_;
- isc::dns::MessagePtr answer_message_;
- isc::dns::OutputBufferPtr respbuf_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_;
- const DNSLookup* lookup_;
- const DNSAnswer* answer_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- class MockServerStop : public MockServer {
- public:
- explicit MockServerStop(IOService& io_service, bool* done) :
- MockServer(io_service),
- done_(done)
- {}
-
- void resume(const bool done) {
- *done_ = done;
- io_.stop();
- }
-
- DNSServer* clone() {
- return (new MockServerStop(*this));
- }
- private:
- bool* done_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- // the second time. (Used in the clientTimeout test, where resume
- // is called initially with the error answer, and later when the
- // lookup times out, it is called without an answer to send back)
- class MockServerStop2 : public MockServer {
- public:
- explicit MockServerStop2(IOService& io_service,
- bool* done1, bool* done2) :
- MockServer(io_service),
- done1_(done1),
- done2_(done2),
- stopped_once_(false)
- {}
-
- void resume(const bool done) {
- if (stopped_once_) {
- *done2_ = done;
- io_.stop();
- } else {
- *done1_ = done;
- stopped_once_ = true;
- }
- }
-
- DNSServer* clone() {
- return (new MockServerStop2(*this));
- }
- private:
- bool* done1_;
- bool* done2_;
- bool stopped_once_;
- };
-
-private:
- class ASIOCallBack : public SimpleCallback {
- public:
- ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
- void operator()(const IOMessage& io_message) const {
- test_obj_->callBack(io_message);
- }
- private:
- ASIOLinkTest* test_obj_;
- };
- void callBack(const IOMessage& io_message) {
- callback_protocol_ = io_message.getSocket().getProtocol();
- callback_native_ = io_message.getSocket().getNative();
- callback_address_ =
- io_message.getRemoteEndpoint().getAddress().toText();
- callback_data_.assign(
- static_cast<const uint8_t*>(io_message.getData()),
- static_cast<const uint8_t*>(io_message.getData()) +
- io_message.getDataSize());
- io_service_->stop();
- }
-protected:
- // We use a pointer for io_service_, because for some tests we
- // need to recreate a new one within one onstance of this class
- IOService* io_service_;
- DNSService* dns_service_;
- ASIOCallBack* callback_;
- int callback_protocol_;
- int callback_native_;
- string callback_address_;
- vector<uint8_t> callback_data_;
- int sock_;
- struct addrinfo* res_;
-};
-
-ASIOLinkTest::ASIOLinkTest() :
- dns_service_(NULL), callback_(NULL), sock_(-1), res_(NULL)
-{
- io_service_ = new IOService();
- setDNSService(true, true);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSend) {
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSend) {
- doTest(AF_INET6, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSend) {
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSend) {
- doTest(AF_INET, IPPROTO_TCP);
-}
-
-TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
- // Explicitly set a specific address to be bound to the socket.
- // The subsequent test does not directly ensures the underlying socket
- // is bound to the expected address, but the success of the tests should
- // reasonably suggest it works as intended.
- // Specifying an address also implicitly means the service runs in a
- // single address-family mode. In tests using TCP we can confirm that
- // by trying to make a connection and seeing a failure. In UDP, it'd be
- // more complicated because we need to use a connected socket and catch
- // an error on a subsequent read operation. We could do it, but for
- // simplicity we only tests the easier cases for now.
-
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, v6AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, DISABLED_clearServers) {
- // FIXME: Enable when clearServers actually close the sockets
- // See #388
- setDNSService();
- dns_service_->clearServers();
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(ASIOLinkTest, v6TCPOnly) {
- // Open only IPv6 TCP socket. A subsequent attempt of establishing an
- // IPv4/TCP connection should fail. See above for why we only test this
- // for TCP.
- setDNSService(false, true);
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(ASIOLinkTest, v4TCPOnly) {
- setDNSService(true, false);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-vector<pair<string, uint16_t> >
-singleAddress(const string &address, uint16_t port) {
- vector<pair<string, uint16_t> > result;
- result.push_back(pair<string, uint16_t>(address, port));
- return (result);
-}
-
-TEST_F(ASIOLinkTest, recursiveSetupV4) {
- setDNSService(true, false);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port)));
-}
-
-TEST_F(ASIOLinkTest, recursiveSetupV6) {
- setDNSService(false, true);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV6_ADDR, port),
- singleAddress(TEST_IPV6_ADDR,port)));
-}
-
-// XXX:
-// This is very inadequate unit testing. It should be generalized into
-// a routine that can do this with variable address family, address, and
-// port, and with the various callbacks defined in such a way as to ensure
-// full code coverage including error cases.
-TEST_F(ASIOLinkTest, forwarderSend) {
- setDNSService(true, false);
-
- // Note: We use the test prot plus one to ensure we aren't binding
- // to the same port as the actual server
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-
- MockServer server(*io_service_);
- RecursiveQuery rq(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port));
-
- Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
-
- char data[4096];
- size_t size = sizeof(data);
- ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
-
- Message m(Message::PARSE);
- InputBuffer ibuf(data, size);
-
- // Make sure we can parse the message that was sent
- EXPECT_NO_THROW(m.parseHeader(ibuf));
- EXPECT_NO_THROW(m.fromWire(ibuf));
-
- // Check that the question sent matches the one we wanted
- QuestionPtr q2 = *m.beginQuestion();
- EXPECT_EQ(q.getName(), q2->getName());
- EXPECT_EQ(q.getType(), q2->getType());
- EXPECT_EQ(q.getClass(), q2->getClass());
-}
-
-int
-createTestSocket()
-{
- struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
- int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to bind test socket");
- }
- return sock_;
-}
-
-int
-setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
- const struct timeval timeo = { tv_sec, tv_usec };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
- if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP()
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- return recv_options;
-}
-
-// try to read from the socket max time
-// *num is incremented for every succesfull read
-// returns true if it can read max times, false otherwise
-bool tryRead(int sock_, int recv_options, size_t max, int* num) {
- size_t i = 0;
- do {
- char inbuff[512];
- if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
- return false;
- } else {
- ++i;
- ++*num;
- }
- } while (i < max);
- return true;
-}
-
-
-// Test it tries the correct amount of times before giving up
-TEST_F(ASIOLinkTest, forwardQueryTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 10, 4000, 3000, 2);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
- // block (see also recvUDP()).
- int recv_options = setSocketTimeout(sock_, 10, 0);
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 3, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set client timeout to lower than querytimeout, we should
-// get a failure answer, but still see retries
-// (no actual answer is given here yet)
-TEST_F(ASIOLinkTest, forwardClientTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done1(true);
- bool done2(true);
- MockServerStop2 server(*io_service_, &done1, &done2);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set it up to retry twice before client timeout fires
- // Since the lookup timer has not fired, it should retry
- // four times
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 50, 120, 1000, 4);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // we know it'll fail, so make it a shorter timeout
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail, but we should have kept on trying
- EXPECT_TRUE(done1);
- EXPECT_FALSE(done2);
- EXPECT_EQ(5, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set lookup timeout to lower than querytimeout*retries, we should
-// fail before the full amount of retries
-TEST_F(ASIOLinkTest, forwardLookupTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set up the test so that it will retry 5 times, but the lookup
- // timeout will fire after only 3 normal timeouts
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 50, 4000, 120, 5);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- query.resolve(question, answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
-}
-
-// as mentioned above, we need a more better framework for this,
-// in addition to that, this sends out queries into the world
-// (which we should catch somehow and fake replies for)
-// for the skeleton code, it shouldn't be too much of a problem
-// Ok so even we don't all have access to the DNS world right now,
-// so disabling these tests too.
-TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
- ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
- RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
- EXPECT_EQ(q.getName(), a->getName());
- EXPECT_EQ(q.getType(), a->getType());
- EXPECT_EQ(q.getClass(), a->getClass());
- EXPECT_EQ(1, a->getRdataCount());
-}
-
-// see comments at previous test
-TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
- EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
-}
-
-
-
-// This fixture is for testing IntervalTimer. Some callback functors are
-// registered as callback function of the timer to test if they are called
-// or not.
-class IntervalTimerTest : public ::testing::Test {
-protected:
- IntervalTimerTest() : io_service_() {}
- ~IntervalTimerTest() {}
- class TimerCallBack : public std::unary_function<void, void> {
- public:
- TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
- void operator()() const {
- test_obj_->timer_called_ = true;
- test_obj_->io_service_.stop();
- return;
- }
- private:
- IntervalTimerTest* test_obj_;
- };
- class TimerCallBackCounter : public std::unary_function<void, void> {
- public:
- TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
- counter_ = 0;
- }
- void operator()() {
- ++counter_;
- return;
- }
- int counter_;
- private:
- IntervalTimerTest* test_obj_;
- };
- class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
- public:
- TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
- IntervalTimer* timer,
- TimerCallBackCounter& counter)
- : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
- {}
- void operator()() {
- ++count_;
- if (count_ == 1) {
- // First time of call back.
- // Store the value of counter_.counter_.
- prev_counter_ = counter_.counter_;
- delete timer_;
- } else if (count_ == 2) {
- // Second time of call back.
- // Stop io_service to stop all timers.
- test_obj_->io_service_.stop();
- // Compare the value of counter_.counter_ with stored one.
- // If TimerCallBackCounter was not called (expected behavior),
- // they are same.
- if (counter_.counter_ == prev_counter_) {
- test_obj_->timer_cancel_success_ = true;
- }
- }
- return;
- }
- private:
- IntervalTimerTest* test_obj_;
- IntervalTimer* timer_;
- TimerCallBackCounter& counter_;
- int count_;
- int prev_counter_;
- };
- class TimerCallBackCanceller {
- public:
- TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
- counter_(counter), itimer_(itimer)
- {}
- void operator()() {
- ++counter_;
- itimer_.cancel();
- }
- private:
- unsigned int& counter_;
- IntervalTimer& itimer_;
- };
- class TimerCallBackOverwriter : public std::unary_function<void, void> {
- public:
- TimerCallBackOverwriter(IntervalTimerTest* test_obj,
- IntervalTimer& timer)
- : test_obj_(test_obj), timer_(timer), count_(0)
- {}
- void operator()() {
- ++count_;
- if (count_ == 1) {
- // First time of call back.
- // Call setup() to update callback function to TimerCallBack.
- test_obj_->timer_called_ = false;
- timer_.setup(TimerCallBack(test_obj_), 100);
- } else if (count_ == 2) {
- // Second time of call back.
- // If it reaches here, re-setup() is failed (unexpected).
- // We should stop here.
- test_obj_->io_service_.stop();
- }
- return;
- }
- private:
- IntervalTimerTest* test_obj_;
- IntervalTimer& timer_;
- int count_;
- };
-protected:
- IOService io_service_;
- bool timer_called_;
- bool timer_cancel_success_;
-};
-
-TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
- // Create asio_link::IntervalTimer and setup.
- IntervalTimer itimer(io_service_);
- // 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(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
-}
-
-TEST_F(IntervalTimerTest, startIntervalTimer) {
- // Create asio_link::IntervalTimer and setup.
- // Then run IOService and test if the callback function is called.
- IntervalTimer itimer(io_service_);
- timer_called_ = false;
- // store start time
- boost::posix_time::ptime start;
- start = boost::posix_time::microsec_clock::universal_time();
- // setup timer
- itimer.setup(TimerCallBack(this), 100);
- EXPECT_EQ(100, itimer.getInterval());
- io_service_.run();
- // reaches here after timer expired
- // delta: difference between elapsed time and 100 milliseconds.
- boost::posix_time::time_duration delta =
- (boost::posix_time::microsec_clock::universal_time() - start)
- - boost::posix_time::millisec(100);
- if (delta.is_negative()) {
- delta.invert_sign();
- }
- // expect TimerCallBack is called; timer_called_ is true
- EXPECT_TRUE(timer_called_);
- // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
- EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
-}
-
-TEST_F(IntervalTimerTest, destructIntervalTimer) {
- // This code isn't exception safe, but we'd rather keep the code
- // simpler and more readable as this is only for tests and if it throws
- // the program would immediately terminate anyway.
-
- // The call back function will not be called after the timer is
- // destroyed.
- //
- // There are two timers:
- // itimer_counter (A)
- // (Calls TimerCallBackCounter)
- // - increments internal counter in callback function
- // itimer_canceller (B)
- // (Calls TimerCallBackCancelDeleter)
- // - first time of callback, it stores the counter value of
- // callback_canceller and destroys itimer_counter
- // - second time of callback, it compares the counter value of
- // callback_canceller with stored value
- // if they are same the timer was not called; expected result
- // if they are different the timer was called after destroyed
- //
- // 0 100 200 300 400 500 600 (ms)
- // (A) i--------+----x
- // ^
- // |destroy itimer_counter
- // (B) i-------------+--------------s
- // ^stop io_service
- // and check if itimer_counter have been
- // stopped
-
- // itimer_counter will be deleted in TimerCallBackCancelDeleter
- IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
- IntervalTimer itimer_canceller(io_service_);
- timer_cancel_success_ = false;
- TimerCallBackCounter callback_canceller(this);
- itimer_counter->setup(callback_canceller, 200);
- itimer_canceller.setup(
- TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
- 300);
- io_service_.run();
- EXPECT_TRUE(timer_cancel_success_);
-}
-
-TEST_F(IntervalTimerTest, cancel) {
- // Similar to destructIntervalTimer test, but the first timer explicitly
- // cancels itself on first callback.
- IntervalTimer itimer_counter(io_service_);
- IntervalTimer itimer_watcher(io_service_);
- unsigned int counter = 0;
- itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
- itimer_watcher.setup(TimerCallBack(this), 200);
- io_service_.run();
- EXPECT_EQ(1, counter);
- EXPECT_EQ(0, itimer_counter.getInterval());
-
- // canceling an already canceled timer shouldn't cause any surprise.
- EXPECT_NO_THROW(itimer_counter.cancel());
-}
-
-TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
- // Calling setup() multiple times updates call back function and interval.
- //
- // There are two timers:
- // itimer (A)
- // (Calls TimerCallBackCounter / TimerCallBack)
- // - increments internal counter in callback function
- // (TimerCallBackCounter)
- // interval: 300 milliseconds
- // - io_service_.stop() (TimerCallBack)
- // interval: 100 milliseconds
- // itimer_overwriter (B)
- // (Calls TimerCallBackOverwriter)
- // - first time of callback, it calls setup() to change call back
- // function to TimerCallBack and interval of itimer to 100
- // milliseconds
- // after 300 + 100 milliseconds from the beginning of this test,
- // TimerCallBack() will be called and io_service_ stops.
- // - second time of callback, it means the test fails.
- //
- // 0 100 200 300 400 500 600 700 800 (ms)
- // (A) i-------------+----C----s
- // ^ ^stop io_service
- // |change call back function
- // (B) i------------------+-------------------S
- // ^(stop io_service on fail)
- //
-
- IntervalTimer itimer(io_service_);
- IntervalTimer itimer_overwriter(io_service_);
- // store start time
- boost::posix_time::ptime start;
- start = boost::posix_time::microsec_clock::universal_time();
- itimer.setup(TimerCallBackCounter(this), 300);
- itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
- io_service_.run();
- // reaches here after timer expired
- // if interval is updated, it takes
- // 400 milliseconds for TimerCallBackOverwriter
- // + 100 milliseconds for TimerCallBack (stop)
- // = 500 milliseconds.
- // otherwise (test fails), it takes
- // 400 milliseconds for TimerCallBackOverwriter
- // + 400 milliseconds for TimerCallBackOverwriter (stop)
- // = 800 milliseconds.
- // delta: difference between elapsed time and 400 + 100 milliseconds
- boost::posix_time::time_duration delta =
- (boost::posix_time::microsec_clock::universal_time() - start)
- - boost::posix_time::millisec(400 + 100);
- if (delta.is_negative()) {
- delta.invert_sign();
- }
- // expect callback function is updated: TimerCallBack is called
- EXPECT_TRUE(timer_called_);
- // expect interval is updated
- EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
-}
-
-}
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
new file mode 100644
index 0000000..9a7b0f4
--- /dev/null
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -0,0 +1,292 @@
+// Copyright (C) 2011 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/asiolink.h>
+
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace {
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+ boost::posix_time::milliseconds(50);
+}
+
+using namespace asiolink;
+
+// This fixture is for testing IntervalTimer. Some callback functors are
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+ IntervalTimerTest() : io_service_() {}
+ ~IntervalTimerTest() {}
+ class TimerCallBack : public std::unary_function<void, void> {
+ public:
+ TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+ void operator()() const {
+ test_obj_->timer_called_ = true;
+ test_obj_->io_service_.stop();
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCounter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCounter(IntervalTimerTest* test_obj) : test_obj_(test_obj) {
+ counter_ = 0;
+ }
+ void operator()() {
+ ++counter_;
+ return;
+ }
+ int counter_;
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCancelDeleter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+ IntervalTimer* timer,
+ TimerCallBackCounter& counter)
+ : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Store the value of counter_.counter_.
+ prev_counter_ = counter_.counter_;
+ delete timer_;
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // Stop io_service to stop all timers.
+ test_obj_->io_service_.stop();
+ // Compare the value of counter_.counter_ with stored one.
+ // If TimerCallBackCounter was not called (expected behavior),
+ // they are same.
+ if (counter_.counter_ == prev_counter_) {
+ test_obj_->timer_cancel_success_ = true;
+ }
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer* timer_;
+ TimerCallBackCounter& counter_;
+ int count_;
+ int prev_counter_;
+ };
+ class TimerCallBackCanceller {
+ public:
+ TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+ counter_(counter), itimer_(itimer)
+ {}
+ void operator()() {
+ ++counter_;
+ itimer_.cancel();
+ }
+ private:
+ unsigned int& counter_;
+ IntervalTimer& itimer_;
+ };
+ class TimerCallBackOverwriter : public std::unary_function<void, void> {
+ public:
+ TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+ IntervalTimer& timer)
+ : test_obj_(test_obj), timer_(timer), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Call setup() to update callback function to TimerCallBack.
+ test_obj_->timer_called_ = false;
+ timer_.setup(TimerCallBack(test_obj_), 100);
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // If it reaches here, re-setup() is failed (unexpected).
+ // We should stop here.
+ test_obj_->io_service_.stop();
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer& timer_;
+ int count_;
+ };
+protected:
+ IOService io_service_;
+ bool timer_called_;
+ bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ IntervalTimer itimer(io_service_);
+ // 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(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ // Then run IOService and test if the callback function is called.
+ IntervalTimer itimer(io_service_);
+ timer_called_ = false;
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ // setup timer
+ itimer.setup(TimerCallBack(this), 100);
+ EXPECT_EQ(100, itimer.getInterval());
+ io_service_.run();
+ // reaches here after timer expired
+ // delta: difference between elapsed time and 100 milliseconds.
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::millisec(100);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect TimerCallBack is called; timer_called_ is true
+ EXPECT_TRUE(timer_called_);
+ // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // The call back function will not be called after the timer is
+ // destroyed.
+ //
+ // There are two timers:
+ // itimer_counter (A)
+ // (Calls TimerCallBackCounter)
+ // - increments internal counter in callback function
+ // itimer_canceller (B)
+ // (Calls TimerCallBackCancelDeleter)
+ // - first time of callback, it stores the counter value of
+ // callback_canceller and destroys itimer_counter
+ // - second time of callback, it compares the counter value of
+ // callback_canceller with stored value
+ // if they are same the timer was not called; expected result
+ // if they are different the timer was called after destroyed
+ //
+ // 0 100 200 300 400 500 600 (ms)
+ // (A) i--------+----x
+ // ^
+ // |destroy itimer_counter
+ // (B) i-------------+--------------s
+ // ^stop io_service
+ // and check if itimer_counter have been
+ // stopped
+
+ // itimer_counter will be deleted in TimerCallBackCancelDeleter
+ IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+ IntervalTimer itimer_canceller(io_service_);
+ timer_cancel_success_ = false;
+ TimerCallBackCounter callback_canceller(this);
+ itimer_counter->setup(callback_canceller, 200);
+ itimer_canceller.setup(
+ TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
+ 300);
+ io_service_.run();
+ EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, cancel) {
+ // Similar to destructIntervalTimer test, but the first timer explicitly
+ // cancels itself on first callback.
+ IntervalTimer itimer_counter(io_service_);
+ IntervalTimer itimer_watcher(io_service_);
+ unsigned int counter = 0;
+ itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
+ itimer_watcher.setup(TimerCallBack(this), 200);
+ io_service_.run();
+ EXPECT_EQ(1, counter);
+ EXPECT_EQ(0, itimer_counter.getInterval());
+
+ // canceling an already canceled timer shouldn't cause any surprise.
+ EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+ // Calling setup() multiple times updates call back function and interval.
+ //
+ // There are two timers:
+ // itimer (A)
+ // (Calls TimerCallBackCounter / TimerCallBack)
+ // - increments internal counter in callback function
+ // (TimerCallBackCounter)
+ // interval: 300 milliseconds
+ // - io_service_.stop() (TimerCallBack)
+ // interval: 100 milliseconds
+ // itimer_overwriter (B)
+ // (Calls TimerCallBackOverwriter)
+ // - first time of callback, it calls setup() to change call back
+ // function to TimerCallBack and interval of itimer to 100
+ // milliseconds
+ // after 300 + 100 milliseconds from the beginning of this test,
+ // TimerCallBack() will be called and io_service_ stops.
+ // - second time of callback, it means the test fails.
+ //
+ // 0 100 200 300 400 500 600 700 800 (ms)
+ // (A) i-------------+----C----s
+ // ^ ^stop io_service
+ // |change call back function
+ // (B) i------------------+-------------------S
+ // ^(stop io_service on fail)
+ //
+
+ IntervalTimer itimer(io_service_);
+ IntervalTimer itimer_overwriter(io_service_);
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ itimer.setup(TimerCallBackCounter(this), 300);
+ itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
+ io_service_.run();
+ // reaches here after timer expired
+ // if interval is updated, it takes
+ // 400 milliseconds for TimerCallBackOverwriter
+ // + 100 milliseconds for TimerCallBack (stop)
+ // = 500 milliseconds.
+ // otherwise (test fails), it takes
+ // 400 milliseconds for TimerCallBackOverwriter
+ // + 400 milliseconds for TimerCallBackOverwriter (stop)
+ // = 800 milliseconds.
+ // delta: difference between elapsed time and 400 + 100 milliseconds
+ boost::posix_time::time_duration delta =
+ (boost::posix_time::microsec_clock::universal_time() - start)
+ - boost::posix_time::millisec(400 + 100);
+ if (delta.is_negative()) {
+ delta.invert_sign();
+ }
+ // expect callback function is updated: TimerCallBack is called
+ EXPECT_TRUE(timer_called_);
+ // expect interval is updated
+ EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+}
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
new file mode 100644
index 0000000..49aa67e
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2011 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+
+TEST(IOServiceTest, badPort) {
+ IOService io_service;
+ EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"5300.0", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, badAddress) {
+ IOService io_service;
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.1.1", NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"2001:db8:::1", NULL, NULL, NULL), IOError);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"localhost", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, unavailableAddress) {
+ IOService io_service;
+ // These addresses should generally be unavailable as a valid local
+ // address, although there's no guarantee in theory.
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"192.0.2.0", NULL, NULL, NULL), IOError);
+
+ // Some OSes would simply reject binding attempt for an AF_INET6 socket
+ // to an IPv4-mapped IPv6 address. Even if those that allow it, since
+ // the corresponding IPv4 address is the same as the one used in the
+ // AF_INET socket case above, it should at least show the same result
+ // as the previous one.
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:192.0.2.0", NULL, NULL, NULL), IOError);
+}
+
+TEST(IOServiceTest, duplicateBind_v6) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv6, "any" address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, false, true, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v6_address) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv6, specific address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV6_ADDR, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv4, "any" address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, true, false, NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+}
+
+TEST(IOServiceTest, duplicateBind_v4_address) {
+ // In each sub test case, second attempt should fail due to duplicate bind
+ IOService io_service;
+
+ // IPv4, specific address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *TEST_IPV4_ADDR, NULL, NULL, NULL), IOError);
+ delete dns_service;
+}
+
+// Disabled because IPv4-mapped addresses don't seem to be working with
+// the IOService constructor
+TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
+ IOService io_service;
+ // Duplicate bind on IPv4-mapped IPv6 address
+ DNSService* dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
+ delete dns_service;
+
+ // XXX:
+ // Currently, this throws an "invalid argument" exception. I have
+ // not been able to get IPv4-mapped addresses to work.
+ dns_service = new DNSService(io_service, *TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
+ EXPECT_THROW(DNSService(io_service, *TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
+ delete dns_service;
+}
+
diff --git a/src/lib/asiolink/tests/ioaddress_unittest.cc b/src/lib/asiolink/tests/ioaddress_unittest.cc
new file mode 100644
index 0000000..57c9496
--- /dev/null
+++ b/src/lib/asiolink/tests/ioaddress_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (C) 2011 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+TEST(IOAddressTest, fromText) {
+ IOAddress io_address_v4("192.0.2.1");
+ EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+ IOAddress io_address_v6("2001:db8::1234");
+ EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+ // bogus IPv4 address-like input
+ EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+ // bogus IPv4 address-like input: out-of-range octet
+ EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
+}
+
+TEST(IOAddressTest, Equality) {
+ EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
+
+ EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
+ EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
+}
diff --git a/src/lib/asiolink/tests/ioendpoint_unittest.cc b/src/lib/asiolink/tests/ioendpoint_unittest.cc
new file mode 100644
index 0000000..a5b5cd2
--- /dev/null
+++ b/src/lib/asiolink/tests/ioendpoint_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (C) 2011 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+TEST(IOEndpointTest, createUDPv4) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(5300, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv4) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(5301, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createUDPv6) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5302, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_UDP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv6) {
+ const IOEndpoint* ep;
+ ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5303, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createIPProto) {
+ EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
+ 5300)->getAddress().toText(),
+ IOError);
+}
+
diff --git a/src/lib/asiolink/tests/iosocket_unittest.cc b/src/lib/asiolink/tests/iosocket_unittest.cc
new file mode 100644
index 0000000..06ae861
--- /dev/null
+++ b/src/lib/asiolink/tests/iosocket_unittest.cc
@@ -0,0 +1,29 @@
+// Copyright (C) 2011 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/asiolink.h>
+
+using namespace asiolink;
+
+TEST(IOSocketTest, dummySockets) {
+ EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
+ EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
+ EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+ EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+
diff --git a/src/lib/asiolink/tests/recursive_query_unittest.cc b/src/lib/asiolink/tests/recursive_query_unittest.cc
new file mode 100644
index 0000000..fc64452
--- /dev/null
+++ b/src/lib/asiolink/tests/recursive_query_unittest.cc
@@ -0,0 +1,791 @@
+// Copyright (C) 2011 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 <config.h>
+
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <string.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/rcode.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
+// In particular, we must not include asio.hpp in this file.
+// The asiolink module is primarily intended to be a wrapper that hide the
+// details of the underlying implementations. We need to test the wrapper
+// level behaviors. In addition, some compilers reject to compile this file
+// if we include asio.hpp unless we specify a special compiler option.
+// If we need to test something at the level of underlying ASIO and need
+// their definition, that test should go to asiolink/internal/tests.
+#include <asiolink/asiolink.h>
+#include <asiolink/iosocket.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace asiolink;
+using namespace isc::dns;
+
+namespace {
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+// This data is intended to be valid as a DNS/TCP-like message: the first
+// two octets encode the length of the rest of the data. This is crucial
+// for the tests below.
+const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+
+// This function returns an addrinfo structure for use by tests, using
+// different addresses and ports depending on whether we're testing
+// IPv4 or v6, TCP or UDP, and client or server operation.
+struct addrinfo*
+resolveAddress(const int family, const int protocol, const bool client) {
+ const char* const addr = (family == AF_INET6) ?
+ TEST_IPV6_ADDR : TEST_IPV4_ADDR;
+ const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+ hints.ai_protocol = protocol;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ struct addrinfo* res;
+ const int error = getaddrinfo(addr, port, &hints, &res);
+ if (error != 0) {
+ isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+ }
+
+ return (res);
+}
+
+// This fixture is a framework for various types of network operations
+// using the ASIO interfaces. Each test case creates an IOService object,
+// opens a local "client" socket for testing, sends data via the local socket
+// to the service that would run in the IOService object.
+// A mock callback function (an ASIOCallBack object) is registered with the
+// IOService object, so the test code should be able to examine the data
+// received on the server side. It then checks the received data matches
+// expected parameters.
+// If initialization parameters of the IOService should be modified, the test
+// case can do it using the setDNSService() method.
+// Note: the set of tests in RecursiveQueryTest use actual network services and may
+// involve undesirable side effects such as blocking.
+class RecursiveQueryTest : public ::testing::Test {
+protected:
+ RecursiveQueryTest();
+ ~RecursiveQueryTest() {
+ if (res_ != NULL) {
+ freeaddrinfo(res_);
+ }
+ if (sock_ != -1) {
+ close(sock_);
+ }
+ delete dns_service_;
+ delete callback_;
+ delete io_service_;
+ }
+
+ // Send a test UDP packet to a mock server
+ void sendUDP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_UDP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
+ res_->ai_addr, res_->ai_addrlen);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected sendto result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Send a test TCP packet to a mock server
+ void sendTCP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_TCP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to connect to the test server");
+ }
+ const int cc = send(sock_, test_data, sizeof(test_data), 0);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected send result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Receive a UDP packet from a mock server; used for testing
+ // recursive lookup. The caller must place a RecursiveQuery
+ // on the IO Service queue before running this routine.
+ void recvUDP(const int family, void* buffer, size_t& size) {
+ res_ = resolveAddress(family, IPPROTO_UDP, true);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "bind failed: " << strerror(errno));
+ }
+
+ // The IO service queue should have a RecursiveQuery object scheduled
+ // to run at this point. This call will cause it to begin an
+ // async send, then return.
+ io_service_->run_one();
+
+ // ... and this one will block until the send has completed
+ io_service_->run_one();
+
+ // Now we attempt to recv() whatever was sent.
+ // XXX: there's no guarantee the receiving socket can immediately get
+ // the packet. Normally we can perform blocking recv to wait for it,
+ // but in theory it's even possible that the packet is lost.
+ // In order to prevent the test from hanging in such a worst case
+ // we add an ad hoc timeout.
+ const struct timeval timeo = { 10, 0 };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
+ sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) {
+ // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
+ // with the error of ENOPROTOOPT. Since this is a workaround
+ // for rare error cases anyway, we simply switch to the
+ // "don't wait" mode. If we still find an error in recv()
+ // can happen often we'll consider a more complete solution.
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ const int ret = recv(sock_, buffer, size, recv_options);
+ if (ret < 0) {
+ isc_throw(IOError, "recvfrom failed: " << strerror(errno));
+ }
+
+ // Pass the message size back via the size parameter
+ size = ret;
+ }
+
+
+ // Set up an IO Service queue using the specified address
+ void setDNSService(const char& address) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
+ }
+
+ // Set up an IO Service queue using the "any" address, on IPv4 if
+ // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
+ void setDNSService(const bool use_ipv4, const bool use_ipv6) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
+ NULL, NULL);
+ }
+
+ // Set up empty DNS Service
+ // Set up an IO Service queue without any addresses
+ void setDNSService() {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
+ }
+
+ // Run a simple server test, on either IPv4 or IPv6, and over either
+ // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
+ // start the IO Service queue. The UDPServer or TCPServer that was
+ // created by setIOService() will receive the test packet and issue a
+ // callback, which enables us to check that the data it received
+ // matches what we sent.
+ void doTest(const int family, const int protocol) {
+ if (protocol == IPPROTO_UDP) {
+ sendUDP(family);
+ } else {
+ sendTCP(family);
+ }
+
+ // There doesn't seem to be an effective test for the validity of
+ // 'native'.
+ // One thing we are sure is it must be different from our local socket.
+ EXPECT_NE(sock_, callback_native_);
+ EXPECT_EQ(protocol, callback_protocol_);
+ EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
+ callback_address_);
+
+ const uint8_t* expected_data =
+ protocol == IPPROTO_UDP ? test_data : test_data + 2;
+ const size_t expected_datasize =
+ protocol == IPPROTO_UDP ? sizeof(test_data) :
+ sizeof(test_data) - 2;
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
+ callback_data_.size(),
+ expected_data, expected_datasize);
+ }
+
+protected:
+ // This is a nonfunctional mockup of a DNSServer object. Its purpose
+ // is to resume after a recursive query or other asynchronous call
+ // has completed.
+ class MockServer : public DNSServer {
+ public:
+ explicit MockServer(IOService& io_service,
+ SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL) :
+ io_(io_service),
+ message_(new Message(Message::PARSE)),
+ answer_message_(new Message(Message::RENDER)),
+ respbuf_(new OutputBuffer(0)),
+ checkin_(checkin), lookup_(lookup), answer_(answer)
+ {}
+
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {}
+
+ void resume(const bool) {
+ // should never be called in our tests
+ }
+
+ DNSServer* clone() {
+ MockServer* s = new MockServer(*this);
+ return (s);
+ }
+
+ inline void asyncLookup() {
+ if (lookup_) {
+ (*lookup_)(*io_message_, message_, answer_message_,
+ respbuf_, this);
+ }
+ }
+
+ protected:
+ IOService& io_;
+ bool done_;
+
+ private:
+ // Currently unused; these will be used for testing
+ // asynchronous lookup calls via the asyncLookup() method
+ boost::shared_ptr<asiolink::IOMessage> io_message_;
+ isc::dns::MessagePtr message_;
+ isc::dns::MessagePtr answer_message_;
+ isc::dns::OutputBufferPtr respbuf_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_;
+ const DNSLookup* lookup_;
+ const DNSAnswer* answer_;
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ class MockServerStop : public MockServer {
+ public:
+ explicit MockServerStop(IOService& io_service, bool* done) :
+ MockServer(io_service),
+ done_(done)
+ {}
+
+ void resume(const bool done) {
+ *done_ = done;
+ io_.stop();
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop(*this));
+ }
+ private:
+ bool* done_;
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ // the second time. (Used in the clientTimeout test, where resume
+ // is called initially with the error answer, and later when the
+ // lookup times out, it is called without an answer to send back)
+ class MockServerStop2 : public MockServer {
+ public:
+ explicit MockServerStop2(IOService& io_service,
+ bool* done1, bool* done2) :
+ MockServer(io_service),
+ done1_(done1),
+ done2_(done2),
+ stopped_once_(false)
+ {}
+
+ void resume(const bool done) {
+ if (stopped_once_) {
+ *done2_ = done;
+ io_.stop();
+ } else {
+ *done1_ = done;
+ stopped_once_ = true;
+ }
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop2(*this));
+ }
+ private:
+ bool* done1_;
+ bool* done2_;
+ bool stopped_once_;
+ };
+
+private:
+ class ASIOCallBack : public SimpleCallback {
+ public:
+ ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
+ void operator()(const IOMessage& io_message) const {
+ test_obj_->callBack(io_message);
+ }
+ private:
+ RecursiveQueryTest* test_obj_;
+ };
+ void callBack(const IOMessage& io_message) {
+ callback_protocol_ = io_message.getSocket().getProtocol();
+ callback_native_ = io_message.getSocket().getNative();
+ callback_address_ =
+ io_message.getRemoteEndpoint().getAddress().toText();
+ callback_data_.assign(
+ static_cast<const uint8_t*>(io_message.getData()),
+ static_cast<const uint8_t*>(io_message.getData()) +
+ io_message.getDataSize());
+ io_service_->stop();
+ }
+protected:
+ // We use a pointer for io_service_, because for some tests we
+ // need to recreate a new one within one onstance of this class
+ IOService* io_service_;
+ DNSService* dns_service_;
+ ASIOCallBack* callback_;
+ int callback_protocol_;
+ int callback_native_;
+ string callback_address_;
+ vector<uint8_t> callback_data_;
+ int sock_;
+ struct addrinfo* res_;
+};
+
+RecursiveQueryTest::RecursiveQueryTest() :
+ dns_service_(NULL), callback_(NULL), sock_(-1), res_(NULL)
+{
+ io_service_ = new IOService();
+ setDNSService(true, true);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSend) {
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSend) {
+ doTest(AF_INET6, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSend) {
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSend) {
+ doTest(AF_INET, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
+ // Explicitly set a specific address to be bound to the socket.
+ // The subsequent test does not directly ensures the underlying socket
+ // is bound to the expected address, but the success of the tests should
+ // reasonably suggest it works as intended.
+ // Specifying an address also implicitly means the service runs in a
+ // single address-family mode. In tests using TCP we can confirm that
+ // by trying to make a connection and seeing a failure. In UDP, it'd be
+ // more complicated because we need to use a connected socket and catch
+ // an error on a subsequent read operation. We could do it, but for
+ // simplicity we only tests the easier cases for now.
+
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, DISABLED_clearServers) {
+ // FIXME: Enable when clearServers actually close the sockets
+ // See #388
+ setDNSService();
+ dns_service_->clearServers();
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPOnly) {
+ // Open only IPv6 TCP socket. A subsequent attempt of establishing an
+ // IPv4/TCP connection should fail. See above for why we only test this
+ // for TCP.
+ setDNSService(false, true);
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPOnly) {
+ setDNSService(true, false);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+vector<pair<string, uint16_t> >
+singleAddress(const string &address, uint16_t port) {
+ vector<pair<string, uint16_t> > result;
+ result.push_back(pair<string, uint16_t>(address, port));
+ return (result);
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV4) {
+ setDNSService(true, false);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port)));
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV6) {
+ setDNSService(false, true);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ singleAddress(TEST_IPV6_ADDR, port),
+ singleAddress(TEST_IPV6_ADDR,port)));
+}
+
+// XXX:
+// This is very inadequate unit testing. It should be generalized into
+// a routine that can do this with variable address family, address, and
+// port, and with the various callbacks defined in such a way as to ensure
+// full code coverage including error cases.
+TEST_F(RecursiveQueryTest, forwarderSend) {
+ setDNSService(true, false);
+
+ // Note: We use the test prot plus one to ensure we aren't binding
+ // to the same port as the actual server
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+
+ MockServer server(*io_service_);
+ RecursiveQuery rq(*dns_service_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port));
+
+ Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+
+ char data[4096];
+ size_t size = sizeof(data);
+ ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
+
+ Message m(Message::PARSE);
+ InputBuffer ibuf(data, size);
+
+ // Make sure we can parse the message that was sent
+ EXPECT_NO_THROW(m.parseHeader(ibuf));
+ EXPECT_NO_THROW(m.fromWire(ibuf));
+
+ // Check that the question sent matches the one we wanted
+ QuestionPtr q2 = *m.beginQuestion();
+ EXPECT_EQ(q.getName(), q2->getName());
+ EXPECT_EQ(q.getType(), q2->getType());
+ EXPECT_EQ(q.getClass(), q2->getClass());
+}
+
+int
+createTestSocket()
+{
+ struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
+ int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to bind test socket");
+ }
+ return sock_;
+}
+
+int
+setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
+ const struct timeval timeo = { tv_sec, tv_usec };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ return recv_options;
+}
+
+// try to read from the socket max time
+// *num is incremented for every succesfull read
+// returns true if it can read max times, false otherwise
+bool tryRead(int sock_, int recv_options, size_t max, int* num) {
+ size_t i = 0;
+ do {
+ char inbuff[512];
+ if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
+ return false;
+ } else {
+ ++i;
+ ++*num;
+ }
+ } while (i < max);
+ return true;
+}
+
+
+// Test it tries the correct amount of times before giving up
+TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ RecursiveQuery query(*dns_service_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 10, 4000, 3000, 2);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
+ // block (see also recvUDP()).
+ int recv_options = setSocketTimeout(sock_, 10, 0);
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 3, &num);
+
+ // The query should fail
+ EXPECT_FALSE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_TRUE(read_success);
+}
+
+// If we set client timeout to lower than querytimeout, we should
+// get a failure answer, but still see retries
+// (no actual answer is given here yet)
+TEST_F(RecursiveQueryTest, forwardClientTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done1(true);
+ bool done2(true);
+ MockServerStop2 server(*io_service_, &done1, &done2);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set it up to retry twice before client timeout fires
+ // Since the lookup timer has not fired, it should retry
+ // four times
+ RecursiveQuery query(*dns_service_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 50, 120, 1000, 4);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // we know it'll fail, so make it a shorter timeout
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail, but we should have kept on trying
+ EXPECT_TRUE(done1);
+ EXPECT_FALSE(done2);
+ EXPECT_EQ(5, num);
+ EXPECT_TRUE(read_success);
+}
+
+// If we set lookup timeout to lower than querytimeout*retries, we should
+// fail before the full amount of retries
+TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 50, 4000, 120, 5);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail
+ EXPECT_FALSE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// as mentioned above, we need a more better framework for this,
+// in addition to that, this sends out queries into the world
+// (which we should catch somehow and fake replies for)
+// for the skeleton code, it shouldn't be too much of a problem
+// Ok so even we don't all have access to the DNS world right now,
+// so disabling these tests too.
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
+
+ Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
+ ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
+ RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(q.getName(), a->getName());
+ EXPECT_EQ(q.getType(), a->getType());
+ EXPECT_EQ(q.getClass(), a->getClass());
+ EXPECT_EQ(1, a->getRdataCount());
+}
+
+// see comments at previous test
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
+
+ Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
+ EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
+}
+
+}
diff --git a/src/lib/asiolink/tests/udp_query_unittest.cc b/src/lib/asiolink/tests/udp_query_unittest.cc
new file mode 100644
index 0000000..9eb1aba
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_query_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2011 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 <gtest/gtest.h>
+#include <asio.hpp>
+#include <boost/bind.hpp>
+#include <cstdlib>
+
+#include <dns/question.h>
+
+#include <asiolink/udp_query.h>
+
+using namespace asio;
+using namespace isc::dns;
+using asio::ip::udp;
+
+namespace {
+
+const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
+const uint16_t TEST_PORT(5301);
+// FIXME Shouldn't we send something that is real message?
+const char TEST_DATA[] = "TEST DATA";
+
+// Test fixture for the asiolink::UDPQuery.
+class UDPQueryTest : public ::testing::Test,
+ public asiolink::UDPQuery::Callback
+{
+ public:
+ // Expected result of the callback
+ asiolink::UDPQuery::Result expected_;
+ // Did the callback run already?
+ bool run_;
+ // We use an io_service to run the query
+ io_service service_;
+ // Something to ask
+ Question question_;
+ // Buffer where the UDPQuery will store response
+ OutputBufferPtr buffer_;
+ // The query we are testing
+ asiolink::UDPQuery query_;
+
+ UDPQueryTest() :
+ run_(false),
+ question_(Name("example.net"), RRClass::IN(), RRType::A()),
+ buffer_(new OutputBuffer(512)),
+ query_(service_, question_, asiolink::IOAddress(TEST_HOST),
+ TEST_PORT, buffer_, this, 100)
+ { }
+
+ // This is the callback's (), so it can be called.
+ void operator()(asiolink::UDPQuery::Result result) {
+ // We check the query returns the correct result
+ EXPECT_EQ(expected_, result);
+ // Check it is called only once
+ EXPECT_FALSE(run_);
+ // And mark the callback was called
+ run_ = true;
+ }
+ // A response handler, pretending to be remote DNS server
+ void respond(udp::endpoint* remote, udp::socket* socket) {
+ // Some data came, just send something back.
+ socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA),
+ *remote);
+ socket->close();
+ }
+};
+
+/*
+ * Test that when we run the query and stop it after it was run,
+ * it returns "stopped" correctly.
+ *
+ * That is why stop() is posted to the service_ as well instead
+ * of calling it.
+ */
+TEST_F(UDPQueryTest, stop) {
+ expected_ = asiolink::UDPQuery::STOPPED;
+ // Post the query
+ service_.post(query_);
+ // Post query_.stop() (yes, the boost::bind thing is just
+ // query_.stop()).
+ service_.post(boost::bind(&asiolink::UDPQuery::stop, query_,
+ asiolink::UDPQuery::STOPPED));
+ // Run both of them
+ service_.run();
+ EXPECT_TRUE(run_);
+}
+
+/*
+ * Test that when we queue the query to service_ and call stop()
+ * before it gets executed, it acts sanely as well (eg. has the
+ * same result as running stop() after - calls the callback).
+ */
+TEST_F(UDPQueryTest, prematureStop) {
+ expected_ = asiolink::UDPQuery::STOPPED;
+ // Stop before it is started
+ query_.stop();
+ service_.post(query_);
+ service_.run();
+ EXPECT_TRUE(run_);
+}
+
+/*
+ * Test that it will timeout when no answer will arrive.
+ */
+TEST_F(UDPQueryTest, timeout) {
+ expected_ = asiolink::UDPQuery::TIME_OUT;
+ service_.post(query_);
+ service_.run();
+ EXPECT_TRUE(run_);
+}
+
+/*
+ * Test that it will succeed when we fake an answer and
+ * stores the same data we send.
+ *
+ * This is done through a real socket on loopback address.
+ */
+TEST_F(UDPQueryTest, receive) {
+ expected_ = asiolink::UDPQuery::SUCCESS;
+ udp::socket socket(service_, udp::v4());
+ socket.set_option(socket_base::reuse_address(true));
+ socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+ char inbuff[512];
+ udp::endpoint remote;
+ socket.async_receive_from(asio::buffer(inbuff, 512), remote, boost::bind(
+ &UDPQueryTest::respond, this, &remote, &socket));
+ service_.post(query_);
+ service_.run();
+ EXPECT_TRUE(run_);
+ ASSERT_EQ(sizeof TEST_DATA, buffer_->getLength());
+ EXPECT_EQ(0, memcmp(TEST_DATA, buffer_->getData(), sizeof TEST_DATA));
+}
+
+}
More information about the bind10-changes
mailing list