I would like any feedback on making the unit test for a BlockingQueue
class more robust or better? improvements to the coding style, use of the gtest
framework, better test cases etc. C++ style suggestions (up to c++ 14). Feel free to add suggestion to improve the BlockingQueue
class if you feel inclined to.
BlockingQueue.h
#pragma once
#include <condition_variable>
#include <mutex>
#include <thread>
#include <queue>
#include <string>
#include <iostream>
#include <sstream>
template <typename T>
class BlockingQueue
{
public:
BlockingQueue() = default;
BlockingQueue(BlockingQueue<T>&& blockingQ);
BlockingQueue<T>& operator=(BlockingQueue<T>&& blockingQ);
BlockingQueue(const BlockingQueue<T>&) = delete;
BlockingQueue<T>& operator=(const BlockingQueue<T>&) = delete;
T deQ();
void enQ(const T& t);
T& front();
void clear();
size_t size();
private:
std::queue<T> queue_;
std::mutex mtx_;
std::condition_variable conditionVar_;
};
// Move constructor
template<typename T>
BlockingQueue<T>::BlockingQueue(BlockingQueue<T>&& blockingQ)
{
std::lock_guard<std::mutex> lock(mtx_);
// Here we are moving blockingQ.queue into queue_
queue_ = blockingQ.queue_;
while (blockingQ.queue_.size() > 0)
{
blockingQ.queue_.pop();
}
// can't copy or move mutex or condition variable, so use default members
}
//Move assignment
template<typename T>
BlockingQueue<T>& BlockingQueue<T>::operator=(BlockingQueue<T>&& blockingQ)
{
if (this == &blockingQ)
{
return *this;
}
std::lock_guard<std::mutex> lock(mtx_);
queue_ = blockingQ.queue_;
while (blockingQ.queue_.size() > 0) // clear blockingQ
{
blockingQ.queue_.pop();
}
// can't move assign mutex or condition variable so use target's
return *this;
}
// Remove element from front of queue
template<typename T>
T BlockingQueue<T>::deQ()
{
std::unique_lock<std::mutex> lock(mtx_);
// This lock type is required for use with condition variables.
// The operating system needs to lock and unlock the mutex:
// * When wait is called, below, the OS suspends waiting thread and releases lock.
// * When notify is called in enQ() the OS relocks the mutex,
// resumes the waiting thread and sets the condition variable to signaled state.
// std::lock_guard does not have publick lock and unlock functions
if (queue_.size() > 0)
{
T temp = queue_.front();
queue_.pop();
return temp;
}
// may have spurious returns so loop on !condition
conditionVar_.wait(lock, [this]() {return queue_.size() > 0; });
T temp = queue_.front();
queue_.pop();
return temp;
}
// push element onto back of queue
template<typename T>
void BlockingQueue<T>::enQ(const T& t)
{
{
std::unique_lock<std::mutex> lock(mtx_);
queue_.push(t);
}
conditionVar_.notify_one();
}
// Peek at next item to be popped
template <typename T>
T& BlockingQueue<T>::front()
{
std::lock_guard<std::mutex> lock(mtx_);
if (queue_.size() > 0)
{
return queue_.front();
}
throw std::exception("attemp to deQue empty queue");
}
template <typename T>
void BlockingQueue<T>::clear()
{
std::lock_guard<std::mutex> l(mtx_);
while (queue_.size() > 0)
queue_.pop();
}
// return number of elements in queue
template<typename T>
size_t BlockingQueue<T>::size()
{
std::lock_guard<std::mutex> l(mtx_);
return queue_.size();
}
test.cpp
#include "pch.h"
#include "../BlockingQueue/BlockingQueue.h"
// This test creates multiple threads that push and pop items from a BlockingQueue
// simultaneously. It tests if the BlockingQueue is thread-safe and can handle race
// conditions correctly.
TEST(BlockingQueueTest, MultipleThreads)
{
BlockingQueue<int> queue;
std::vector<std::thread> threads;
// Create 10 threads that push 100 items to the queue
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&queue]() {
for (int j = 0; j < 100; ++j)
{
queue.enQ(j);
}
});
}
// Create 10 threads that pop 100 items from the queue
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&queue]() {
for (int j = 0; j < 100; ++j)
{
queue.deQ();
}
});
}
// Wait for all threads to finish
for (auto& thread : threads)
{
thread.join();
}
// Test if the queue is empty
EXPECT_EQ(queue.size(), 0);
}
// This test creates two threads that push and pop items from a BlockingQueue.
// One thread pops items faster than the other, causing a race condition.
// This test checks if the BlockingQueue can handle this race condition correctly.
TEST(BlockingQueueTest, RaceCondition)
{
BlockingQueue<int> queue;
std::thread popThread([&queue]() {
for (int i = 0; i < 1000; ++i)
{
queue.deQ();
// Add a sleep to simulate a slower consumer
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
// Push 1000 items to the queue
for (int i = 0; i < 1000; ++i)
{
queue.enQ(i);
}
// Wait for the pop thread to finish
popThread.join();
// Test if the queue is empty
EXPECT_EQ(queue.size(), 0);
}