mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-05 09:28:40 +02:00
Add ReaderWriterQueue and update ConcurrentQueue
This commit is contained in:
@@ -0,0 +1,778 @@
|
||||
// ©2013-2015 Cameron Desrochers
|
||||
// Unit tests for moodycamel::ReaderWriterQueue
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "minitest.h"
|
||||
#include "../common/simplethread.h"
|
||||
#include "../../readerwriterqueue.h"
|
||||
|
||||
using namespace moodycamel;
|
||||
|
||||
|
||||
// *NOT* thread-safe
|
||||
struct Foo
|
||||
{
|
||||
Foo() : copied(false) { id = _id()++; }
|
||||
Foo(Foo const& other) : id(other.id), copied(true) { }
|
||||
~Foo()
|
||||
{
|
||||
if (copied) return;
|
||||
if (id != _last_destroyed_id() + 1) {
|
||||
_destroyed_in_order() = false;
|
||||
}
|
||||
_last_destroyed_id() = id;
|
||||
++_destroy_count();
|
||||
}
|
||||
static void reset() { _destroy_count() = 0; _id() = 0; _destroyed_in_order() = true; _last_destroyed_id() = -1; }
|
||||
static int destroy_count() { return _destroy_count(); }
|
||||
static bool destroyed_in_order() { return _destroyed_in_order(); }
|
||||
|
||||
private:
|
||||
static int& _destroy_count() { static int c = 0; return c; }
|
||||
static int& _id() { static int i = 0; return i; }
|
||||
static bool& _destroyed_in_order() { static bool d = true; return d; }
|
||||
static int& _last_destroyed_id() { static int i = -1; return i; }
|
||||
|
||||
int id;
|
||||
bool copied;
|
||||
};
|
||||
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
class UniquePtrWrapper
|
||||
{
|
||||
public:
|
||||
UniquePtrWrapper() = default;
|
||||
UniquePtrWrapper(std::unique_ptr<int> p) : m_p(std::move(p)) {}
|
||||
int get_value() const { return *m_p; }
|
||||
std::unique_ptr<int>& get_ptr() { return m_p; }
|
||||
private:
|
||||
std::unique_ptr<int> m_p;
|
||||
};
|
||||
#endif
|
||||
|
||||
/// Extracted from private static method of ReaderWriterQueue
|
||||
static size_t ceilToPow2(size_t x)
|
||||
{
|
||||
// From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
--x;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
for (size_t i = 1; i < sizeof(size_t); i <<= 1) {
|
||||
x |= x >> (i << 3);
|
||||
}
|
||||
++x;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
class ReaderWriterQueueTests : public TestClass<ReaderWriterQueueTests>
|
||||
{
|
||||
public:
|
||||
ReaderWriterQueueTests()
|
||||
{
|
||||
REGISTER_TEST(create_empty_queue);
|
||||
REGISTER_TEST(enqueue_one);
|
||||
REGISTER_TEST(enqueue_many);
|
||||
REGISTER_TEST(nonempty_destroy);
|
||||
REGISTER_TEST(try_enqueue);
|
||||
REGISTER_TEST(try_dequeue);
|
||||
REGISTER_TEST(peek);
|
||||
REGISTER_TEST(pop);
|
||||
REGISTER_TEST(size_approx);
|
||||
REGISTER_TEST(max_capacity);
|
||||
REGISTER_TEST(threaded);
|
||||
REGISTER_TEST(blocking);
|
||||
REGISTER_TEST(vector);
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
REGISTER_TEST(emplace);
|
||||
REGISTER_TEST(try_enqueue_fail_workaround);
|
||||
REGISTER_TEST(try_emplace_fail);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool create_empty_queue()
|
||||
{
|
||||
{
|
||||
ReaderWriterQueue<int> q;
|
||||
}
|
||||
|
||||
{
|
||||
ReaderWriterQueue<int> q(1234);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_one()
|
||||
{
|
||||
int item;
|
||||
|
||||
{
|
||||
item = 0;
|
||||
ReaderWriterQueue<int> q(1);
|
||||
q.enqueue(12345);
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == 12345);
|
||||
}
|
||||
|
||||
{
|
||||
item = 0;
|
||||
ReaderWriterQueue<int> q(1);
|
||||
ASSERT_OR_FAIL(q.try_enqueue(12345));
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == 12345);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enqueue_many()
|
||||
{
|
||||
int item = -1;
|
||||
|
||||
{
|
||||
ReaderWriterQueue<int> q(100);
|
||||
for (int i = 0; i != 100; ++i) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i != 100; ++i) {
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == i);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ReaderWriterQueue<int> q(100);
|
||||
for (int i = 0; i != 1200; ++i) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i != 1200; ++i) {
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == i);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nonempty_destroy()
|
||||
{
|
||||
Foo item;
|
||||
|
||||
// Some elements at beginning
|
||||
Foo::reset();
|
||||
{
|
||||
ReaderWriterQueue<Foo> q(31);
|
||||
for (int i = 0; i != 10; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
}
|
||||
ASSERT_OR_FAIL(Foo::destroy_count() == 10);
|
||||
ASSERT_OR_FAIL(Foo::destroyed_in_order());
|
||||
|
||||
// Entire block
|
||||
Foo::reset();
|
||||
{
|
||||
ReaderWriterQueue<Foo> q(31);
|
||||
for (int i = 0; i != 31; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
}
|
||||
ASSERT_OR_FAIL(Foo::destroy_count() == 31);
|
||||
ASSERT_OR_FAIL(Foo::destroyed_in_order());
|
||||
|
||||
// Multiple blocks
|
||||
Foo::reset();
|
||||
{
|
||||
ReaderWriterQueue<Foo> q(31);
|
||||
for (int i = 0; i != 94; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
}
|
||||
ASSERT_OR_FAIL(Foo::destroy_count() == 94);
|
||||
ASSERT_OR_FAIL(Foo::destroyed_in_order());
|
||||
|
||||
// Some elements in another block
|
||||
Foo::reset();
|
||||
{
|
||||
ReaderWriterQueue<Foo> q(31);
|
||||
for (int i = 0; i != 42; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
for (int i = 0; i != 31; ++i) {
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
}
|
||||
}
|
||||
ASSERT_OR_FAIL(Foo::destroy_count() == 42);
|
||||
ASSERT_OR_FAIL(Foo::destroyed_in_order());
|
||||
|
||||
// Some elements in multiple blocks
|
||||
Foo::reset();
|
||||
{
|
||||
ReaderWriterQueue<Foo> q(31);
|
||||
for (int i = 0; i != 123; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
for (int i = 0; i != 25; ++i) {
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
}
|
||||
for (int i = 0; i != 47; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
for (int i = 0; i != 140; ++i) {
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
}
|
||||
for (int i = 0; i != 230; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
for (int i = 0; i != 130; ++i) {
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
}
|
||||
for (int i = 0; i != 100; ++i) {
|
||||
q.enqueue(Foo());
|
||||
}
|
||||
}
|
||||
ASSERT_OR_FAIL(Foo::destroy_count() == 500);
|
||||
ASSERT_OR_FAIL(Foo::destroyed_in_order());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_enqueue()
|
||||
{
|
||||
ReaderWriterQueue<int> q(31);
|
||||
int item;
|
||||
int size = 0;
|
||||
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
if ((rand() & 1) == 1) {
|
||||
bool result = q.try_enqueue(i);
|
||||
if (size == 31) {
|
||||
ASSERT_OR_FAIL(!result);
|
||||
}
|
||||
else {
|
||||
ASSERT_OR_FAIL(result);
|
||||
++size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool result = q.try_dequeue(item);
|
||||
if (size == 0) {
|
||||
ASSERT_OR_FAIL(!result);
|
||||
}
|
||||
else {
|
||||
ASSERT_OR_FAIL(result);
|
||||
--size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_dequeue()
|
||||
{
|
||||
int item;
|
||||
|
||||
{
|
||||
ReaderWriterQueue<int> q(1);
|
||||
ASSERT_OR_FAIL(!q.try_dequeue(item));
|
||||
}
|
||||
|
||||
{
|
||||
ReaderWriterQueue<int, 2> q(10);
|
||||
ASSERT_OR_FAIL(!q.try_dequeue(item));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool threaded()
|
||||
{
|
||||
weak_atomic<int> result;
|
||||
result = 1;
|
||||
|
||||
ReaderWriterQueue<int> q(100);
|
||||
SimpleThread reader([&]() {
|
||||
int item;
|
||||
int prevItem = -1;
|
||||
for (int i = 0; i != 1000000; ++i) {
|
||||
if (q.try_dequeue(item)) {
|
||||
if (item <= prevItem) {
|
||||
result = 0;
|
||||
}
|
||||
prevItem = item;
|
||||
}
|
||||
}
|
||||
});
|
||||
SimpleThread writer([&]() {
|
||||
for (int i = 0; i != 1000000; ++i) {
|
||||
if (((i >> 7) & 1) == 0) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
else {
|
||||
q.try_enqueue(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
writer.join();
|
||||
reader.join();
|
||||
|
||||
return result.load() == 1 ? true : false;
|
||||
}
|
||||
|
||||
bool peek()
|
||||
{
|
||||
weak_atomic<int> result;
|
||||
result = 1;
|
||||
|
||||
ReaderWriterQueue<int> q(100);
|
||||
SimpleThread reader([&]() {
|
||||
int item;
|
||||
int prevItem = -1;
|
||||
int* peeked;
|
||||
for (int i = 0; i != 100000; ++i) {
|
||||
peeked = q.peek();
|
||||
if (peeked != nullptr) {
|
||||
if (q.try_dequeue(item)) {
|
||||
if (item <= prevItem || item != *peeked) {
|
||||
result = 0;
|
||||
}
|
||||
prevItem = item;
|
||||
}
|
||||
else {
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
SimpleThread writer([&]() {
|
||||
for (int i = 0; i != 100000; ++i) {
|
||||
if (((i >> 7) & 1) == 0) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
else {
|
||||
q.try_enqueue(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
writer.join();
|
||||
reader.join();
|
||||
|
||||
return result.load() == 1 ? true : false;
|
||||
}
|
||||
|
||||
bool pop()
|
||||
{
|
||||
weak_atomic<int> result;
|
||||
result = 1;
|
||||
|
||||
ReaderWriterQueue<int> q(100);
|
||||
SimpleThread reader([&]() {
|
||||
int item;
|
||||
int prevItem = -1;
|
||||
int* peeked;
|
||||
for (int i = 0; i != 100000; ++i) {
|
||||
peeked = q.peek();
|
||||
if (peeked != nullptr) {
|
||||
item = *peeked;
|
||||
if (q.pop()) {
|
||||
if (item <= prevItem) {
|
||||
result = 0;
|
||||
}
|
||||
prevItem = item;
|
||||
}
|
||||
else {
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
SimpleThread writer([&]() {
|
||||
for (int i = 0; i != 100000; ++i) {
|
||||
if (((i >> 7) & 1) == 0) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
else {
|
||||
q.try_enqueue(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
writer.join();
|
||||
reader.join();
|
||||
|
||||
return result.load() == 1 ? true : false;
|
||||
}
|
||||
|
||||
bool size_approx()
|
||||
{
|
||||
weak_atomic<int> result;
|
||||
weak_atomic<int> front;
|
||||
weak_atomic<int> tail;
|
||||
|
||||
result = 1;
|
||||
front = 0;
|
||||
tail = 0;
|
||||
|
||||
ReaderWriterQueue<int> q(10);
|
||||
SimpleThread reader([&]() {
|
||||
int item;
|
||||
for (int i = 0; i != 100000; ++i) {
|
||||
if (q.try_dequeue(item)) {
|
||||
fence(memory_order_release);
|
||||
front = front.load() + 1;
|
||||
}
|
||||
int size = (int)q.size_approx();
|
||||
fence(memory_order_acquire);
|
||||
int tail_ = tail.load();
|
||||
int front_ = front.load();
|
||||
if (size > tail_ - front_ || size < 0) {
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
SimpleThread writer([&]() {
|
||||
for (int i = 0; i != 100000; ++i) {
|
||||
tail = tail.load() + 1;
|
||||
fence(memory_order_release);
|
||||
q.enqueue(i);
|
||||
int tail_ = tail.load();
|
||||
int front_ = front.load();
|
||||
fence(memory_order_acquire);
|
||||
int size = (int)q.size_approx();
|
||||
if (size > tail_ - front_ || size < 0) {
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
writer.join();
|
||||
reader.join();
|
||||
|
||||
return result.load() == 1 ? true : false;
|
||||
}
|
||||
|
||||
bool max_capacity()
|
||||
{
|
||||
{
|
||||
// this math for queue size estimation is only valid for q_size <= 256
|
||||
for (size_t q_size = 2; q_size < 256; ++q_size) {
|
||||
ReaderWriterQueue<int> q(q_size);
|
||||
ASSERT_OR_FAIL(q.max_capacity() == ceilToPow2(q_size+1)-1);
|
||||
|
||||
const size_t start_cap = q.max_capacity();
|
||||
for (size_t i = 0; i < start_cap+1; ++i) // fill 1 past capacity to resize
|
||||
q.enqueue(i);
|
||||
ASSERT_OR_FAIL(q.max_capacity() == 3*start_cap+1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blocking()
|
||||
{
|
||||
{
|
||||
BlockingReaderWriterQueue<int> q;
|
||||
int item;
|
||||
|
||||
q.enqueue(123);
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == 123);
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
|
||||
q.enqueue(234);
|
||||
ASSERT_OR_FAIL(q.size_approx() == 1);
|
||||
ASSERT_OR_FAIL(*q.peek() == 234);
|
||||
ASSERT_OR_FAIL(*q.peek() == 234);
|
||||
ASSERT_OR_FAIL(q.pop());
|
||||
|
||||
ASSERT_OR_FAIL(q.try_enqueue(345));
|
||||
q.wait_dequeue(item);
|
||||
ASSERT_OR_FAIL(item == 345);
|
||||
ASSERT_OR_FAIL(!q.peek());
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
ASSERT_OR_FAIL(!q.try_dequeue(item));
|
||||
}
|
||||
|
||||
weak_atomic<int> result;
|
||||
result = 1;
|
||||
|
||||
{
|
||||
BlockingReaderWriterQueue<int> q(100);
|
||||
SimpleThread reader([&]() {
|
||||
int item = -1;
|
||||
int prevItem = -1;
|
||||
for (int i = 0; i != 1000000; ++i) {
|
||||
q.wait_dequeue(item);
|
||||
if (item <= prevItem) {
|
||||
result = 0;
|
||||
}
|
||||
prevItem = item;
|
||||
}
|
||||
});
|
||||
SimpleThread writer([&]() {
|
||||
for (int i = 0; i != 1000000; ++i) {
|
||||
q.enqueue(i);
|
||||
}
|
||||
});
|
||||
|
||||
writer.join();
|
||||
reader.join();
|
||||
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
ASSERT_OR_FAIL(result.load());
|
||||
}
|
||||
|
||||
{
|
||||
BlockingReaderWriterQueue<int> q(100);
|
||||
SimpleThread reader([&]() {
|
||||
int item = -1;
|
||||
int prevItem = -1;
|
||||
for (int i = 0; i != 1000000; ++i) {
|
||||
if (!q.wait_dequeue_timed(item, 1000)) {
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
if (item <= prevItem) {
|
||||
result = 0;
|
||||
}
|
||||
prevItem = item;
|
||||
}
|
||||
});
|
||||
SimpleThread writer([&]() {
|
||||
for (int i = 0; i != 1000000; ++i) {
|
||||
q.enqueue(i);
|
||||
for (volatile int x = 0; x != 100; ++x);
|
||||
}
|
||||
});
|
||||
|
||||
writer.join();
|
||||
reader.join();
|
||||
|
||||
int item;
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
ASSERT_OR_FAIL(!q.wait_dequeue_timed(item, 0));
|
||||
ASSERT_OR_FAIL(!q.wait_dequeue_timed(item, 1));
|
||||
ASSERT_OR_FAIL(result.load());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vector()
|
||||
{
|
||||
{
|
||||
std::vector<ReaderWriterQueue<int>> queues;
|
||||
queues.push_back(ReaderWriterQueue<int>());
|
||||
queues.emplace_back();
|
||||
|
||||
queues[0].enqueue(1);
|
||||
queues[1].enqueue(2);
|
||||
std::swap(queues[0], queues[1]);
|
||||
|
||||
int item;
|
||||
ASSERT_OR_FAIL(queues[0].try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == 2);
|
||||
|
||||
ASSERT_OR_FAIL(queues[1].try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == 1);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<BlockingReaderWriterQueue<int>> queues;
|
||||
queues.push_back(BlockingReaderWriterQueue<int>());
|
||||
queues.emplace_back();
|
||||
|
||||
queues[0].enqueue(1);
|
||||
queues[1].enqueue(2);
|
||||
std::swap(queues[0], queues[1]);
|
||||
|
||||
int item;
|
||||
ASSERT_OR_FAIL(queues[0].try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item == 2);
|
||||
|
||||
queues[1].wait_dequeue(item);
|
||||
ASSERT_OR_FAIL(item == 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if MOODYCAMEL_HAS_EMPLACE
|
||||
bool emplace()
|
||||
{
|
||||
ReaderWriterQueue<UniquePtrWrapper> q(100);
|
||||
std::unique_ptr<int> p { new int(123) };
|
||||
q.emplace(std::move(p));
|
||||
UniquePtrWrapper item;
|
||||
ASSERT_OR_FAIL(q.try_dequeue(item));
|
||||
ASSERT_OR_FAIL(item.get_value() == 123);
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is what you have to do to try_enqueue() a movable type, and demonstrates why try_emplace() is useful
|
||||
bool try_enqueue_fail_workaround()
|
||||
{
|
||||
ReaderWriterQueue<UniquePtrWrapper> q(0);
|
||||
{
|
||||
// A failed try_enqueue() will still delete p
|
||||
std::unique_ptr<int> p { new int(123) };
|
||||
q.try_enqueue(std::move(p));
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
ASSERT_OR_FAIL(p == nullptr);
|
||||
}
|
||||
{
|
||||
// Workaround isn't pretty and potentially expensive - use try_emplace() instead
|
||||
std::unique_ptr<int> p { new int(123) };
|
||||
UniquePtrWrapper w(std::move(p));
|
||||
q.try_enqueue(std::move(w));
|
||||
p = std::move(w.get_ptr());
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
ASSERT_OR_FAIL(p != nullptr);
|
||||
ASSERT_OR_FAIL(*p == 123);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_emplace_fail()
|
||||
{
|
||||
ReaderWriterQueue<UniquePtrWrapper> q(0);
|
||||
std::unique_ptr<int> p { new int(123) };
|
||||
q.try_emplace(std::move(p));
|
||||
ASSERT_OR_FAIL(q.size_approx() == 0);
|
||||
ASSERT_OR_FAIL(p != nullptr);
|
||||
ASSERT_OR_FAIL(*p == 123);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
void printTests(ReaderWriterQueueTests const& tests)
|
||||
{
|
||||
std::printf(" Supported tests are:\n");
|
||||
|
||||
std::vector<std::string> names;
|
||||
tests.getAllTestNames(names);
|
||||
for (auto it = names.cbegin(); it != names.cend(); ++it) {
|
||||
std::printf(" %s\n", it->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Basic test harness
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
bool disablePrompt = false;
|
||||
std::vector<std::string> selectedTests;
|
||||
|
||||
// Disable buffering (so that when run in, e.g., Sublime Text, the output appears as it is written)
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
|
||||
// Isolate the executable name
|
||||
std::string progName = argv[0];
|
||||
auto slash = progName.find_last_of("/\\");
|
||||
if (slash != std::string::npos) {
|
||||
progName = progName.substr(slash + 1);
|
||||
}
|
||||
|
||||
ReaderWriterQueueTests tests;
|
||||
|
||||
// Parse command line options
|
||||
if (argc == 1) {
|
||||
std::printf("Running all unit tests for moodycamel::ReaderWriterQueue.\n(Run %s --help for other options.)\n\n", progName.c_str());
|
||||
}
|
||||
else {
|
||||
bool printHelp = false;
|
||||
bool printedTests = false;
|
||||
bool error = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--help") == 0) {
|
||||
printHelp = true;
|
||||
}
|
||||
else if (std::strcmp(argv[i], "--disable-prompt") == 0) {
|
||||
disablePrompt = true;
|
||||
}
|
||||
else if (std::strcmp(argv[i], "--run") == 0) {
|
||||
if (i + 1 == argc || argv[i + 1][0] == '-') {
|
||||
std::printf("Expected test name argument for --run option.\n");
|
||||
if (!printedTests) {
|
||||
printTests(tests);
|
||||
printedTests = true;
|
||||
}
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tests.validateTestName(argv[++i])) {
|
||||
std::printf("Unrecognized test '%s'.\n", argv[i]);
|
||||
if (!printedTests) {
|
||||
printTests(tests);
|
||||
printedTests = true;
|
||||
}
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
selectedTests.push_back(argv[i]);
|
||||
}
|
||||
else {
|
||||
std::printf("Unrecognized option '%s'.\n", argv[i]);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error || printHelp) {
|
||||
if (error) {
|
||||
std::printf("\n");
|
||||
}
|
||||
std::printf("%s\n Description: Runs unit tests for moodycamel::ReaderWriterQueue\n", progName.c_str());
|
||||
std::printf(" --help Prints this help blurb\n");
|
||||
std::printf(" --run test Runs only the specified test(s)\n");
|
||||
std::printf(" --disable-prompt Disables prompt before exit when the tests finish\n");
|
||||
return error ? -1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int exitCode = 0;
|
||||
|
||||
bool result;
|
||||
if (selectedTests.size() > 0) {
|
||||
result = tests.run(selectedTests);
|
||||
}
|
||||
else {
|
||||
result = tests.run();
|
||||
}
|
||||
|
||||
if (result) {
|
||||
std::printf("All %stests passed.\n", (selectedTests.size() > 0 ? "selected " : ""));
|
||||
}
|
||||
else {
|
||||
std::printf("Test(s) failed!\n");
|
||||
exitCode = 2;
|
||||
}
|
||||
|
||||
if (!disablePrompt) {
|
||||
std::printf("Press ENTER to exit.\n");
|
||||
getchar();
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user