You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
concurrentqueue/tests/fuzztests/fuzztests.cpp

868 lines
26 KiB
C++

// ©2013-2014 Cameron Desrochers.
// Distributed under the simplified BSD license (see the LICENSE file that
// should have come with this file).
// Fuzz (random) tests for moodycamel::ConcurrentQueue
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <ctime>
#include <cassert>
#include <string>
#include <random>
#include <atomic>
#include <fstream>
#include <iomanip>
#include <vector>
#include <csignal>
#include <mutex>
#include <exception>
#include <cctype>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include "../../concurrentqueue.h"
#include "../common/simplethread.h"
#include "../common/systemtime.h"
#include "../corealgos.h"
void failHook()
{
(void)1; // Attach debuggers here
}
#define _STR(x) #x
#define STR(x) _STR(x)
#define ASSERT_OR_FAIL_THREAD(cond) if (!(cond)) { const char* n = nullptr; failReason.compare_exchange_strong(n, "assertion failed on line " STR(__LINE__) ": " #cond, std::memory_order_relaxed, std::memory_order_relaxed); \
failed.store(true, std::memory_order_relaxed); failHook(); return; }
#define FAIL_IF_THREAD_TIMEOUT() if (getTimeDelta(startTime) > 60000) { const char* n = nullptr; failReason.compare_exchange_strong(n, "test timed out (detected on line " STR(__LINE__) ")", std::memory_order_relaxed, std::memory_order_relaxed); \
failed.store(true, std::memory_order_relaxed); failHook(); return; }
#define ASSERT_OR_FAIL(cond) if (!(cond)) { out_failReason = "assertion failed on line " STR(__LINE__) ": " #cond; result = false; failHook(); break; }
using namespace moodycamel;
typedef std::minstd_rand RNG_t;
enum test_type {
multithread_produce,
multithread_consume,
multithread_produce_and_consume,
completely_random,
// Core algo tests
core_add_only_list,
core_thread_local,
TEST_TYPE_COUNT
};
std::uint64_t test_count[TEST_TYPE_COUNT] = { 0 };
std::uint64_t fail_count[TEST_TYPE_COUNT] = { 0 };
const char* test_names[TEST_TYPE_COUNT] = {
"multithread_produce",
"multithread_consume",
"multithread_produce_and_consume",
"completely_random",
"core_add_only_list",
"core_thread_local",
};
const int SINGLE_SEED_ITERATIONS = 100;
const char* LOG_FILE = "fuzztests.log";
struct FuzzTraits : public ConcurrentQueueDefaultTraits
{
static const size_t BLOCK_SIZE = 8;
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 4;
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 4;
static const size_t INITIAL_IMPLCICIT_PRODUCER_HASH_SIZE = 1;
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 24;
};
struct TestListItem : corealgos::ListItem
{
int value;
TestListItem() : value(0) { }
explicit TestListItem(int value) : value(value) { }
inline TestListItem* prev(std::memory_order order = std::memory_order_relaxed) const
{
return static_cast<TestListItem*>(concurrentListPrev.load(order));
}
};
bool run_test(uint64_t seed, int iterations, test_type& out_type, const char*& out_failReason)
{
bool result = true;
RNG_t baseRng((unsigned int)seed);
std::uniform_int_distribution<int> randTest(0, TEST_TYPE_COUNT - 1);
std::uniform_int_distribution<int> randInitialSize(0, 70);
auto type = static_cast<test_type>(randTest(baseRng));
out_type = type;
for (int iteration = 0; iteration != iterations; ++iteration) {
RNG_t rng(baseRng);
std::atomic<bool> failed(false);
std::atomic<const char*> failReason;
failReason = nullptr;
SystemTime startTime = getSystemTime();
switch (type) {
case multithread_produce:
{
const int countIncrement = std::uniform_int_distribution<int>(1, 1000)(rng);
int count = std::uniform_int_distribution<int>(0, 500)(rng) * countIncrement;
int prodCount = std::uniform_int_distribution<int>(0, 6)(rng);
bool useConsumerToken = static_cast<bool>(std::uniform_int_distribution<int>(0, 1)(rng));
ConcurrentQueue<int, FuzzTraits> q(randInitialSize(rng));
std::vector<SimpleThread> producers(prodCount);
std::vector<bool> useProducerToken(prodCount);
for (int i = 0; i != prodCount; ++i) {
useProducerToken[i] = static_cast<bool>(std::uniform_int_distribution<int>(0, 1)(rng));
producers[i] = SimpleThread([&](int i) {
ProducerToken t(q);
for (int j = 0; j != count && !failed.load(std::memory_order_relaxed); j += countIncrement) {
if (useProducerToken[i]) {
for (int k = 0; k != countIncrement; ++k) {
ASSERT_OR_FAIL_THREAD(q.enqueue(t, (i << 24) | (k + j)));
}
}
else {
for (int k = 0; k != countIncrement; ++k) {
ASSERT_OR_FAIL_THREAD(q.enqueue((i << 24) | (k + j)));
}
}
FAIL_IF_THREAD_TIMEOUT();
}
}, i);
}
SimpleThread consumer([&]() {
int item;
std::vector<int> lastItems(prodCount);
ConsumerToken t(q);
for (int i = 0; i != prodCount; ++i) {
lastItems[i] = -1;
}
for (int i = 0; i != count * prodCount && !failed.load(std::memory_order_relaxed);) {
if (useConsumerToken) {
for (int j = 0; j != 10000; ++j) {
if (q.try_dequeue(t, item)) {
++i;
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) < count);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) == lastItems[item >> 24] + 1);
lastItems[item >> 24] = (item & 0xFFFFFF);
}
}
}
else {
for (int j = 0; j != 10000; ++j) {
if (q.try_dequeue(item)) {
++i;
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) < count);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) == lastItems[item >> 24] + 1);
lastItems[item >> 24] = (item & 0xFFFFFF);
}
}
}
FAIL_IF_THREAD_TIMEOUT();
}
});
for (int i = 0; i != prodCount; ++i) {
producers[i].join();
}
consumer.join();
if (failed.load(std::memory_order_relaxed)) {
break;
}
int item;
ASSERT_OR_FAIL(!q.try_dequeue(item));
break;
}
case multithread_consume:
{
const int countIncrement = std::uniform_int_distribution<int>(1, 1000)(rng);
int count = std::uniform_int_distribution<int>(0, 500)(rng) * countIncrement;
int consCount = std::uniform_int_distribution<int>(0, 6)(rng);
bool useProducerToken = static_cast<bool>(std::uniform_int_distribution<int>(0, 1)(rng));
std::atomic<bool> producerDone(false);
ConcurrentQueue<int, FuzzTraits> q(randInitialSize(rng));
std::vector<SimpleThread> consumers(consCount);
std::vector<bool> useConsumerToken(consCount);
for (int i = 0; i != consCount; ++i) {
useConsumerToken[i] = static_cast<bool>(std::uniform_int_distribution<int>(0, 1)(rng));
consumers[i] = SimpleThread([&](int i) {
int item, lastItem = -1;
ConsumerToken t(q);
bool doneConsuming = false;
while (!doneConsuming && !failed.load(std::memory_order_relaxed)) {
auto producerDoneLocal = producerDone.load(std::memory_order_acquire);
if (useConsumerToken[i]) {
for (int j = 0; j != 10000; ++j) {
if (q.try_dequeue(t, item)) {
ASSERT_OR_FAIL_THREAD(item >= 0 && item < count * consCount && item > lastItem);
lastItem = item;
}
else if (producerDoneLocal) {
doneConsuming = true;
break;
}
}
}
else {
for (int j = 0; j != 10000; ++j) {
if (q.try_dequeue(item)) {
ASSERT_OR_FAIL_THREAD(item >= 0 && item < count * consCount && item > lastItem);
lastItem = item;
}
else if (producerDoneLocal) {
doneConsuming = true;
break;
}
}
}
FAIL_IF_THREAD_TIMEOUT();
}
}, i);
}
SimpleThread producer([&]() {
ProducerToken t(q);
for (int i = 0; i != count * consCount && !failed.load(std::memory_order_relaxed); i += countIncrement) {
if (useProducerToken) {
for (int j = 0; j != countIncrement; ++j) {
ASSERT_OR_FAIL_THREAD(q.enqueue(t, i + j));
}
}
else {
for (int j = 0; j != countIncrement; ++j) {
ASSERT_OR_FAIL_THREAD(q.enqueue(i + j));
}
}
FAIL_IF_THREAD_TIMEOUT();
}
producerDone.store(true, std::memory_order_release);
});
producer.join();
for (int i = 0; i != consCount; ++i) {
consumers[i].join();
}
if (failed.load(std::memory_order_relaxed)) {
break;
}
int item;
ASSERT_OR_FAIL(consCount == 0 || !q.try_dequeue(item));
break;
}
case multithread_produce_and_consume:
{
const int countIncrement = std::uniform_int_distribution<int>(1, 1000)(rng);
int count = std::uniform_int_distribution<int>(0, 500)(rng) * countIncrement;
int prodCount = std::uniform_int_distribution<int>(0, 6)(rng);
int consCount = std::uniform_int_distribution<int>(0, 6)(rng);
std::atomic<bool> producersDone(false);
ConcurrentQueue<int, FuzzTraits> q(randInitialSize(rng));
std::vector<SimpleThread> producers(prodCount);
std::vector<bool> useProducerToken(prodCount);
for (int i = 0; i != prodCount; ++i) {
useProducerToken[i] = static_cast<bool>(std::uniform_int_distribution<int>(0, 1)(rng));
producers[i] = SimpleThread([&](int i) {
ProducerToken t(q);
for (int j = 0; j != count && !failed.load(std::memory_order_relaxed); j += countIncrement) {
if (useProducerToken[i]) {
for (int k = 0; k != countIncrement; ++k) {
ASSERT_OR_FAIL_THREAD(q.enqueue(t, (i << 24) | (k + j)));
}
}
else {
for (int k = 0; k != countIncrement; ++k) {
ASSERT_OR_FAIL_THREAD(q.enqueue((i << 24) | (k + j)));
}
}
FAIL_IF_THREAD_TIMEOUT();
}
}, i);
}
std::vector<SimpleThread> consumers(consCount);
std::vector<bool> useConsumerToken(consCount);
for (int i = 0; i != consCount; ++i) {
useConsumerToken[i] = static_cast<bool>(std::uniform_int_distribution<int>(0, 1)(rng));
consumers[i] = SimpleThread([&](int i) {
int item;
std::vector<int> lastItems(prodCount);
ConsumerToken t(q);
for (int j = 0; j != prodCount; ++j) {
lastItems[j] = -1;
}
bool doneConsuming = false;
while (!doneConsuming && !failed.load(std::memory_order_relaxed)) {
auto producersDoneLocal = producersDone.load(std::memory_order_acquire);
if (useConsumerToken[i]) {
for (int j = 0; j != 10000; ++j) {
if (q.try_dequeue(t, item)) {
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) < count);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) > lastItems[item >> 24]);
lastItems[item >> 24] = item & 0xFFFFFF;
}
else if (producersDoneLocal) {
doneConsuming = true;
break;
}
}
}
else {
for (int j = 0; j != 10000; ++j) {
if (q.try_dequeue(item)) {
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) < count);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) > lastItems[item >> 24]);
lastItems[item >> 24] = item & 0xFFFFFF;
}
else if (producersDoneLocal) {
doneConsuming = true;
break;
}
}
}
FAIL_IF_THREAD_TIMEOUT();
}
}, i);
}
for (int i = 0; i != prodCount; ++i) {
producers[i].join();
}
producersDone.store(true, std::memory_order_release);
for (int i = 0; i != consCount; ++i) {
consumers[i].join();
}
if (failed.load(std::memory_order_relaxed)) {
break;
}
int item;
ASSERT_OR_FAIL(consCount == 0 || !q.try_dequeue(item));
break;
}
case completely_random:
{
int threadCount = std::uniform_int_distribution<int>(0, 32)(rng);
ConcurrentQueue<int, FuzzTraits> q(randInitialSize(rng));
std::vector<SimpleThread> threads(threadCount);
std::vector<unsigned int> seeds(threadCount);
std::vector<unsigned int> opCounts(threadCount);
unsigned int largestOpCount = 0;
for (int i = 0; i != threadCount; ++i) {
opCounts[i] = std::uniform_int_distribution<unsigned int>(0, 500000)(rng);
if (opCounts[i] > largestOpCount) {
largestOpCount = opCounts[i];
}
}
// Note: If you're wondering where all the memory goes, it's mostly here!
std::vector<unsigned int> itemStates(largestOpCount * threadCount * 2);
for (std::size_t j = 0; j != itemStates.size(); ++j) {
itemStates[j] = 0;
}
for (int i = 0; i != threadCount; ++i) {
seeds[i] = std::uniform_int_distribution<unsigned int>(0, 0xFFFFFFFF)(rng);
threads[i] = SimpleThread([&](int i) {
RNG_t rng((unsigned int)seeds[i]);
ConsumerToken ct(q);
ProducerToken pt(q);
int item;
int opCount = opCounts[i];
std::vector<int> lastItems(threadCount * 2); // * 2 because there's two producer queues per thread (one implicit, one explicit)
for (int j = 0; j != threadCount * 2; ++j) {
lastItems[j] = -1;
}
for (int j = 0; j < opCount && !failed.load(std::memory_order_relaxed); ++j) {
int op = std::uniform_int_distribution<int>(0, 7)(rng);
unsigned int* state;
switch (op) {
case 0:
state = &itemStates[(i * 2) * largestOpCount + j];
ASSERT_OR_FAIL_THREAD(*state == 0);
*state = 1;
ASSERT_OR_FAIL_THREAD(q.enqueue(pt, ((i * 2) << 24) | j));
break;
case 1:
state = &itemStates[(i * 2 + 1) * largestOpCount + j];
ASSERT_OR_FAIL_THREAD(*state == 0);
*state = 1;
ASSERT_OR_FAIL_THREAD(q.enqueue(((i * 2 + 1) << 24) | j));
break;
case 2:
if (q.try_dequeue(ct, item)) {
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) >= 0 && (item & 0xFFFFFF) < (int)largestOpCount);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) > lastItems[item >> 24]);
lastItems[item >> 24] = item & 0xFFFFFF;
state = &itemStates[(item >> 24) * largestOpCount + (item & 0xFFFFFF)];
ASSERT_OR_FAIL_THREAD(*state == 1);
*state = 2;
}
break;
case 3:
if (q.try_dequeue(item)) {
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) >= 0 && (item & 0xFFFFFF) < (int)largestOpCount);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) > lastItems[item >> 24]);
lastItems[item >> 24] = item & 0xFFFFFF;
state = &itemStates[(item >> 24) * largestOpCount + (item & 0xFFFFFF)];
ASSERT_OR_FAIL_THREAD(*state == 1);
*state = 2;
}
break;
case 4:
case 5: {
std::vector<int> bulkData(std::min(opCount - j, std::uniform_int_distribution<int>(0, 1024)(rng)));
for (std::size_t k = 0; k != bulkData.size(); ++k) {
state = &itemStates[(i * 2 + op - 4) * largestOpCount + j + k];
ASSERT_OR_FAIL_THREAD(*state == 0);
*state = 1;
bulkData[k] = ((i * 2 + op - 4) << 24) | (j + (int)k);
}
if (op == 4) {
ASSERT_OR_FAIL_THREAD(q.enqueue_bulk(pt, bulkData.begin(), bulkData.size()));
}
else {
ASSERT_OR_FAIL_THREAD(q.enqueue_bulk(bulkData.begin(), bulkData.size()));
}
j += (int)bulkData.size() - 1;
break;
}
case 6:
case 7: {
std::vector<int> bulkData(std::min(opCount - j, std::uniform_int_distribution<int>(0, 1024)(rng)));
std::size_t count = 0;
if (op == 6) {
count = q.try_dequeue_bulk(ct, bulkData.begin(), bulkData.size());
}
else {
count = q.try_dequeue_bulk(bulkData.begin(), bulkData.size());
}
for (std::size_t k = 0; k != count; ++k) {
item = bulkData[k];
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) >= 0 && (item & 0xFFFFFF) < (int)largestOpCount);
ASSERT_OR_FAIL_THREAD((item & 0xFFFFFF) > lastItems[item >> 24]);
lastItems[item >> 24] = item & 0xFFFFFF;
state = &itemStates[(item >> 24) * largestOpCount + (item & 0xFFFFFF)];
ASSERT_OR_FAIL_THREAD(*state == 1);
*state = 2;
}
if (count > 0) {
j += (int)count - 1;
}
break;
}
default:
assert(false);
}
FAIL_IF_THREAD_TIMEOUT();
}
}, i);
}
for (int i = 0; i != threadCount; ++i) {
threads[i].join();
}
#if MCDBGQ_TRACKMEM
auto stats = q.getMemStats(); // Make available under debugger
((void)stats);
#endif
int item;
while (q.try_dequeue(item)) {
unsigned int* state = &itemStates[(item >> 24) * largestOpCount + (item & 0xFFFFFF)];
ASSERT_OR_FAIL(*state == 1);
*state = 2;
}
for (std::size_t j = 0; j != itemStates.size(); ++j) {
ASSERT_OR_FAIL(itemStates[j] == 0 || itemStates[j] == 2);
}
if (failed.load(std::memory_order_relaxed)) {
break;
}
break;
}
case core_add_only_list:
{
int threadCount = std::uniform_int_distribution<int>(0, 48)(rng);
std::vector<SimpleThread> threads(threadCount);
std::vector<int> opCounts(threadCount);
for (int i = 0; i != threadCount; ++i) {
opCounts[i] = std::uniform_int_distribution<int>(0, 500000)(rng);
}
std::size_t expectedMemUsage = 0;
for (int i = 0; i != threadCount; ++i) {
expectedMemUsage += opCounts[i] * sizeof(TestListItem);
}
corealgos::ConcurrentAddOnlyList<TestListItem> list;
for (int i = 0; i != threadCount; ++i) {
threads[i] = SimpleThread([&](int tid) {
auto temp = expectedMemUsage;
((void)temp);
int opCount = opCounts[tid];
for (int j = 0; j != opCount; ++j) {
list.add(new TestListItem((tid << 24) | j));
}
}, i);
}
for (int i = 0; i != threadCount; ++i) {
threads[i].join();
}
std::vector<int> lastItems(threadCount);
for (int i = 0; i != threadCount; ++i) {
lastItems[i] = opCounts[i];
}
auto tail = list.tail();
while (tail != nullptr) {
auto tid = tail->value >> 24;
ASSERT_OR_FAIL(lastItems[tid] - 1 == (tail->value & 0xFFFFFF));
--lastItems[tid];
auto next = tail->prev();
delete tail;
tail = next;
}
break;
}
case core_thread_local:
{
int threadCount = std::uniform_int_distribution<int>(32, 256)(rng);
std::vector<SimpleThread> threads(threadCount);
std::vector<int> opCounts(threadCount);
std::vector<int*> localData(threadCount);
for (int i = 0; i != threadCount; ++i) {
opCounts[i] = std::uniform_int_distribution<int>(10000, 250000)(rng);
}
corealgos::ThreadLocal<TestListItem> tls(1);
for (int i = 0; i != threadCount; ++i) {
threads[i] = SimpleThread([&](int tid) {
auto p = tls.get_or_create();
ASSERT_OR_FAIL_THREAD(p->value == 0);
p->value = tid;
localData[tid] = &p->value;
int opCount = opCounts[tid];
for (int j = 0; j != opCount; ++j) {
auto q = tls.get_or_create();
ASSERT_OR_FAIL_THREAD(q == p);
ASSERT_OR_FAIL_THREAD(q->value == tid);
FAIL_IF_THREAD_TIMEOUT();
}
}, i);
}
for (int i = 0; i != threadCount; ++i) {
threads[i].join();
}
for (int i = 0; i != threadCount; ++i) {
ASSERT_OR_FAIL(localData[i] != nullptr);
ASSERT_OR_FAIL(*localData[i] == i);
}
break;
}
default:
assert(false);
}
++test_count[type];
if (failed.load(std::memory_order_relaxed)) {
out_failReason = failReason.load(std::memory_order_relaxed);
result = false;
}
if (!result) {
++fail_count[type];
break;
}
}
return result;
}
static const char* timestamp()
{
static char buf[32];
time_t time = std::time(NULL);
strcpy(buf, std::asctime(std::localtime(&time)));
buf[strlen(buf) - 1] = '\0'; // Remove trailing newline
return buf;
}
extern "C" { typedef void (*signal_handler_t)(int); }
static std::atomic<std::uint64_t> g_seed(0);
static std::atomic_flag reported_signal_error = ATOMIC_FLAG_INIT;
static std::atomic<signal_handler_t> g_prev_sigsegv(nullptr);
static std::atomic<signal_handler_t> g_prev_sigabrt(nullptr);
static std::mutex g_signal_handler_mutex;
void on_signal(int signal)
{
if (reported_signal_error.test_and_set()) {
return;
}
std::unique_lock<std::mutex> lock(g_signal_handler_mutex);
auto seed = g_seed.load(std::memory_order_acquire);
// Technically undefined behaviour to use stdlib functions,
// but oh well
const char* error = signal == SIGABRT ?
"Abort detected (assertion failed?)" :
"Segmentation fault detected!";
{
std::ofstream fout(LOG_FILE, std::ios::app);
fout << "*** " << error << "\n Seed: " << std::hex << seed << std::endl;
}
std::printf("*** %s\n Seed: %08x%08x\n", error, (uint32_t)(seed >> 32), (uint32_t)(seed));
std::fflush(stdout);
}
extern "C" void signal_handler(int signal)
{
on_signal(signal);
if (signal_handler_t handler_fn = g_prev_sigsegv.load(std::memory_order_relaxed)) {
handler_fn(signal);
}
else {
std::exit(signal);
}
}
#ifdef _WIN32
LONG CALLBACK se_handler(PEXCEPTION_POINTERS info)
{
if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
on_signal(SIGSEGV);
}
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
int main(int argc, char** argv)
{
bool singleSeed = false;
uint64_t seed = 0;
// 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);
}
// Parse command line options
if (argc > 1) {
bool printHelp = 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], "--seed") == 0) {
if (i + 1 == argc || argv[i + 1][0] == '-') {
std::printf("Expected seed number argument for --seed option.\n");
error = true;
continue;
}
++i;
seed = 0;
// hex
for (int j = 0; argv[i][j] != '\0'; ++j) {
char ch = static_cast<char>(std::tolower(argv[i][j]));
if (j == 1 && seed == 0 && ch == 'x') {
continue; // Skip 0x, if any
}
else if (ch >= 'a' && ch <= 'f') {
seed = (seed << 4) | (10 + ch - 'a');
}
else if (ch >= '0' && ch <= '9') {
seed = (seed << 4) | (ch - '0');
}
else {
std::printf("Expected hex seed argument, found '%s' instead\n", argv[i]);
error = true;
}
}
singleSeed = true;
}
else {
std::printf("Unrecognized option '%s'.\n\n", argv[i]);
error = true;
}
}
if (error || printHelp) {
std::printf("%s\n Description: Runs fuzz tests (randomized stability tests) for moodycamel::ConcurrentQueue\n", progName.c_str());
std::printf(" An infinite series of random tests are run, each with a different seed.\nIf a test fails, the seed for that test is reported.\n");
std::printf(" --help Prints this help blurb\n");
std::printf(" --seed N Runs one test with the given seed\n");
return error ? -1 : 0;
}
}
{
bool logExists = true;
{
std::ifstream fin(LOG_FILE);
if (!fin) {
logExists = false;
}
}
std::ofstream fout(LOG_FILE, std::ios::app);
if (logExists) {
fout << "\n\n";
}
if (singleSeed) {
std::printf("Running %d iterations of single test with seed %08x%08x.\n\n", SINGLE_SEED_ITERATIONS, (uint32_t)(seed >> 32), (uint32_t)(seed));
fout << "--- New run (" << timestamp() << "): Executing " << SINGLE_SEED_ITERATIONS << " iterations of a single test with seed " << std::hex << seed << " ---" << std::endl;
}
else {
std::printf("Running random fuzz tests for moodycamel::ConcurrentQueue.\n");
std::printf("Press CTRL+C to exit.\n");
std::printf("(Run %s --help for options.)\n\n", progName.c_str());
fout << "--- New run (" << timestamp() << "): Executing random fuzz tests ---" << std::endl;
}
}
int exitCode = 0;
test_type test;
const char* failReason;
if (singleSeed) {
if (!run_test(seed, SINGLE_SEED_ITERATIONS, test, failReason)) {
exitCode = 1;
std::ofstream fout(LOG_FILE, std::ios::app);
fout << test_names[test] << " failed: " << failReason << std::endl;
std::printf(" %s failed: %s\n", test_names[test], failReason);
}
else {
std::ofstream fout(LOG_FILE, std::ios::app);
fout << test_names[test] << " succeeded!" << std::endl;
std::printf(" %s succeeded!\n", test_names[test]);
}
}
else {
#ifdef _WIN32
AddVectoredExceptionHandler(1 /* first? */, &se_handler);
#endif
uint32_t iteration = 0;
while (true) {
seed = (static_cast<uint64_t>(std::time(NULL)) << 32) | iteration++;
// MurmurHash3 64-bit finalizer
seed ^= seed >> 33;
seed *= 0xff51afd7ed558ccd;
seed ^= seed >> 33;
seed *= 0xc4ceb9fe1a85ec53;
g_seed.store(seed, std::memory_order_release);
std::signal(SIGSEGV, signal_handler);
std::signal(SIGABRT, signal_handler);
bool result;
try {
result = run_test(seed, 2, test, failReason);
}
catch (std::exception const& e) {
std::ofstream fout(LOG_FILE, std::ios::app);
fout << "*** Exception thrown: " << e.what() << "\n Seed: " << std::hex << seed << "\n Test: " << test_names[test] << std::endl;
std::printf("*** Exception thrown: %s\n Seed: %08x%08x\n Test: %s\n\n", e.what(), (uint32_t)(seed >> 32), (uint32_t)(seed), test_names[test]);
std::exit(2); // There shouldn't be any exceptions!
}
catch (...) {
std::ofstream fout(LOG_FILE, std::ios::app);
fout << "*** Unknown exception thrown!\n Seed: " << std::hex << seed << "\n Test: " << test_names[test] << std::endl;
std::printf("*** Unknown exception thrown!\n Seed: %08x%08x\n Test: %s\n\n", (uint32_t)(seed >> 32), (uint32_t)(seed), test_names[test]);
std::exit(2);
}
std::signal(SIGSEGV, SIG_DFL);
std::signal(SIGABRT, SIG_DFL);
if (!result) {
exitCode = 1;
std::ofstream fout(LOG_FILE, std::ios::app);
fout << "*** Failure detected!\n Seed: " << std::hex << seed << "\n Test: " << test_names[test] << "\n Reason: " << failReason << std::endl;
std::printf("*** Failure detected!\n Seed: %08x%08x\n Test: %s\n Reason: %s\n", (uint32_t)(seed >> 32), (uint32_t)(seed), test_names[test], failReason);
}
if ((iteration & 31) == 0) {
std::uint64_t total = 0;
char breakdown[128 * TEST_TYPE_COUNT];
char* ptr = breakdown;
for (int i = 0; i != TEST_TYPE_COUNT; ++i) {
std::sprintf(ptr, " %s: %llu successful, %llu failed\n", test_names[i], (unsigned long long)(test_count[i] - fail_count[i]), (unsigned long long)fail_count[i]);
ptr += std::strlen(ptr);
total += test_count[i];
}
std::ofstream fout(LOG_FILE, std::ios::app);
fout << "Executed " << total << " tests so far:\n" << breakdown;
std::printf("Executed %llu tests so far:\n%s", (unsigned long long)total, breakdown);
}
}
}
return exitCode;
}