diff options
Diffstat (limited to 'src/lib/util/tests/watched_thread_unittest.cc')
-rw-r--r-- | src/lib/util/tests/watched_thread_unittest.cc | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/src/lib/util/tests/watched_thread_unittest.cc b/src/lib/util/tests/watched_thread_unittest.cc new file mode 100644 index 0000000..dd01550 --- /dev/null +++ b/src/lib/util/tests/watched_thread_unittest.cc @@ -0,0 +1,218 @@ +// Copyright (C) 2018-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 <util/watched_thread.h> + +#include <gtest/gtest.h> + +#include <atomic> +#include <functional> +#include <signal.h> +#include <unistd.h> + +using namespace std; +using namespace isc; +using namespace isc::util; + +namespace { + +/// @brief Test Fixture for testing @c isc::util::WatchedThread +class WatchedThreadTest : public ::testing::Test { +public: + /// @brief Maximum number of passes allowed in worker event loop + static const int WORKER_MAX_PASSES; + + /// @brief Constructor. + WatchedThreadTest() {} + + /// @brief Destructor. + ~WatchedThreadTest() { + } + + /// @brief Sleeps for a given number of event periods sleep + /// Each period is 50 ms. + void nap(int periods) { + usleep(periods * 50 * 1000); + }; + + /// @brief Worker function to be used by the WatchedThread's thread + /// + /// The function runs 10 passes through an "event" loop. + /// On each pass: + /// - check terminate command + /// - instigate the desired event (second pass only) + /// - naps for 1 period (50ms) + /// + /// @param watch_type type of event that should occur + void worker(WatchedThread::WatchType watch_type) { + sigset_t nsset; + pthread_sigmask(SIG_SETMASK, 0, &nsset); + EXPECT_EQ(1, sigismember(&nsset, SIGCHLD)); + EXPECT_EQ(1, sigismember(&nsset, SIGINT)); + EXPECT_EQ(1, sigismember(&nsset, SIGHUP)); + EXPECT_EQ(1, sigismember(&nsset, SIGTERM)); + for (passes_ = 1; passes_ < WORKER_MAX_PASSES; ++passes_) { + + // Stop if we're told to do it. + if (wthread_->shouldTerminate()) { + return; + } + + // On the second pass, set the event. + if (passes_ == 2) { + switch (watch_type) { + case WatchedThread::ERROR: + wthread_->setError("we have an error"); + break; + case WatchedThread::READY: + wthread_->markReady(watch_type); + break; + case WatchedThread::TERMINATE: + default: + // Do nothing, we're waiting to be told to stop. + break; + } + } + + // Take a nap. + nap(1); + } + + // Indicate why we stopped. + wthread_->setError("thread expired"); + } + + /// @brief Current WatchedThread instance. + WatchedThreadPtr wthread_; + + /// @brief Counter used to track the number of passes made + /// within the thread worker function. + std::atomic<int> passes_; +}; + +const int WatchedThreadTest::WORKER_MAX_PASSES = 10; + +/// Verifies the basic operation of the WatchedThread class. +/// It checks that a WatchedThread can be created, can be stopped, +/// and that in set and clear sockets. +TEST_F(WatchedThreadTest, watchedThreadClassBasics) { + + /// We'll create a WatchedThread and let it run until it expires. (Note this is more + /// of a test of WatchedThreadTest itself and ensures that the assumptions made in + /// our other tests as to why threads have finished are sound. + wthread_.reset(new WatchedThread()); + ASSERT_FALSE(wthread_->isRunning()); + wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE)); + ASSERT_TRUE(wthread_->isRunning()); + + // Wait more long enough (we hope) for the thread to expire. + nap(WORKER_MAX_PASSES * 4); + + // It should have done the maximum number of passes. + EXPECT_EQ(passes_, WORKER_MAX_PASSES); + + // Error should be ready and error text should be "thread expired". + ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + EXPECT_EQ("thread expired", wthread_->getLastError()); + + // Thread is technically still running, so let's stop it. + EXPECT_TRUE(wthread_->isRunning()); + ASSERT_NO_THROW(wthread_->stop()); + ASSERT_FALSE(wthread_->isRunning()); + + /// Now we'll test stopping a thread. + /// Start the WatchedThread, let it run a little and then tell it to stop. + wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE)); + ASSERT_TRUE(wthread_->isRunning()); + + // No watches should be ready. + ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + + // Wait a little while. + nap(3); + + // Tell it to stop. + wthread_->stop(); + ASSERT_FALSE(wthread_->isRunning()); + + // It should have done less than the maximum number of passes. + EXPECT_LT(passes_, WORKER_MAX_PASSES); + + // No watches should be ready. Error text should be "thread stopped". + ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + EXPECT_EQ("thread stopped", wthread_->getLastError()); + + + // Next we'll test error notification. + // Start the WatchedThread with a thread that sets an error on the second pass. + wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::ERROR)); + ASSERT_TRUE(wthread_->isRunning()); + + // No watches should be ready. + ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + + // Wait a little while. + nap(6); + + // It should now indicate an error. + ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR)); + EXPECT_EQ("we have an error", wthread_->getLastError()); + + // Tell it to stop. + wthread_->stop(); + ASSERT_FALSE(wthread_->isRunning()); + + // It should have done less than the maximum number of passes. + EXPECT_LT(passes_, WORKER_MAX_PASSES); + + // No watches should be ready. Error text should be "thread stopped". + ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + EXPECT_EQ("thread stopped", wthread_->getLastError()); + + + // Finally, we'll test data ready notification. + // We'll start the WatchedThread with a thread that indicates data ready on its second pass. + wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::READY)); + ASSERT_TRUE(wthread_->isRunning()); + + // No watches should be ready. + ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + + // Wait a little while. + nap(6); + + // It should now indicate data ready. + ASSERT_TRUE(wthread_->isReady(WatchedThread::READY)); + + // Tell it to stop. + wthread_->stop(); + ASSERT_FALSE(wthread_->isRunning()); + + // It should have done less than the maximum number of passes. + EXPECT_LT(passes_, WORKER_MAX_PASSES); + + // No watches should be ready. Error text should be "thread stopped". + ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::READY)); + ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE)); + EXPECT_EQ("thread stopped", wthread_->getLastError()); +} + +} |