// Copyright (C) 2016-2024 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 #include #include #include #include #include #include #include #include #include #include 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 makeCallback(const std::string& timer_name); /// @brief Create a callback which generates exception. std::function 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 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(); io_service_->restart(); try { io_service_->poll(); } catch (...) { } } 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_->restart(); } 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 TimerMgrTest::makeCallback(const std::string& timer_name) { return (std::bind(&TimerMgrTest::timerCallback, this, timer_name)); } std::function 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. size_t count = 0; for (auto const& it : calls_count_) { unsigned int calls_count = it.second; ASSERT_GT(calls_count, 0) << "expected calls counter for timer " << ++count << " 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