Initial Commit - Lesson 31 (Commit #1)
This commit is contained in:
867
Plugins/GameLiftServerSDK/ThirdParty/concurrentqueue/tests/fuzztests/fuzztests.cpp
vendored
Normal file
867
Plugins/GameLiftServerSDK/ThirdParty/concurrentqueue/tests/fuzztests/fuzztests.cpp
vendored
Normal file
@@ -0,0 +1,867 @@
|
||||
// ©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;
|
||||
}
|
||||
9
Plugins/GameLiftServerSDK/ThirdParty/concurrentqueue/tests/fuzztests/makefile
vendored
Normal file
9
Plugins/GameLiftServerSDK/ThirdParty/concurrentqueue/tests/fuzztests/makefile
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# ©2013-2014 Cameron Desrochers
|
||||
|
||||
include ../../build/makefile.inc
|
||||
|
||||
default:
|
||||
$(MAKE) -C ../../build bin/fuzztests$(EXT)
|
||||
|
||||
run: default
|
||||
../../build/bin/fuzztests$(EXT)
|
||||
Reference in New Issue
Block a user