From 3735a4355377c963dd6653068dc44c709737c333 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Tue, 30 Jan 2024 11:13:21 -0800 Subject: [PATCH] Add TestDriver class --- ConflictSet.cpp | 97 +++---------------------------------------------- Internal.h | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 92 deletions(-) diff --git a/ConflictSet.cpp b/ConflictSet.cpp index e85caf6..b626797 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -17,8 +17,6 @@ #include #endif -#define DEBUG_VERBOSE 0 - // ==================== BEGIN IMPLEMENTATION ==================== struct Entry { @@ -45,11 +43,6 @@ struct Node { Type type = Type::Invalid; }; -Node *getChild(Node *self, uint8_t index); -int getChildGeq(Node *self, int child); -Node *&getOrCreateChild(Node *&self, uint8_t index); -Node *newNode(); -void eraseChild(Node *self, uint8_t index); struct Node4 : Node { // Sorted @@ -1015,95 +1008,15 @@ int main(void) { #ifdef ENABLE_FUZZ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - // TODO call setOldestVersion, and check range writes/reads - Arbitrary arbitrary{{data, size}}; + TestDriver driver{data, size}; - int64_t writeVersion = 0; - ConflictSet::Impl cs{writeVersion}; - ReferenceImpl refImpl{writeVersion}; - - while (arbitrary.hasEntropy()) { - Arena arena; - { - int numWrites = arbitrary.bounded(10); - int64_t v = ++writeVersion; - auto *writes = new (arena) ConflictSet::WriteRange[numWrites]; - auto keys = set(arena); - while (int(keys.size()) < numWrites) { - if (!arbitrary.hasEntropy()) { - // Tell the fuzzer it's not interesting - return -1; - } - int keyLen = arbitrary.bounded(8); - auto *begin = new (arena) uint8_t[keyLen]; - arbitrary.randomBytes(begin, keyLen); - keys.insert(std::string_view((const char *)begin, keyLen)); - } - auto iter = keys.begin(); - for (int i = 0; i < numWrites; ++i) { - writes[i].begin.p = (const uint8_t *)iter->data(); - writes[i].begin.len = iter->size(); - ++iter; - writes[i].end.len = 0; - writes[i].writeVersion = v; -#if DEBUG_VERBOSE && !defined(NDEBUG) - printf("Write: {%s} -> %d\n", printable(writes[i].begin).c_str(), - int(writes[i].writeVersion)); -#endif - } - assert(iter == keys.end()); - cs.addWrites(writes, numWrites); - refImpl.addWrites(writes, numWrites); - } - bool success = checkCorrectness(cs.root, refImpl); + do { + bool success = checkCorrectness(driver.cs.root, driver.refImpl); if (!success) { abort(); } - { - int numReads = arbitrary.bounded(10); - int64_t v = writeVersion - arbitrary.bounded(10); - auto *reads = new (arena) ConflictSet::ReadRange[numReads]; - auto keys = set(arena); - while (int(keys.size()) < numReads) { - if (!arbitrary.hasEntropy()) { - // Tell the fuzzer it's not interesting - return -1; - } - int keyLen = arbitrary.bounded(8); - auto *begin = new (arena) uint8_t[keyLen]; - arbitrary.randomBytes(begin, keyLen); - keys.insert(std::string_view((const char *)begin, keyLen)); - } - auto iter = keys.begin(); - for (int i = 0; i < numReads; ++i) { - reads[i].begin.p = (const uint8_t *)iter->data(); - reads[i].begin.len = iter->size(); - ++iter; - reads[i].end.len = 0; - reads[i].readVersion = v; -#if DEBUG_VERBOSE && !defined(NDEBUG) - printf("Read: {%s} at %d\n", printable(reads[i].begin).c_str(), - int(reads[i].readVersion)); -#endif - } - assert(iter == keys.end()); - auto *results1 = new (arena) ConflictSet::Result[numReads]; - auto *results2 = new (arena) ConflictSet::Result[numReads]; - cs.check(reads, results1, numReads); - refImpl.check(reads, results2, numReads); - for (int i = 0; i < numReads; ++i) { - if (results1[i] != results2[i]) { - fprintf(stderr, "Expected %d, got %d for read of %s at version %d\n", - results2[i], results1[i], printable(reads[i].begin).c_str(), - int(reads[i].readVersion)); - std::string referenceLogicalMap; - refImpl.printLogical(referenceLogicalMap); - fprintf(stderr, "Logical map:\n\n%s\n", referenceLogicalMap.c_str()); - abort(); - } - } - } - } + } while (!driver.next()); + return 0; } #endif diff --git a/Internal.h b/Internal.h index f05cfc4..2a5c0a9 100644 --- a/Internal.h +++ b/Internal.h @@ -2,6 +2,7 @@ #include "ConflictSet.h" +#include #include #include #include @@ -12,6 +13,11 @@ #include #include +#define DEBUG_VERBOSE 0 + +// This header contains code that we want to reuse outside of ConflictSet.cpp or +// want to exclude from coverage since it's only testing related. + // GCOVR_EXCL_START __attribute__((always_inline)) inline void *safe_malloc(size_t s) { @@ -442,4 +448,95 @@ inline std::string printable(const Key &key) { return printable(std::string_view((const char *)key.p, key.len)); } +template struct TestDriver { + // TODO call setOldestVersion, and check range writes/reads + Arbitrary arbitrary; + explicit TestDriver(const uint8_t *data, size_t size) + : arbitrary({data, size}) {} + + int64_t writeVersion = 0; + ConflictSetImpl cs{writeVersion}; + ReferenceImpl refImpl{writeVersion}; + + // Call until it returns true, for "done". Check internal invariants etc + // between calls to next. + bool next() { + if (!arbitrary.hasEntropy()) { + return true; + } + Arena arena; + { + int numWrites = arbitrary.bounded(10); + int64_t v = ++writeVersion; + auto *writes = new (arena) ConflictSet::WriteRange[numWrites]; + auto keys = set(arena); + while (int(keys.size()) < numWrites) { + if (!arbitrary.hasEntropy()) { + return true; + } + int keyLen = arbitrary.bounded(8); + auto *begin = new (arena) uint8_t[keyLen]; + arbitrary.randomBytes(begin, keyLen); + keys.insert(std::string_view((const char *)begin, keyLen)); + } + auto iter = keys.begin(); + for (int i = 0; i < numWrites; ++i) { + writes[i].begin.p = (const uint8_t *)iter->data(); + writes[i].begin.len = iter->size(); + ++iter; + writes[i].end.len = 0; + writes[i].writeVersion = v; +#if DEBUG_VERBOSE && !defined(NDEBUG) + printf("Write: {%s} -> %d\n", printable(writes[i].begin).c_str(), + int(writes[i].writeVersion)); +#endif + } + assert(iter == keys.end()); + cs.addWrites(writes, numWrites); + refImpl.addWrites(writes, numWrites); + } + { + int numReads = arbitrary.bounded(10); + int64_t v = writeVersion - arbitrary.bounded(10); + auto *reads = new (arena) ConflictSet::ReadRange[numReads]; + auto keys = set(arena); + while (int(keys.size()) < numReads) { + if (!arbitrary.hasEntropy()) { + return true; + } + int keyLen = arbitrary.bounded(8); + auto *begin = new (arena) uint8_t[keyLen]; + arbitrary.randomBytes(begin, keyLen); + keys.insert(std::string_view((const char *)begin, keyLen)); + } + auto iter = keys.begin(); + for (int i = 0; i < numReads; ++i) { + reads[i].begin.p = (const uint8_t *)iter->data(); + reads[i].begin.len = iter->size(); + ++iter; + reads[i].end.len = 0; + reads[i].readVersion = v; +#if DEBUG_VERBOSE && !defined(NDEBUG) + printf("Read: {%s} at %d\n", printable(reads[i].begin).c_str(), + int(reads[i].readVersion)); +#endif + } + assert(iter == keys.end()); + auto *results1 = new (arena) ConflictSet::Result[numReads]; + auto *results2 = new (arena) ConflictSet::Result[numReads]; + cs.check(reads, results1, numReads); + refImpl.check(reads, results2, numReads); + for (int i = 0; i < numReads; ++i) { + if (results1[i] != results2[i]) { + fprintf(stderr, "Expected %d, got %d for read of %s at version %d\n", + results2[i], results1[i], printable(reads[i].begin).c_str(), + int(reads[i].readVersion)); + abort(); + } + } + } + return false; + } +}; + // GCOVR_EXCL_STOP \ No newline at end of file