// ©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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #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(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 randTest(0, TEST_TYPE_COUNT - 1); std::uniform_int_distribution randInitialSize(0, 70); auto type = static_cast(randTest(baseRng)); out_type = type; for (int iteration = 0; iteration != iterations; ++iteration) { RNG_t rng(baseRng); std::atomic failed(false); std::atomic failReason; failReason = nullptr; SystemTime startTime = getSystemTime(); switch (type) { case multithread_produce: { const int countIncrement = std::uniform_int_distribution(1, 1000)(rng); int count = std::uniform_int_distribution(0, 500)(rng) * countIncrement; int prodCount = std::uniform_int_distribution(0, 6)(rng); bool useConsumerToken = static_cast(std::uniform_int_distribution(0, 1)(rng)); ConcurrentQueue q(randInitialSize(rng)); std::vector producers(prodCount); std::vector useProducerToken(prodCount); for (int i = 0; i != prodCount; ++i) { useProducerToken[i] = static_cast(std::uniform_int_distribution(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 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(1, 1000)(rng); int count = std::uniform_int_distribution(0, 500)(rng) * countIncrement; int consCount = std::uniform_int_distribution(0, 6)(rng); bool useProducerToken = static_cast(std::uniform_int_distribution(0, 1)(rng)); std::atomic producerDone(false); ConcurrentQueue q(randInitialSize(rng)); std::vector consumers(consCount); std::vector useConsumerToken(consCount); for (int i = 0; i != consCount; ++i) { useConsumerToken[i] = static_cast(std::uniform_int_distribution(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(1, 1000)(rng); int count = std::uniform_int_distribution(0, 500)(rng) * countIncrement; int prodCount = std::uniform_int_distribution(0, 6)(rng); int consCount = std::uniform_int_distribution(0, 6)(rng); std::atomic producersDone(false); ConcurrentQueue q(randInitialSize(rng)); std::vector producers(prodCount); std::vector useProducerToken(prodCount); for (int i = 0; i != prodCount; ++i) { useProducerToken[i] = static_cast(std::uniform_int_distribution(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 consumers(consCount); std::vector useConsumerToken(consCount); for (int i = 0; i != consCount; ++i) { useConsumerToken[i] = static_cast(std::uniform_int_distribution(0, 1)(rng)); consumers[i] = SimpleThread([&](int i) { int item; std::vector 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(0, 32)(rng); ConcurrentQueue q(randInitialSize(rng)); std::vector threads(threadCount); std::vector seeds(threadCount); std::vector opCounts(threadCount); unsigned int largestOpCount = 0; for (int i = 0; i != threadCount; ++i) { opCounts[i] = std::uniform_int_distribution(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 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(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 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(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 bulkData(std::min(opCount - j, std::uniform_int_distribution(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 bulkData(std::min(opCount - j, std::uniform_int_distribution(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(0, 48)(rng); std::vector threads(threadCount); std::vector opCounts(threadCount); for (int i = 0; i != threadCount; ++i) { opCounts[i] = std::uniform_int_distribution(0, 500000)(rng); } std::size_t expectedMemUsage = 0; for (int i = 0; i != threadCount; ++i) { expectedMemUsage += opCounts[i] * sizeof(TestListItem); } corealgos::ConcurrentAddOnlyList 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 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(32, 256)(rng); std::vector threads(threadCount); std::vector opCounts(threadCount); std::vector localData(threadCount); for (int i = 0; i != threadCount; ++i) { opCounts[i] = std::uniform_int_distribution(10000, 250000)(rng); } corealgos::ThreadLocal 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 g_seed(0); static std::atomic_flag reported_signal_error = ATOMIC_FLAG_INIT; static std::atomic g_prev_sigsegv(nullptr); static std::atomic 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 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(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(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; }