diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcpsrv/tests/timer_mgr_unittest.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcpsrv/tests/timer_mgr_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/timer_mgr_unittest.cc | 484 |
1 files changed, 484 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc new file mode 100644 index 0000000..4f7b58d --- /dev/null +++ b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc @@ -0,0 +1,484 @@ +// Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <asiolink/asio_wrapper.h> +#include <asiolink/interval_timer.h> +#include <asiolink/io_service.h> +#include <dhcpsrv/timer_mgr.h> +#include <exceptions/exceptions.h> +#include <testutils/multi_threading_utils.h> + +#include <gtest/gtest.h> + +#include <functional> +#include <sstream> +#include <unistd.h> + +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::test; + +namespace { + +/// @brief Test fixture class for @c TimerMgr. +class TimerMgrTest : public ::testing::Test { +public: + + /// @brief Constructor + TimerMgrTest() = default; + + /// @brief Destructor + virtual ~TimerMgrTest() = default; + +private: + + /// @brief Prepares the class for a test. + virtual void SetUp(); + + /// @brief Cleans up after the test. + virtual void TearDown(); + +public: + + /// @brief Wrapper method for registering a new timer. + /// + /// This method registers a new timer in the @c TimerMgr. It associates a + /// @c timerCallback method with a timer. This method registers a number of + /// calls to the particular timer in the @c calls_count_ map. + /// + /// @param timer_name Unique timer name. + /// @param timer_interval Timer interval. + /// @param mode Interval timer mode, which defaults to + /// @c IntervalTimer::ONE_SHOT. + void registerTimer(const std::string& timer_name, const long timer_interval, + const IntervalTimer::Mode& timer_mode = IntervalTimer::ONE_SHOT); + + /// @brief Wait for one or many ready handlers. + /// + /// @param timeout Wait timeout in milliseconds. + /// @param call_receive Indicates if the @c IfaceMgr::receive6 + /// should be called to run pending callbacks and clear + /// watch sockets. + void doWait(const long timeout, const bool call_receive = true); + + /// @brief Generic callback for timers under test. + /// + /// This callback increases the calls count for specified timer name. + /// + /// @param timer_name Name of the timer for which callback counter should + /// be increased. + void timerCallback(const std::string& timer_name); + + /// @brief Callback which generates exception. + /// + /// This callback is used to test that the @c TimerMgr can handle + /// the case when the callback generates exceptions. + void timerCallbackWithException(); + + /// @brief Create a generic callback function for the timer. + /// + /// This is just a wrapped to make it a bit more convenient + /// in the test. + std::function<void ()> makeCallback(const std::string& timer_name); + + /// @brief Create a callback which generates exception. + std::function<void ()> makeCallbackWithException(); + + /// @brief Callback for timeout. + /// + /// This callback indicates the test timeout by setting the + /// @c timeout_ member. + void timeoutCallback(); + + // @brief This test checks that certain errors are returned when invalid + // parameters are specified when registering a timer, or when + // the registration can't be made. + void testRegisterTimer(); + + /// @brief This test verifies that it is possible to unregister a timer from + /// the TimerMgr. + void testUnregisterTimer(); + + /// @brief This test verifies that it is possible to unregister all timers. + void testUnregisterTimers(); + + /// @brief This test verifies that the timer execution can be cancelled. + void testCancel(); + + /// @brief This test verifies that the callbacks for the scheduled timers + /// are actually called. + void testScheduleTimers(); + + /// @brief This test verifies that exceptions emitted from the callback + /// would be handled by the TimerMgr. + void testCallbackWithException(); + + /// @brief Type definition for a map holding calls counters for + /// timers. + typedef std::map<std::string, unsigned int> CallsCount; + + /// @brief Pointer to IO service used by the tests. + IOServicePtr io_service_; + + /// @brief Holds the calls count for test timers. + /// + /// The key of this map holds the timer names. The value holds the number + /// of calls to the timer handlers. + CallsCount calls_count_; + + /// @brief Instance of @c TimerMgr used by the tests. + TimerMgrPtr timer_mgr_; +}; + +void +TimerMgrTest::SetUp() { + io_service_.reset(new IOService()); + timer_mgr_ = TimerMgr::instance(); + timer_mgr_->setIOService(io_service_); + calls_count_.clear(); +} + +void +TimerMgrTest::TearDown() { + // Remove all timers. + timer_mgr_->unregisterTimers(); +} + +void +TimerMgrTest::registerTimer(const std::string& timer_name, const long timer_interval, + const IntervalTimer::Mode& timer_mode) { + // Register the timer with the generic callback that counts the + // number of callback invocations. + ASSERT_NO_THROW( + timer_mgr_->registerTimer(timer_name, makeCallback(timer_name), timer_interval, + timer_mode) + ); + + calls_count_[timer_name] = 0; + +} + +void +TimerMgrTest::doWait(const long timeout, const bool /*call_receive*/) { + IntervalTimer timer(*io_service_); + timer.setup([this]() { + io_service_->stop(); + }, timeout, IntervalTimer::ONE_SHOT); + io_service_->run(); + io_service_->get_io_service().reset(); +} + +void +TimerMgrTest::timerCallback(const std::string& timer_name) { + // Accumulate the number of calls to the timer handler. + ++calls_count_[timer_name]; + + // The timer installed is the ONE_SHOT timer, so we have + // to reschedule the timer. + timer_mgr_->setup(timer_name); +} + +void +TimerMgrTest::timerCallbackWithException() { + isc_throw(Exception, "timerCallbackWithException"); +} + +std::function<void ()> +TimerMgrTest::makeCallback(const std::string& timer_name) { + return (std::bind(&TimerMgrTest::timerCallback, this, timer_name)); +} + +std::function<void ()> +TimerMgrTest::makeCallbackWithException() { + return (std::bind(&TimerMgrTest::timerCallbackWithException, this)); +} + +// This test checks that certain errors are returned when invalid +// parameters are specified when registering a timer, or when +// the registration can't be made. +void +TimerMgrTest::testRegisterTimer() { + // Empty timer name is not allowed. + ASSERT_THROW(timer_mgr_->registerTimer("", makeCallback("timer1"), 1, + IntervalTimer::ONE_SHOT), + BadValue); + + // Add a timer with a correct name. + ASSERT_NO_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1, + IntervalTimer::ONE_SHOT)); + EXPECT_TRUE(timer_mgr_->isTimerRegistered("timer2")); + + // Adding the timer with the same name as the existing timer is not + // allowed. + ASSERT_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1, + IntervalTimer::ONE_SHOT), + BadValue); +} + +TEST_F(TimerMgrTest, registerTimer) { + // Disable Multi-Threading. + MultiThreadingTest mt(false); + testRegisterTimer(); +} + +TEST_F(TimerMgrTest, registerTimerMultiThreading) { + // Enable Multi-Threading. + MultiThreadingTest mt(true); + testRegisterTimer(); +} + +void +TimerMgrTest::testUnregisterTimer() { + // Register a timer and start it. + ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1)); + ASSERT_EQ(1, timer_mgr_->timersCount()); + ASSERT_NO_THROW(timer_mgr_->setup("timer1")); + + // Wait for the timer to execute several times. + doWait(100); + + // Remember how many times the timer's callback was executed. + const unsigned int calls_count = calls_count_["timer1"]; + ASSERT_GT(calls_count, 0); + + // Check that an attempt to unregister a non-existing timer would + // result in exception. + ASSERT_THROW(timer_mgr_->unregisterTimer("timer2"), BadValue); + // Number of timers shouldn't have changed. + ASSERT_EQ(1, timer_mgr_->timersCount()); + + // Now unregister the correct one. + ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1")); + ASSERT_EQ(0, timer_mgr_->timersCount()); + EXPECT_FALSE(timer_mgr_->isTimerRegistered("timer1")); + + doWait(100); + + // The number of calls for the timer1 shouldn't change as the + // timer had been unregistered. + EXPECT_EQ(calls_count_["timer1"], calls_count); +} + +TEST_F(TimerMgrTest, unregisterTimer) { + // Disable Multi-Threading. + MultiThreadingTest mt(false); + testUnregisterTimer(); +} + +TEST_F(TimerMgrTest, unregisterTimerMultiThreading) { + // Enable Multi-Threading. + MultiThreadingTest mt(true); + testUnregisterTimer(); +} + +void +TimerMgrTest::testUnregisterTimers() { + // Register 10 timers. + for (int i = 1; i <= 20; ++i) { + std::ostringstream s; + s << "timer" << i; + ASSERT_NO_FATAL_FAILURE(registerTimer(s.str(), 1)) + << "fatal failure occurred while registering " + << s.str(); + ASSERT_EQ(i, timer_mgr_->timersCount()) + << "invalid number of registered timers returned"; + ASSERT_NO_THROW(timer_mgr_->setup(s.str())) + << "exception thrown while calling setup() for the " + << s.str(); + } + + doWait(500); + + // Make sure that all timers have been executed at least once. + for (CallsCount::iterator it = calls_count_.begin(); + it != calls_count_.end(); ++it) { + unsigned int calls_count = it->second; + ASSERT_GT(calls_count, 0) + << "expected calls counter for timer" + << (std::distance(calls_count_.begin(), it) + 1) + << " greater than 0"; + } + + // Copy counters for all timers. + CallsCount calls_count(calls_count_); + + // Let's unregister all timers. + ASSERT_NO_THROW(timer_mgr_->unregisterTimers()); + + // Make sure there are no timers registered. + ASSERT_EQ(0, timer_mgr_->timersCount()); + + doWait(500); + + // The calls counter shouldn't change because there are + // no timers registered. + EXPECT_TRUE(calls_count == calls_count_); +} + +TEST_F(TimerMgrTest, unregisterTimers) { + // Disable Multi-Threading. + MultiThreadingTest mt(false); + testUnregisterTimers(); +} + +TEST_F(TimerMgrTest, unregisterTimersMultiThreading) { + // Enable Multi-Threading. + MultiThreadingTest mt(true); + testUnregisterTimers(); +} + +void +TimerMgrTest::testCancel() { + // Register timer. + ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1)); + + // Kick in the timer and wait for 500ms. + ASSERT_NO_THROW(timer_mgr_->setup("timer1")); + + doWait(500); + + // Canceling non-existing timer should fail. + EXPECT_THROW(timer_mgr_->cancel("timer2"), BadValue); + + // Canceling the good one should pass, even when the worker + // thread is running. + ASSERT_NO_THROW(timer_mgr_->cancel("timer1")); + + // Remember how many calls have been invoked and wait for + // another 500ms. + unsigned int calls_count = calls_count_["timer1"]; + + doWait(500); + + // The number of calls shouldn't change because the timer had been + // cancelled. + ASSERT_EQ(calls_count, calls_count_["timer1"]); + + // Setup the timer again. + ASSERT_NO_THROW(timer_mgr_->setup("timer1")); + + // Restart the thread. + doWait(500); + + // New calls should be recorded. + EXPECT_GT(calls_count_["timer1"], calls_count); +} + +TEST_F(TimerMgrTest, cancel) { + // Disable Multi-Threading. + MultiThreadingTest mt(false); + testCancel(); +} + +TEST_F(TimerMgrTest, cancelMultiThreading) { + // Enable Multi-Threading. + MultiThreadingTest mt(true); + testCancel(); +} + +void +TimerMgrTest::testScheduleTimers() { + // Register two timers: 'timer1' and 'timer2'. The first timer will + // be executed at the 50ms interval. The second one at the 100ms + // interval. + ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 50)); + ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 100)); + + // Kick in the timers. + ASSERT_NO_THROW(timer_mgr_->setup("timer1")); + ASSERT_NO_THROW(timer_mgr_->setup("timer2")); + + // Run IfaceMgr::receive6() in the loop for 1000ms. This function + // will read data from the watch sockets created when the timers + // were registered. The data is delivered to the watch sockets + // at the interval of the timers, which should break the blocking + // call to receive6(). As a result, the callbacks associated + // with the watch sockets should be called. + doWait(1000); + + // We have been running the timer for 1000ms at the interval of + // 50ms. The maximum number of callbacks is 20. However, the + // callback itself takes time. Stopping the thread takes time. + // So, the real number differs significantly. We don't know + // exactly how many have been executed. It should be more + // than 10 for sure. But we really made up the numbers here. + EXPECT_GT(calls_count_["timer1"], 10); + // For the second timer it should be more than 5. + EXPECT_GT(calls_count_["timer2"], 5); + + // Because the interval of the 'timer1' is lower than the + // interval of the 'timer2' the number of calls should + // be higher for the 'timer1'. + EXPECT_GT(calls_count_["timer1"], calls_count_["timer2"]); + + // Remember the number of calls from 'timer1' and 'timer2'. + unsigned int calls_count_timer1 = calls_count_["timer1"]; + unsigned int calls_count_timer2 = calls_count_["timer2"]; + + // Unregister the 'timer1'. + ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1")); + + // Wait another 500ms. The 'timer1' was unregistered so it + // should not make any more calls. The 'timer2' should still + // work as previously. + doWait(500); + + // The number of calls shouldn't have changed. + EXPECT_EQ(calls_count_timer1, calls_count_["timer1"]); + // There should be some new calls registered for the 'timer2'. + EXPECT_GT(calls_count_["timer2"], calls_count_timer2); +} + +TEST_F(TimerMgrTest, scheduleTimers) { + // Disable Multi-Threading. + MultiThreadingTest mt(false); + testScheduleTimers(); +} + +TEST_F(TimerMgrTest, scheduleTimersMultiThreading) { + // Enable Multi-Threading. + MultiThreadingTest mt(true); + testScheduleTimers(); +} + +void +TimerMgrTest::testCallbackWithException() { + // Create timer which will trigger callback generating exception. + ASSERT_NO_THROW( + timer_mgr_->registerTimer("timer1", makeCallbackWithException(), 1, + IntervalTimer::ONE_SHOT) + ); + + // Setup the timer. + ASSERT_NO_THROW(timer_mgr_->setup("timer1")); + + // Start thread. We hope that exception will be caught by the @c TimerMgr + // and will not kill the process. + doWait(500); +} + +TEST_F(TimerMgrTest, callbackWithException) { + // Disable Multi-Threading. + MultiThreadingTest mt(false); + testCallbackWithException(); +} + +TEST_F(TimerMgrTest, callbackWithExceptionMultiThreading) { + // Enable Multi-Threading. + MultiThreadingTest mt(true); + testCallbackWithException(); +} + +// This test verifies that IO service specified for the TimerMgr +// must not be null. +TEST_F(TimerMgrTest, setIOService) { + EXPECT_THROW(timer_mgr_->setIOService(IOServicePtr()), + BadValue); +} + +} // end of anonymous namespace |