libicsneo/include/icsneo/third-party/readerwriterqueue/tests/unittests/unittests.cpp

779 lines
16 KiB
C++

// ©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;
}