From: Razvan Becheriu Date: Thu, 12 Sep 2019 12:23:52 +0000 (+0300) Subject: [#886, !508] added unit tests X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f85d8213be1c6e0a74585c20f19c3fe7885d77c4;p=thirdparty%2Fkea.git [#886, !508] added unit tests --- diff --git a/src/lib/dhcpsrv/tests/thread_resource_mgr_unittest.cc b/src/lib/dhcpsrv/tests/thread_resource_mgr_unittest.cc index 5561567a3a..7c1227cd1e 100644 --- a/src/lib/dhcpsrv/tests/thread_resource_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/thread_resource_mgr_unittest.cc @@ -10,54 +10,91 @@ #include +#include + +#include +#include #include +#include using namespace isc::dhcp; using namespace std; namespace { -/// @brief Test Fixture for testing isc::dhcp::ThreadResourceMgr -class ThreadResourceMgrTest : public ::testing::Test { -}; - +/// @brief test class to keep track of all constructed objects of a specific +/// class type +/// +/// @template parameter class to make this functionality available for a wide +/// range of 'similar' but distinct classes template -class Resource { +class Resource : public boost::noncopyable { public: - Resource() { + /// @brief Constructor + Resource() : data_() { lock_guard lk(mutex_); + // increase current number of instances of this class Resource::count_++; + // increase the total number of instances ever created Resource::created_count_++; + // check that this instance in new and should not be found in the + // verification set + EXPECT_TRUE(Resource::set_.find(&data_) == Resource::set_.end()); + // add this instance to the verification set + Resource::set_.emplace(&data_); } + /// @brief Destructor virtual ~Resource() { lock_guard lk(mutex_); + // decrease current number of instances of this class Resource::count_--; + // increase the total number of instances ever destroyed Resource::destroyed_count_++; + // check that this instance is found in the verification set + EXPECT_FALSE(Resource::set_.find(&data_) == Resource::set_.end()); + // remove this instance from the verification set + Resource::set_.erase(&data_); } + /// @brief count number of current allocated instances of the class + /// + /// @return number of current allocated instances of the class static uint32_t count() { lock_guard lk(mutex_); return Resource::count_; } + /// @brief count number of class instances ever created + /// + /// @return number of class instances ever created static uint32_t createdCount() { lock_guard lk(mutex_); return Resource::created_count_; } + /// @brief count number of class instances ever destroyed + /// + /// @return number of class instances ever destroyed static uint32_t destroyedCount() { lock_guard lk(mutex_); return Resource::destroyed_count_; } + /// @brief reset all statistics for this class static void reset() { lock_guard lk(mutex_); + // reset all statistics for this class Resource::count_ = 0; Resource::created_count_ = 0; Resource::destroyed_count_ = 0; + Resource::set_.clear(); } + private: + /// @brief data element + T data_; + /// @brief total number of instances at any given time static uint32_t count_; @@ -69,12 +106,194 @@ private: /// @brief mutex used to keep the internal state consistent static std::mutex mutex_; + + /// @brief set to fold + static std::set set_; }; -// This test verifies that each thread can access it's own allocated resource -TEST(ThreadResourceMgrTest, testThreadResources) { - ThreadResourceMgr integers; - ThreadResourceMgr bools; +template +uint32_t Resource::count_; +template +uint32_t Resource::created_count_; +template +uint32_t Resource::destroyed_count_; +template +std::mutex Resource::mutex_; +template +std::set Resource::set_; + +/// @brief Test Fixture for testing isc::dhcp::ThreadResourceMgr +class ThreadResourceMgrTest : public ::testing::Test { +public: + /// @brief Constructor + ThreadResourceMgrTest() : wait_(false) { + } + + /// @brief Destructor + ~ThreadResourceMgrTest() { + } + + /// @brief flag which indicates if working thread should wait for main + /// thread signal + /// + /// @return the wait flag + bool wait() { + return wait_; + } + + /// @brief function used by main thread to unblock processing threads + void signalThreads() { + lock_guard lk(wait_mutex_); + // clear the wait flag so that threads will no longer wait for the main + // thread signal + wait_ = false; + // wake all threads if waiting for main thread signal + wait_cv_.notify_all(); + } + + /// @brief reset resource manager for the template class and perform sanity + /// checks + template + void reset() { + get() = make_shared>>(); + sanityCheck(); + wait_ = true; + } + + /// @brief check statistics + /// + /// @param expected_count check equality of this value with the number of + /// class instances + /// @param expected_created check equality of this value with the number of + /// class instances ever created + /// @param expected_destroyed check equality of this value with the number + /// of class instances ever destroyed + template + void checkInstances(uint32_t expected_count, + uint32_t expected_created, + uint32_t expected_destroyed) { + ASSERT_EQ(Resource::count(), expected_count); + ASSERT_EQ(Resource::createdCount(), expected_created); + ASSERT_EQ(Resource::destroyedCount(), expected_destroyed); + } + + /// @brief sanity check that the number of created instances is equal to the + /// number of destroyed instances + template + void sanityCheck() { + ASSERT_EQ(Resource::createdCount(), Resource::destroyedCount()); + } + + /// @brief get the instance of the resource manager responsible for a + /// specific class + /// + /// @return the resource manager responsible for a specific class + template + shared_ptr>> &get() { + static shared_ptr>> container; + return container; + } + + /// @brief run function which accesses the resource allocated for the + /// calling thread and verifies the class statistics + /// @param expected_count check equality of this value with the number of + /// class instances + /// @param expected_created check equality of this value with the number of + /// class instances ever created + /// @param expected_destroyed check equality of this value with the number + /// of class instances ever destroyed + /// @param signal indicate if the function should wait for signal from main + /// thread or exit immediately + template + void run(uint32_t expected_count, + uint32_t expected_created, + uint32_t expected_destroyed, + bool signal = false) { + // get resource for this thread + auto left = get()->resource().get(); + // verify statistics + checkInstances(expected_count, expected_created, expected_destroyed); + // get the resource for this thread once more + auto right = get()->resource().get(); + // check that it is the same resource + ASSERT_EQ(left, right); + // verify statistics which should have not changed on multiple + // sequential requests for the same resource + checkInstances(expected_count, expected_created, expected_destroyed); + + if (signal) { + unique_lock lk(wait_mutex_); + // if specified, wait for signal from main thread + wait_cv_.wait(lk, [&]{ return (wait() == false); }); + } + } + +private: + /// @brief mutex used to keep the internal state consistent + /// related to the control of the main thread over the working threads exit + std::mutex wait_mutex_; + + /// @brief condition variable used to signal working threads to exit + condition_variable wait_cv_; + + /// @brief flag which indicates if working thread should wait for main + /// thread signal + bool wait_; +}; + +/// @brief This test verifies that each thread can access it's own allocated +/// resource. Multiple threads are created and run in parallel. The checks are +/// done while threads are still running. +/// It is very important for the threads to run in parallel and not just run and +/// join the thread as this will cause newer threads to use the old thread id +/// and receive the same resource. +/// If destroying threads, the resource manager should also be reset. +TEST_F(ThreadResourceMgrTest, testThreadResources) { + std::list> threads; + + // reset statistics for uint_32 type + reset(); + // call run function on main thread and verify statistics + run(1, 1, 0); + // call run on a different thread and verify statistics + threads.push_back(std::make_shared(std::bind( + &ThreadResourceMgrTest::run, this, 2, 2, 0, true))); + // call run again on a different thread and verify statistics + threads.push_back(std::make_shared(std::bind( + &ThreadResourceMgrTest::run, this, 3, 3, 0, true))); + // signal all threads + signalThreads(); + // wait for all threads to finish + for (auto &thread : threads) { + thread->join(); + } + // reset statistics for uint_32 type + reset(); + // verify statistics 0 instances, 3 created, 3 destroyed + checkInstances(0, 3, 3); + + threads.clear(); + + // reset statistics for bool type + reset(); + // call run function on main thread and verify statistics + run(1, 1, 0); + // call run on a different thread and verify statistics + threads.push_back(std::make_shared(std::bind( + &ThreadResourceMgrTest::run, this, 2, 2, 0, true))); + // call run again on a different thread and verify statistics + threads.push_back(std::make_shared(std::bind( + &ThreadResourceMgrTest::run, this, 3, 3, 0, true))); + // signal all threads + signalThreads(); + // wait for all threads to finish + for (auto &thread : threads) { + thread->join(); + } + // reset statistics for bool type + reset(); + // verify statistics 0 instances, 3 created, 3 destroyed + checkInstances(0, 3, 3); } } // namespace