]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#886, !508] added unit tests
authorRazvan Becheriu <razvan@isc.org>
Thu, 12 Sep 2019 12:23:52 +0000 (15:23 +0300)
committerRazvan Becheriu <razvan@isc.org>
Mon, 4 Nov 2019 17:53:25 +0000 (19:53 +0200)
src/lib/dhcpsrv/tests/thread_resource_mgr_unittest.cc
src/lib/dhcpsrv/thread_resource_mgr.h

index 5561567a3a6550ea346897edfccf383e4935963d..7c1227cd1e1a7c6fe5da249358efa38ccb510dff 100644 (file)
 
 #include <dhcpsrv/thread_resource_mgr.h>
 
+#include <boost/noncopyable.hpp>
+
+#include <condition_variable>
+#include <list>
 #include <mutex>
+#include <thread>
 
 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 <typename T>
-class Resource {
+class Resource : public boost::noncopyable {
 public:
-    Resource() {
+    /// @brief Constructor
+    Resource() : data_() {
         lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lk(mutex_);
         return Resource::destroyed_count_;
     }
 
+    /// @brief reset all statistics for this class
     static void reset() {
         lock_guard<std::mutex> 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<T*> set_;
 };
 
-// This test verifies that each thread can access it's own allocated resource
-TEST(ThreadResourceMgrTest, testThreadResources) {
-    ThreadResourceMgr<uint32_t> integers;
-    ThreadResourceMgr<bool> bools;
+template <typename T>
+uint32_t Resource<T>::count_;
+template <typename T>
+uint32_t Resource<T>::created_count_;
+template <typename T>
+uint32_t Resource<T>::destroyed_count_;
+template <typename T>
+std::mutex Resource<T>::mutex_;
+template <typename T>
+std::set<T*> Resource<T>::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<mutex> 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 <typename T>
+    void reset() {
+        get<T>() = make_shared<ThreadResourceMgr<Resource<T>>>();
+        sanityCheck<T>();
+        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 <typename T>
+    void checkInstances(uint32_t expected_count,
+                        uint32_t expected_created,
+                        uint32_t expected_destroyed) {
+        ASSERT_EQ(Resource<T>::count(), expected_count);
+        ASSERT_EQ(Resource<T>::createdCount(), expected_created);
+        ASSERT_EQ(Resource<T>::destroyedCount(), expected_destroyed);
+    }
+
+    /// @brief sanity check that the number of created instances is equal to the
+    /// number of destroyed instances
+    template <typename T>
+    void sanityCheck() {
+        ASSERT_EQ(Resource<T>::createdCount(), Resource<T>::destroyedCount());
+    }
+
+    /// @brief get the instance of the resource manager responsible for a
+    /// specific class
+    ///
+    /// @return the resource manager responsible for a specific class
+    template <typename T>
+    shared_ptr<ThreadResourceMgr<Resource<T>>> &get() {
+        static shared_ptr<ThreadResourceMgr<Resource<T>>> 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 <typename T>
+    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<T>()->resource().get();
+        // verify statistics
+        checkInstances<T>(expected_count, expected_created, expected_destroyed);
+        // get the resource for this thread once more
+        auto right = get<T>()->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<T>(expected_count, expected_created, expected_destroyed);
+
+        if (signal) {
+            unique_lock<std::mutex> 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<shared_ptr<std::thread>> threads;
+
+    // reset statistics for uint_32 type
+    reset<uint32_t>();
+    // call run function on main thread and verify statistics
+    run<uint32_t>(1, 1, 0);
+    // call run on a different thread and verify statistics
+    threads.push_back(std::make_shared<std::thread>(std::bind(
+        &ThreadResourceMgrTest::run<uint32_t>, this, 2, 2, 0, true)));
+    // call run again on a different thread and verify statistics
+    threads.push_back(std::make_shared<std::thread>(std::bind(
+        &ThreadResourceMgrTest::run<uint32_t>, 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<uint32_t>();
+    // verify statistics 0 instances, 3 created, 3 destroyed
+    checkInstances<uint32_t>(0, 3, 3);
+
+    threads.clear();
+
+    // reset statistics for bool type
+    reset<bool>();
+    // call run function on main thread and verify statistics
+    run<bool>(1, 1, 0);
+    // call run on a different thread and verify statistics
+    threads.push_back(std::make_shared<std::thread>(std::bind(
+        &ThreadResourceMgrTest::run<bool>, this, 2, 2, 0, true)));
+    // call run again on a different thread and verify statistics
+    threads.push_back(std::make_shared<std::thread>(std::bind(
+        &ThreadResourceMgrTest::run<bool>, 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<bool>();
+    // verify statistics 0 instances, 3 created, 3 destroyed
+    checkInstances<bool>(0, 3, 3);
 }
 
 }  // namespace
index 50e7f62899bf2fdb900f4a22d7582321c8e0b664..405e92f6114b7af41d0c502fdb5a2b8fa4caa7a4 100644 (file)
@@ -17,7 +17,7 @@ namespace dhcp {
 
 template <typename Resource>
 class ThreadResourceMgr {
-    typedef boost::shared_ptr<Resource> ResourcePtr;
+    typedef std::shared_ptr<Resource> ResourcePtr;
 public:
     /// @brief function to retrieve the specific resource of calling thread
     /// This function returns the resource of the calling thread from the map
@@ -26,7 +26,7 @@ public:
     ///
     /// @return the specific resource of the calling thread
     ResourcePtr resource() {
-        std::lock_guard<std::mutex> lock(&mutex_);
+        std::lock_guard<std::mutex> lock(mutex_);
         auto id = std::this_thread::get_id();
         if (map_.find(id) != map_.end()) {
             return map_[id];