Add TestDriver class
This commit is contained in:
@@ -17,8 +17,6 @@
|
|||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DEBUG_VERBOSE 0
|
|
||||||
|
|
||||||
// ==================== BEGIN IMPLEMENTATION ====================
|
// ==================== BEGIN IMPLEMENTATION ====================
|
||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
@@ -45,11 +43,6 @@ struct Node {
|
|||||||
|
|
||||||
Type type = Type::Invalid;
|
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 {
|
struct Node4 : Node {
|
||||||
// Sorted
|
// Sorted
|
||||||
@@ -1015,95 +1008,15 @@ int main(void) {
|
|||||||
|
|
||||||
#ifdef ENABLE_FUZZ
|
#ifdef ENABLE_FUZZ
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
// TODO call setOldestVersion, and check range writes/reads
|
TestDriver<ConflictSet::Impl> driver{data, size};
|
||||||
Arbitrary arbitrary{{data, size}};
|
|
||||||
|
|
||||||
int64_t writeVersion = 0;
|
do {
|
||||||
ConflictSet::Impl cs{writeVersion};
|
bool success = checkCorrectness(driver.cs.root, driver.refImpl);
|
||||||
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<std::string_view>(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);
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
{
|
} while (!driver.next());
|
||||||
int numReads = arbitrary.bounded(10);
|
|
||||||
int64_t v = writeVersion - arbitrary.bounded(10);
|
|
||||||
auto *reads = new (arena) ConflictSet::ReadRange[numReads];
|
|
||||||
auto keys = set<std::string_view>(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
97
Internal.h
97
Internal.h
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -12,6 +13,11 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#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
|
// GCOVR_EXCL_START
|
||||||
|
|
||||||
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
__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));
|
return printable(std::string_view((const char *)key.p, key.len));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class ConflictSetImpl> 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<std::string_view>(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<std::string_view>(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
|
// GCOVR_EXCL_STOP
|
Reference in New Issue
Block a user