From: Marcin Siodelski Date: Wed, 1 Aug 2012 15:40:00 +0000 (+0200) Subject: [1959] Created TestControl class, unit tests and main function. X-Git-Tag: trac2351_base~47^2~11^2~40 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=625cc0a45da9ee3356df3263d03a16eca5051b03;p=thirdparty%2Fkea.git [1959] Created TestControl class, unit tests and main function. --- diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am index 6ebc00f92a..e9593df056 100644 --- a/tests/tools/perfdhcp/Makefile.am +++ b/tests/tools/perfdhcp/Makefile.am @@ -18,23 +18,25 @@ if USE_STATIC_LINK AM_LDFLAGS += -static endif -lib_LTLIBRARIES = libperfdhcp++.la -libperfdhcp___la_SOURCES = command_options.cc command_options.h -libperfdhcp___la_SOURCES += localized_option.h -libperfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h -libperfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h -libperfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h - -libperfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS) +pkglibexec_PROGRAMS = perfdhcp2 +perfdhcp2_SOURCES = main.cc +perfdhcp2_SOURCES += command_options.cc command_options.h +perfdhcp2_SOURCES += localized_option.h +perfdhcp2_SOURCES += perf_pkt6.cc perf_pkt6.h +perfdhcp2_SOURCES += perf_pkt4.cc perf_pkt4.h +perfdhcp2_SOURCES += pkt_transform.cc pkt_transform.h +perfdhcp2_SOURCES += test_control.cc test_control.h + +perfdhcp2_CXXFLAGS = $(AM_CXXFLAGS) if USE_CLANGPP # Disable unused parameter warning caused by some of the # Boost headers when compiling with clang. -libperfdhcp___la_CXXFLAGS += -Wno-unused-parameter +perfdhcp2_CXXFLAGS += -Wno-unused-parameter endif -libperfdhcp___la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la -libperfdhcp___la_LIBADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la -libperfdhcp___la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la +perfdhcp2_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la +perfdhcp2_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la +perfdhcp2_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la -pkglibexec_PROGRAMS = perfdhcp -perfdhcp_SOURCES = perfdhcp.c +#pkglibexec_PROGRAMS = perfdhcp +#perfdhcp_SOURCES = perfdhcp.c diff --git a/tests/tools/perfdhcp/main.cc b/tests/tools/perfdhcp/main.cc new file mode 100644 index 0000000000..d70b8fae8d --- /dev/null +++ b/tests/tools/perfdhcp/main.cc @@ -0,0 +1,46 @@ +// Copyright (C) 2012 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 +#include + +#include +#include + +#include "test_control.h" +#include "command_options.h" + +using namespace isc::perfdhcp; + +int +main(int argc, char* argv[]) { + CommandOptions& command_options = CommandOptions::instance(); + try { + command_options.parse(argc, argv); + } catch(isc::Exception& e) { + std::cout << "Error parsing command line options: " + << e.what() << std::endl; + command_options.usage(); + return(1); + } + try{ + TestControl& test_control = TestControl::instance(); + test_control.run(); + } catch (isc::Exception& e) { + std::cout << "Error starting perfdhcp: " << e.what() << std::endl; + return(1); + } + return(0); +} + diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc new file mode 100644 index 0000000000..728d4d58ec --- /dev/null +++ b/tests/tools/perfdhcp/test_control.cc @@ -0,0 +1,239 @@ +// Copyright (C) 2012 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 +#include +#include +#include + +#include + +#include +#include +#include +#include "test_control.h" +#include "command_options.h" + +using namespace std; +using namespace boost; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; + +namespace isc { +namespace perfdhcp { + +TestControl& +TestControl::instance() { + static TestControl test_control; + return (test_control); +} + +TestControl::TestControl() : + send_due_(microsec_clock::universal_time()), + last_sent_(send_due_) { +} + +bool +TestControl::checkExitConditions() const { + CommandOptions& options = CommandOptions::instance(); + if ((options.getNumRequests().size() > 0) && + (sent_packets_0_ >= options.getNumRequests()[0])) { + return(true); + } else if ((options.getNumRequests().size() == 2) && + (sent_packets_1_ >= options.getNumRequests()[1])) { + return(true); + } + return(false); +} + +Pkt4* +TestControl::createDiscoverPkt4() { + const uint32_t transid = static_cast(random()); + Pkt4* pkt4 = new Pkt4(DHCPDISCOVER, transid); + + OptionBuffer opt_request_list_buf(); + // createRequestListBuffer4(opt_request_list_buf); + return NULL; +} + +OptionPtr +TestControl::factoryRequestList4(Option::Universe u, + uint16_t type, + const OptionBuffer& buf) +{ + const uint8_t buf_array[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + Option* opt = new Option(u, type, buf); + opt->setData(buf_with_options.begin(), + buf_with_options.end()); + return OptionPtr(opt); +} + +uint64_t +TestControl::getNextExchangesNum() const { + CommandOptions& options = CommandOptions::instance(); + // Reset number of exchanges. + uint64_t due_exchanges = 0; + // Get current time. + ptime now(microsec_clock::universal_time()); + // The due time indicates when we should start sening next chunk + // of packets. If it is already due time, we should calculate + // how many packets to send. + if (now >= send_due_) { + // If rate is specified from the command line we have to + // synchornize with it. + if (options.getRate() != 0) { + time_period period(send_due_, now); + // Null condition should not occur because we + // have checked it in the first if statement but + // let's keep this check just in case. + if (period.is_null()) { + return (0); + } + time_duration duration = period.length(); + // due_factor indicates the number of seconds that + // sending next chunk of packets will take. + double due_factor = duration.fractional_seconds() / + time_duration::ticks_per_second(); + due_factor += duration.total_seconds(); + // Multiplying due_factor by expected rate gives the number + // of exchanges to be initiated. + due_exchanges = static_cast(due_factor * options.getRate()); + // We want to make sure that at least one packet goes out. + due_exchanges += 1; + // We should not exceed aggressivity as it could have been + // restricted from command line. + if (due_exchanges > options.getAggressivity()) { + due_exchanges = options.getAggressivity(); + } + } else { + // Rate is not specified so we rely on aggressivity + // which is the number of packets to be sent in + // one chunk. + due_exchanges = options.getAggressivity(); + } + return (due_exchanges); + } + return (0); +} + +void +TestControl::registerOptionFactories4() const { + static bool factories_registered = false; + if (!factories_registered) { + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST, + &TestControl::factoryRequestList4); + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories6() const { + static bool factories_registered = false; + if (!factories_registered) { + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories() const { + CommandOptions& options = CommandOptions::instance(); + switch(options.getIpVersion()) { + case 4: + registerOptionFactories4(); + break; + case 6: + registerOptionFactories6(); + break; + default: + isc_throw(InvalidOperation, "command line options have to be parsed " + "before DHCP option factories can be registered"); + } +} + +void +TestControl::run() { + sent_packets_0_ = 0; + sent_packets_1_ = 0; + CommandOptions& options = CommandOptions::instance(); + // Ip version is not set ONLY in case the command options + // where not parsed. This surely means that parse() function + // was not called prior to starting the test. This is fatal + // error. + if (options.getIpVersion() == 0) { + isc_throw(InvalidOperation, "command options must be parsed before running " + "a test"); + } + registerOptionFactories(); + uint64_t packets_sent = 0; + for (;;) { + updateSendDue(); + if (checkExitConditions()) { + break; + } + uint64_t packets_due = getNextExchangesNum(); + for (uint64_t i = packets_due; i > 0; --i) { + startExchange(); + ++packets_sent; + cout << "Packets sent " << packets_sent << endl; + } + } + +} + +void +TestControl::startExchange() { + ++sent_packets_0_; + last_sent_ = microsec_clock::universal_time(); +} + +void +TestControl::updateSendDue() { + // If default constructor was called, this should not happen but + // if somebody has changed default constructor it is better to + // keep this check. + if (last_sent_.is_not_a_date_time()) { + isc_throw(Unexpected, "time of last sent packet not initialized"); + } + // Get the expected exchange rate. + CommandOptions& options = CommandOptions::instance(); + int rate = options.getRate(); + // If rate was not specified we will wait just one clock tick to + // send next packet. This simulates best effort conditions. + long duration = 1; + if (rate != 0) { + // We use number of ticks instead of nanoseconds because + // nanosecond resolution may not be available on some + // machines. Number of ticks guarantees the highest possible + // timer resolution. + duration = time_duration::ticks_per_second() / rate; + } + // Calculate due time to initate next chunk of exchanges. + send_due_ = last_sent_ + time_duration(0, 0, 0, duration); +} + + +} // namespace perfdhcp +} // namespace isc diff --git a/tests/tools/perfdhcp/test_control.h b/tests/tools/perfdhcp/test_control.h new file mode 100644 index 0000000000..b1d7d27d32 --- /dev/null +++ b/tests/tools/perfdhcp/test_control.h @@ -0,0 +1,117 @@ +// Copyright (C) 2012 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. + +#ifndef __TEST_CONTROL_H +#define __TEST_CONTROL_H + +#include +#include + +#include +#include + +#include +#include + +namespace isc { +namespace perfdhcp { + +/// \brief Test Control class. +/// +/// This class is responsible for running whole perfdhcp test. +/// +class TestControl : public boost::noncopyable { +public: + /// TestControl is a singleton class. This method returns reference + /// to its sole instance. + /// + /// \return the only existing instance of test control + static TestControl& instance(); + + /// Run performance test. + /// + /// Method runs whole performance test. Command line options must + /// be parsed prior to running this function. Othewise function will + /// throw exception. + /// + /// \throw isc::InvalidOperation if command line options are not parsed. + /// \throw isc::Unexpected if internal Test Controler error occured. + void run(); + +private: + + /// \brief Private default constructor. + /// + /// Default constructor is private as the object can be created + /// only via \ref instance method. + TestControl(); + + /// \brief Check if test exit condtitions fulfiled. + /// + /// Method checks if test exit conditions are fulfiled. + /// Exit conditions are checked periodically from the + /// main loop. Program should break the main loop when + /// this method returns true. It is calling function + /// responsibility to break main loop gracefully and + /// cleanup after test execution. + /// + /// \return true if any of the exit conditions is fulfiled. + bool checkExitConditions() const; + + dhcp::Pkt4* createDiscoverPkt4(); + + static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u, + uint16_t type, + const dhcp::OptionBuffer& buf); + + /// \brief Returns number of exchanges to be started. + /// + /// Method returns number of new exchanges to be started as soon + /// as possible to satisfy expected rate. Calculation used here + /// is based on current time, due time calculated with + /// \ref updateSendTime function and expected rate. + /// + /// \return number of exchanges to be started immediatelly. + uint64_t getNextExchangesNum() const; + + void registerOptionFactories4() const; + + void registerOptionFactories6() const; + + void registerOptionFactories() const; + + /// \brief Start new exchange of DHCP messages. + /// + void startExchange(); + + /// \brief Update due time to initiate next chunk of exchanges. + /// + /// Method updates due time to initiate next chunk of exchanges. + /// Function takes current time, last sent packet's time and + /// expected rate in its calculations. + void updateSendDue(); + + boost::posix_time::ptime send_due_; ///< Due time to initiate next chunk + ///< of exchanges. + boost::posix_time::ptime last_sent_; ///< Indicates when the last exchange + /// was initiated. + + uint64_t sent_packets_0_; + uint64_t sent_packets_1_; +}; + +} // namespace perfdhcp +} // namespace isc + +#endif // __COMMAND_OPTIONS_H diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am index ab1251c5c9..000bab527b 100644 --- a/tests/tools/perfdhcp/tests/Makefile.am +++ b/tests/tools/perfdhcp/tests/Makefile.am @@ -21,10 +21,12 @@ run_unittests_SOURCES += command_options_unittest.cc run_unittests_SOURCES += perf_pkt6_unittest.cc run_unittests_SOURCES += perf_pkt4_unittest.cc run_unittests_SOURCES += localized_option_unittest.cc +run_unittests_SOURCES += test_control_unittest.cc run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc +run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc new file mode 100644 index 0000000000..7a66b03fb3 --- /dev/null +++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (C) 2012 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 +#include +#include +#include + +#include "../test_control.h" +#include "../command_options.h" +#include "exceptions/exceptions.h" + +using namespace std; +using namespace isc; +using namespace isc::perfdhcp; + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp TestControl class. +class TestControlTest : public virtual ::testing::Test +{ +public: + /// \brief Default Constructor + TestControlTest() { } + +protected: +}; + +TEST_F(TestControlTest, Run) { + // Get the instance of TestControl object. + TestControl& test_control = TestControl::instance(); + // Running test without parsing command line arguments is + // expected to cause exception. + EXPECT_THROW(test_control.run(), isc::InvalidOperation); + + // The command line is to run single test iteration and exit. + // We have to declare argv as const walk around the problem + // of deprecated conversion from string to char*. + const char* argv[] = { "perfdhcp", "-l", "eth0", "-r", "10", "-n", "1" }; + const int argc = sizeof(argv) / sizeof(argv[0]); + CommandOptions& options = CommandOptions::instance(); + + // const_cast is odd but it seems to be the most straight forward way + // to achive the goal. In the future we might think about creating + // a tokenizing function that would dynamically produce non-const + // argv value. + ASSERT_NO_THROW(options.parse(argc, const_cast(argv))); + EXPECT_NO_THROW(test_control.run()); + + // This is ok to run the test again with the same parameters. + // It may trigger exception if TestControl singleton is in + // invalid state after test run. We want to make sure it is + // safe to rerun the test. + EXPECT_NO_THROW(test_control.run()); +}