aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt16
-rw-r--r--headers/skiplist.cuh359
-rw-r--r--tests/arena_test.cu2
-rw-r--r--tests/skiplist_test.cu376
m---------thirdparty/googletest0
-rw-r--r--utils/random.h (renamed from headers/random.h)0
-rw-r--r--utils/testutil.cpp51
-rw-r--r--utils/testutil.cuh88
9 files changed, 892 insertions, 3 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..3380c92
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "thirdparty/googletest"]
+ path = thirdparty/googletest
+ url = https://github.com/google/googletest.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a22fb5..b33b646 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -10,11 +10,23 @@ add_library(cleveldb "")
target_sources(
cleveldb
PUBLIC
+ "utils/testutil.cuh"
+ "utils/testutil.cpp"
"headers/arena.cuh"
"headers/arena.cu"
- "headers/random.h"
+ "utils/random.h"
+ "headers/skiplist.cuh"
)
+enable_testing()
+set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+set(install_gtest OFF)
+set(install_gmock OFF)
+set(build_gmock ON)
+
+
+add_subdirectory("thirdparty/googletest")
+
function(cleveldb_test test_file)
get_filename_component(test_target_name "${test_file}" NAME_WE)
@@ -22,7 +34,7 @@ function(cleveldb_test test_file)
target_sources(arena_test
PRIVATE
"${test_file}")
- target_link_libraries("${test_target_name}" cleveldb)
+ target_link_libraries("${test_target_name}" cleveldb gtest)
add_test(NAME "${test_target_name}" COMMAND "${test_target_name}")
endfunction(cleveldb_test)
diff --git a/headers/skiplist.cuh b/headers/skiplist.cuh
new file mode 100644
index 0000000..fb19f7e
--- /dev/null
+++ b/headers/skiplist.cuh
@@ -0,0 +1,359 @@
+//
+// Created by user on 05/11/2021.
+//
+
+#ifndef CLEVELDB_SKIPLIST_CUH
+#define CLEVELDB_SKIPLIST_CUH
+
+#include <atomic>
+#include <cassert>
+#include <cstdlib>
+
+#include "arena.h"
+#include "../utils/random.h"
+
+namespace cleveldb {
+
+ class Arena;
+
+ template <typename Key, class Comparator>
+ class SkipList {
+ private:
+ struct Node;
+
+ public:
+ // Create a new SkipList object that will use "cmp" for comparing keys,
+ // and will allocate memory using "*arena". Objects allocated in the arena
+ // must remain allocated for the lifetime of the skiplist object.
+ explicit SkipList(Comparator cmp, Arena* arena);
+
+ SkipList(const SkipList&) = delete;
+ SkipList& operator=(const SkipList&) = delete;
+
+ // Insert key into the list.
+ // REQUIRES: nothing that compares equal to key is currently in the list.
+ void Insert(const Key& key);
+
+ // Returns true iff an entry that compares equal to key is in the list.
+ bool Contains(const Key& key) const;
+
+ // Iteration over the contents of a skip list
+ class Iterator {
+ public:
+ // Initialize an iterator over the specified list.
+ // The returned iterator is not valid.
+ explicit Iterator(const SkipList* list);
+
+ // Returns true iff the iterator is positioned at a valid node.
+ bool Valid() const;
+
+ // Returns the key at the current position.
+ // REQUIRES: Valid()
+ const Key& key() const;
+
+ // Advances to the next position.
+ // REQUIRES: Valid()
+ void Next();
+
+ // Advances to the previous position.
+ // REQUIRES: Valid()
+ void Prev();
+
+ // Advance to the first entry with a key >= target
+ void Seek(const Key& target);
+
+ // Position at the first entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToFirst();
+
+ // Position at the last entry in list.
+ // Final state of iterator is Valid() iff list is not empty.
+ void SeekToLast();
+
+ private:
+ const SkipList* list_;
+ Node* node_;
+ // Intentionally copyable
+ };
+
+ private:
+ enum { kMaxHeight = 12 };
+
+ inline int GetMaxHeight() const {
+ return max_height_.load(std::memory_order_relaxed);
+ }
+
+ Node* NewNode(const Key& key, int height);
+ int RandomHeight();
+ bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
+
+ // Return true if key is greater than the data stored in "n"
+ bool KeyIsAfterNode(const Key& key, Node* n) const;
+
+ // Return the earliest node that comes at or after key.
+ // Return nullptr if there is no such node.
+ //
+ // If prev is non-null, fills prev[level] with pointer to previous
+ // node at "level" for every level in [0..max_height_-1].
+ Node* FindGreaterOrEqual(const Key& key, Node** prev) const;
+
+ // Return the latest node with a key < key.
+ // Return head_ if there is no such node.
+ Node* FindLessThan(const Key& key) const;
+
+ // Return the last node in the list.
+ // Return head_ if list is empty.
+ Node* FindLast() const;
+
+ // Immutable after construction
+ Comparator const compare_;
+ Arena* const arena_; // Arena used for allocations of nodes
+
+ Node* const head_;
+
+ // Modified only by Insert(). Read racily by readers, but stale
+ // values are ok.
+ std::atomic<int> max_height_; // Height of the entire list
+
+ // Read/written only by Insert().
+ Random rnd_;
+ };
+
+// Implementation details follow
+ template <typename Key, class Comparator>
+ struct SkipList<Key, Comparator>::Node {
+ explicit Node(const Key& k) : key(k) {}
+
+ Key const key;
+
+ // Accessors/mutators for links. Wrapped in methods so we can
+ // add the appropriate barriers as necessary.
+ Node* Next(int n) {
+ assert(n >= 0);
+ // Use an 'acquire load' so that we observe a fully initialized
+ // version of the returned Node.
+ return next_[n].load(std::memory_order_acquire);
+ }
+ void SetNext(int n, Node* x) {
+ assert(n >= 0);
+ // Use a 'release store' so that anybody who reads through this
+ // pointer observes a fully initialized version of the inserted node.
+ next_[n].store(x, std::memory_order_release);
+ }
+
+ // No-barrier variants that can be safely used in a few locations.
+ Node* NoBarrier_Next(int n) {
+ assert(n >= 0);
+ return next_[n].load(std::memory_order_relaxed);
+ }
+ void NoBarrier_SetNext(int n, Node* x) {
+ assert(n >= 0);
+ next_[n].store(x, std::memory_order_relaxed);
+ }
+
+ private:
+ // Array of length equal to the node height. next_[0] is lowest level link.
+ std::atomic<Node*> next_[1];
+ };
+
+ template <typename Key, class Comparator>
+ typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::NewNode(
+ const Key& key, int height) {
+ char* const node_memory = arena_->AllocateAligned(
+ sizeof(Node) + sizeof(std::atomic<Node*>) * (height - 1));
+ return new (node_memory) Node(key);
+ }
+
+ template <typename Key, class Comparator>
+ inline SkipList<Key, Comparator>::Iterator::Iterator(const SkipList* list) {
+ list_ = list;
+ node_ = nullptr;
+ }
+
+ template <typename Key, class Comparator>
+ inline bool SkipList<Key, Comparator>::Iterator::Valid() const {
+ return node_ != nullptr;
+ }
+
+ template <typename Key, class Comparator>
+ inline const Key& SkipList<Key, Comparator>::Iterator::key() const {
+ assert(Valid());
+ return node_->key;
+ }
+
+ template <typename Key, class Comparator>
+ inline void SkipList<Key, Comparator>::Iterator::Next() {
+ assert(Valid());
+ node_ = node_->Next(0);
+ }
+
+ template <typename Key, class Comparator>
+ inline void SkipList<Key, Comparator>::Iterator::Prev() {
+ // Instead of using explicit "prev" links, we just search for the
+ // last node that falls before key.
+ assert(Valid());
+ node_ = list_->FindLessThan(node_->key);
+ if (node_ == list_->head_) {
+ node_ = nullptr;
+ }
+ }
+
+ template <typename Key, class Comparator>
+ inline void SkipList<Key, Comparator>::Iterator::Seek(const Key& target) {
+ node_ = list_->FindGreaterOrEqual(target, nullptr);
+ }
+
+ template <typename Key, class Comparator>
+ inline void SkipList<Key, Comparator>::Iterator::SeekToFirst() {
+ node_ = list_->head_->Next(0);
+ }
+
+ template <typename Key, class Comparator>
+ inline void SkipList<Key, Comparator>::Iterator::SeekToLast() {
+ node_ = list_->FindLast();
+ if (node_ == list_->head_) {
+ node_ = nullptr;
+ }
+ }
+
+ template <typename Key, class Comparator>
+ int SkipList<Key, Comparator>::RandomHeight() {
+ // Increase height with probability 1 in kBranching
+ static const unsigned int kBranching = 4;
+ int height = 1;
+ while (height < kMaxHeight && ((rnd_.Next() % kBranching) == 0)) {
+ height++;
+ }
+ assert(height > 0);
+ assert(height <= kMaxHeight);
+ return height;
+ }
+
+ template <typename Key, class Comparator>
+ bool SkipList<Key, Comparator>::KeyIsAfterNode(const Key& key, Node* n) const {
+ // null n is considered infinite
+ return (n != nullptr) && (compare_(n->key, key) < 0);
+ }
+
+ template <typename Key, class Comparator>
+ typename SkipList<Key, Comparator>::Node*
+ SkipList<Key, Comparator>::FindGreaterOrEqual(const Key& key,
+ Node** prev) const {
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ while (true) {
+ Node* next = x->Next(level);
+ if (KeyIsAfterNode(key, next)) {
+ // Keep searching in this list
+ x = next;
+ } else {
+ if (prev != nullptr) prev[level] = x;
+ if (level == 0) {
+ return next;
+ } else {
+ // Switch to next list
+ level--;
+ }
+ }
+ }
+ }
+
+ template <typename Key, class Comparator>
+ typename SkipList<Key, Comparator>::Node*
+ SkipList<Key, Comparator>::FindLessThan(const Key& key) const {
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ while (true) {
+ assert(x == head_ || compare_(x->key, key) < 0);
+ Node* next = x->Next(level);
+ if (next == nullptr || compare_(next->key, key) >= 0) {
+ if (level == 0) {
+ return x;
+ } else {
+ // Switch to next list
+ level--;
+ }
+ } else {
+ x = next;
+ }
+ }
+ }
+
+ template <typename Key, class Comparator>
+ typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::FindLast()
+ const {
+ Node* x = head_;
+ int level = GetMaxHeight() - 1;
+ while (true) {
+ Node* next = x->Next(level);
+ if (next == nullptr) {
+ if (level == 0) {
+ return x;
+ } else {
+ // Switch to next list
+ level--;
+ }
+ } else {
+ x = next;
+ }
+ }
+ }
+
+ template <typename Key, class Comparator>
+ SkipList<Key, Comparator>::SkipList(Comparator cmp, Arena* arena)
+ : compare_(cmp),
+ arena_(arena),
+ head_(NewNode(0 /* any key will do */, kMaxHeight)),
+ max_height_(1),
+ rnd_(0xdeadbeef) {
+ for (int i = 0; i < kMaxHeight; i++) {
+ head_->SetNext(i, nullptr);
+ }
+ }
+
+ template <typename Key, class Comparator>
+ void SkipList<Key, Comparator>::Insert(const Key& key) {
+ // TODO(opt): We can use a barrier-free variant of FindGreaterOrEqual()
+ // here since Insert() is externally synchronized.
+ Node* prev[kMaxHeight];
+ Node* x = FindGreaterOrEqual(key, prev);
+
+ // Our data structure does not allow duplicate insertion
+ assert(x == nullptr || !Equal(key, x->key));
+
+ int height = RandomHeight();
+ if (height > GetMaxHeight()) {
+ for (int i = GetMaxHeight(); i < height; i++) {
+ prev[i] = head_;
+ }
+ // It is ok to mutate max_height_ without any synchronization
+ // with concurrent readers. A concurrent reader that observes
+ // the new value of max_height_ will see either the old value of
+ // new level pointers from head_ (nullptr), or a new value set in
+ // the loop below. In the former case the reader will
+ // immediately drop to the next level since nullptr sorts after all
+ // keys. In the latter case the reader will use the new node.
+ max_height_.store(height, std::memory_order_relaxed);
+ }
+
+ x = NewNode(key, height);
+ for (int i = 0; i < height; i++) {
+ // NoBarrier_SetNext() suffices since we will add a barrier when
+ // we publish a pointer to "x" in prev[i].
+ x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i));
+ prev[i]->SetNext(i, x);
+ }
+ }
+
+ template <typename Key, class Comparator>
+ bool SkipList<Key, Comparator>::Contains(const Key& key) const {
+ Node* x = FindGreaterOrEqual(key, nullptr);
+ if (x != nullptr && Equal(key, x->key)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+} // namespace cleveldb
+#endif //CLEVELDB_SKIPLIST_CUH
diff --git a/tests/arena_test.cu b/tests/arena_test.cu
index be9795d..9be7739 100644
--- a/tests/arena_test.cu
+++ b/tests/arena_test.cu
@@ -1,5 +1,5 @@
#include "../headers/arena.cuh"
-#include "../headers/random.h"
+#include "../utils/random.h"
using namespace cleveldb;
diff --git a/tests/skiplist_test.cu b/tests/skiplist_test.cu
new file mode 100644
index 0000000..d636708
--- /dev/null
+++ b/tests/skiplist_test.cu
@@ -0,0 +1,376 @@
+//
+// Created by user on 06/11/2021.
+//
+
+// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file. See the AUTHORS file for names of contributors.
+
+#include "db/skiplist.h"
+
+#include <atomic>
+#include <set>
+
+#include "gtest/gtest.h"
+#include "leveldb/env.h"
+#include "port/thread_annotations.h"
+#include "util/arena.h"
+#include "util/hash.h"
+#include "util/random.h"
+#include "util/testutil.h"
+
+namespace leveldb {
+
+ typedef uint64_t Key;
+
+ struct Comparator {
+ int operator()(const Key& a, const Key& b) const {
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return +1;
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ TEST(SkipTest, Empty) {
+ Arena arena;
+ Comparator cmp;
+ SkipList<Key, Comparator> list(cmp, &arena);
+ ASSERT_TRUE(!list.Contains(10));
+
+ SkipList<Key, Comparator>::Iterator iter(&list);
+ ASSERT_TRUE(!iter.Valid());
+ iter.SeekToFirst();
+ ASSERT_TRUE(!iter.Valid());
+ iter.Seek(100);
+ ASSERT_TRUE(!iter.Valid());
+ iter.SeekToLast();
+ ASSERT_TRUE(!iter.Valid());
+}
+
+TEST(SkipTest, InsertAndLookup) {
+const int N = 2000;
+const int R = 5000;
+Random rnd(1000);
+std::set<Key> keys;
+Arena arena;
+Comparator cmp;
+SkipList<Key, Comparator> list(cmp, &arena);
+for (int i = 0; i < N; i++) {
+Key key = rnd.Next() % R;
+if (keys.insert(key).second) {
+list.Insert(key);
+}
+}
+
+for (int i = 0; i < R; i++) {
+if (list.Contains(i)) {
+ASSERT_EQ(keys.count(i), 1);
+} else {
+ASSERT_EQ(keys.count(i), 0);
+}
+}
+
+// Simple iterator tests
+{
+SkipList<Key, Comparator>::Iterator iter(&list);
+ASSERT_TRUE(!iter.Valid());
+
+iter.Seek(0);
+ASSERT_TRUE(iter.Valid());
+ASSERT_EQ(*(keys.begin()), iter.key());
+
+iter.SeekToFirst();
+ASSERT_TRUE(iter.Valid());
+ASSERT_EQ(*(keys.begin()), iter.key());
+
+iter.SeekToLast();
+ASSERT_TRUE(iter.Valid());
+ASSERT_EQ(*(keys.rbegin()), iter.key());
+}
+
+// Forward iteration test
+for (int i = 0; i < R; i++) {
+SkipList<Key, Comparator>::Iterator iter(&list);
+iter.Seek(i);
+
+// Compare against model iterator
+std::set<Key>::iterator model_iter = keys.lower_bound(i);
+for (int j = 0; j < 3; j++) {
+if (model_iter == keys.end()) {
+ASSERT_TRUE(!iter.Valid());
+break;
+} else {
+ASSERT_TRUE(iter.Valid());
+ASSERT_EQ(*model_iter, iter.key());
+++model_iter;
+iter.Next();
+}
+}
+}
+
+// Backward iteration test
+{
+SkipList<Key, Comparator>::Iterator iter(&list);
+iter.SeekToLast();
+
+// Compare against model iterator
+for (std::set<Key>::reverse_iterator model_iter = keys.rbegin();
+model_iter != keys.rend(); ++model_iter) {
+ASSERT_TRUE(iter.Valid());
+ASSERT_EQ(*model_iter, iter.key());
+iter.Prev();
+}
+ASSERT_TRUE(!iter.Valid());
+}
+}
+
+// We want to make sure that with a single writer and multiple
+// concurrent readers (with no synchronization other than when a
+// reader's iterator is created), the reader always observes all the
+// data that was present in the skip list when the iterator was
+// constructed. Because insertions are happening concurrently, we may
+// also observe new values that were inserted since the iterator was
+// constructed, but we should never miss any values that were present
+// at iterator construction time.
+//
+// We generate multi-part keys:
+// <key,gen,hash>
+// where:
+// key is in range [0..K-1]
+// gen is a generation number for key
+// hash is hash(key,gen)
+//
+// The insertion code picks a random key, sets gen to be 1 + the last
+// generation number inserted for that key, and sets hash to Hash(key,gen).
+//
+// At the beginning of a read, we snapshot the last inserted
+// generation number for each key. We then iterate, including random
+// calls to Next() and Seek(). For every key we encounter, we
+// check that it is either expected given the initial snapshot or has
+// been concurrently added since the iterator started.
+class ConcurrentTest {
+private:
+ static constexpr uint32_t K = 4;
+
+ static uint64_t key(Key key) { return (key >> 40); }
+ static uint64_t gen(Key key) { return (key >> 8) & 0xffffffffu; }
+ static uint64_t hash(Key key) { return key & 0xff; }
+
+ static uint64_t HashNumbers(uint64_t k, uint64_t g) {
+ uint64_t data[2] = {k, g};
+ return Hash(reinterpret_cast<char*>(data), sizeof(data), 0);
+ }
+
+ static Key MakeKey(uint64_t k, uint64_t g) {
+ static_assert(sizeof(Key) == sizeof(uint64_t), "");
+ assert(k <= K); // We sometimes pass K to seek to the end of the skiplist
+ assert(g <= 0xffffffffu);
+ return ((k << 40) | (g << 8) | (HashNumbers(k, g) & 0xff));
+ }
+
+ static bool IsValidKey(Key k) {
+ return hash(k) == (HashNumbers(key(k), gen(k)) & 0xff);
+ }
+
+ static Key RandomTarget(Random* rnd) {
+ switch (rnd->Next() % 10) {
+ case 0:
+ // Seek to beginning
+ return MakeKey(0, 0);
+ case 1:
+ // Seek to end
+ return MakeKey(K, 0);
+ default:
+ // Seek to middle
+ return MakeKey(rnd->Next() % K, 0);
+ }
+ }
+
+ // Per-key generation
+ struct State {
+ std::atomic<int> generation[K];
+ void Set(int k, int v) {
+ generation[k].store(v, std::memory_order_release);
+ }
+ int Get(int k) { return generation[k].load(std::memory_order_acquire); }
+
+ State() {
+ for (int k = 0; k < K; k++) {
+ Set(k, 0);
+ }
+ }
+ };
+
+ // Current state of the test
+ State current_;
+
+ Arena arena_;
+
+ // SkipList is not protected by mu_. We just use a single writer
+ // thread to modify it.
+ SkipList<Key, Comparator> list_;
+
+public:
+ ConcurrentTest() : list_(Comparator(), &arena_) {}
+
+ // REQUIRES: External synchronization
+ void WriteStep(Random* rnd) {
+ const uint32_t k = rnd->Next() % K;
+ const intptr_t g = current_.Get(k) + 1;
+ const Key key = MakeKey(k, g);
+ list_.Insert(key);
+ current_.Set(k, g);
+ }
+
+ void ReadStep(Random* rnd) {
+ // Remember the initial committed state of the skiplist.
+ State initial_state;
+ for (int k = 0; k < K; k++) {
+ initial_state.Set(k, current_.Get(k));
+ }
+
+ Key pos = RandomTarget(rnd);
+ SkipList<Key, Comparator>::Iterator iter(&list_);
+ iter.Seek(pos);
+ while (true) {
+ Key current;
+ if (!iter.Valid()) {
+ current = MakeKey(K, 0);
+ } else {
+ current = iter.key();
+ ASSERT_TRUE(IsValidKey(current)) << current;
+ }
+ ASSERT_LE(pos, current) << "should not go backwards";
+
+ // Verify that everything in [pos,current) was not present in
+ // initial_state.
+ while (pos < current) {
+ ASSERT_LT(key(pos), K) << pos;
+
+ // Note that generation 0 is never inserted, so it is ok if
+ // <*,0,*> is missing.
+ ASSERT_TRUE((gen(pos) == 0) ||
+ (gen(pos) > static_cast<Key>(initial_state.Get(key(pos)))))
+ << "key: " << key(pos) << "; gen: " << gen(pos)
+ << "; initgen: " << initial_state.Get(key(pos));
+
+ // Advance to next key in the valid key space
+ if (key(pos) < key(current)) {
+ pos = MakeKey(key(pos) + 1, 0);
+ } else {
+ pos = MakeKey(key(pos), gen(pos) + 1);
+ }
+ }
+
+ if (!iter.Valid()) {
+ break;
+ }
+
+ if (rnd->Next() % 2) {
+ iter.Next();
+ pos = MakeKey(key(pos), gen(pos) + 1);
+ } else {
+ Key new_target = RandomTarget(rnd);
+ if (new_target > pos) {
+ pos = new_target;
+ iter.Seek(new_target);
+ }
+ }
+ }
+ }
+};
+
+// Needed when building in C++11 mode.
+constexpr uint32_t ConcurrentTest::K;
+
+// Simple test that does single-threaded testing of the ConcurrentTest
+// scaffolding.
+TEST(SkipTest, ConcurrentWithoutThreads) {
+ConcurrentTest test;
+Random rnd(test::RandomSeed());
+for (int i = 0; i < 10000; i++) {
+test.ReadStep(&rnd);
+test.WriteStep(&rnd);
+}
+}
+
+class TestState {
+public:
+ ConcurrentTest t_;
+ int seed_;
+ std::atomic<bool> quit_flag_;
+
+ enum ReaderState { STARTING, RUNNING, DONE };
+
+ explicit TestState(int s)
+ : seed_(s), quit_flag_(false), state_(STARTING), state_cv_(&mu_) {}
+
+ void Wait(ReaderState s) LOCKS_EXCLUDED(mu_) {
+ mu_.Lock();
+ while (state_ != s) {
+ state_cv_.Wait();
+ }
+ mu_.Unlock();
+ }
+
+ void Change(ReaderState s) LOCKS_EXCLUDED(mu_) {
+ mu_.Lock();
+ state_ = s;
+ state_cv_.Signal();
+ mu_.Unlock();
+ }
+
+private:
+ port::Mutex mu_;
+ ReaderState state_ GUARDED_BY(mu_);
+ port::CondVar state_cv_ GUARDED_BY(mu_);
+};
+
+static void ConcurrentReader(void* arg) {
+ TestState* state = reinterpret_cast<TestState*>(arg);
+ Random rnd(state->seed_);
+ int64_t reads = 0;
+ state->Change(TestState::RUNNING);
+ while (!state->quit_flag_.load(std::memory_order_acquire)) {
+ state->t_.ReadStep(&rnd);
+ ++reads;
+ }
+ state->Change(TestState::DONE);
+}
+
+static void RunConcurrent(int run) {
+ const int seed = test::RandomSeed() + (run * 100);
+ Random rnd(seed);
+ const int N = 1000;
+ const int kSize = 1000;
+ for (int i = 0; i < N; i++) {
+ if ((i % 100) == 0) {
+ std::fprintf(stderr, "Run %d of %d\n", i, N);
+ }
+ TestState state(seed + 1);
+ Env::Default()->Schedule(ConcurrentReader, &state);
+ state.Wait(TestState::RUNNING);
+ for (int i = 0; i < kSize; i++) {
+ state.t_.WriteStep(&rnd);
+ }
+ state.quit_flag_.store(true, std::memory_order_release);
+ state.Wait(TestState::DONE);
+ }
+}
+
+TEST(SkipTest, Concurrent1) { RunConcurrent(1); }
+TEST(SkipTest, Concurrent2) { RunConcurrent(2); }
+TEST(SkipTest, Concurrent3) { RunConcurrent(3); }
+TEST(SkipTest, Concurrent4) { RunConcurrent(4); }
+TEST(SkipTest, Concurrent5) { RunConcurrent(5); }
+
+} // namespace leveldb
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/thirdparty/googletest b/thirdparty/googletest
new file mode 160000
+Subproject bf0701daa9f5b30e5882e2f8f9a5280bcba87e7
diff --git a/headers/random.h b/utils/random.h
index 2b04757..2b04757 100644
--- a/headers/random.h
+++ b/utils/random.h
diff --git a/utils/testutil.cpp b/utils/testutil.cpp
new file mode 100644
index 0000000..1d3e04c
--- /dev/null
+++ b/utils/testutil.cpp
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file. See the AUTHORS file for names of contributors.
+
+#include "util/testutil.h"
+
+#include <string>
+
+#include "util/random.h"
+
+namespace cleveldb {
+ namespace test {
+
+ Slice RandomString(Random* rnd, int len, std::string* dst) {
+ dst->resize(len);
+ for (int i = 0; i < len; i++) {
+ (*dst)[i] = static_cast<char>(' ' + rnd->Uniform(95)); // ' ' .. '~'
+ }
+ return Slice(*dst);
+ }
+
+ std::string RandomKey(Random* rnd, int len) {
+ // Make sure to generate a wide variety of characters so we
+ // test the boundary conditions for short-key optimizations.
+ static const char kTestChars[] = {'\0', '\1', 'a', 'b', 'c',
+ 'd', 'e', '\xfd', '\xfe', '\xff'};
+ std::string result;
+ for (int i = 0; i < len; i++) {
+ result += kTestChars[rnd->Uniform(sizeof(kTestChars))];
+ }
+ return result;
+ }
+
+ Slice CompressibleString(Random* rnd, double compressed_fraction, size_t len,
+ std::string* dst) {
+ int raw = static_cast<int>(len * compressed_fraction);
+ if (raw < 1) raw = 1;
+ std::string raw_data;
+ RandomString(rnd, raw, &raw_data);
+
+ // Duplicate the random data until we have filled "len" bytes
+ dst->clear();
+ while (dst->size() < len) {
+ dst->append(raw_data);
+ }
+ dst->resize(len);
+ return Slice(*dst);
+ }
+
+ } // namespace test
+} // namespace leveldb
diff --git a/utils/testutil.cuh b/utils/testutil.cuh
new file mode 100644
index 0000000..f33e691
--- /dev/null
+++ b/utils/testutil.cuh
@@ -0,0 +1,88 @@
+#ifndef CLEVELDB_TESTUTIL_CUH
+
+// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file. See the AUTHORS file for names of contributors.
+
+#ifndef STORAGE_LEVELDB_UTIL_TESTUTIL_H_
+#define STORAGE_LEVELDB_UTIL_TESTUTIL_H_
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "helpers/memenv/memenv.h"
+#include "leveldb/env.h"
+#include "leveldb/slice.h"
+#include "util/random.h"
+
+namespace cleveldb {
+ namespace test {
+
+ MATCHER(IsOK, "") { return arg.ok(); }
+
+// Macros for testing the results of functions that return leveldb::Status or
+// absl::StatusOr<T> (for any type T).
+#define EXPECT_LEVELDB_OK(expression) \
+ EXPECT_THAT(expression, leveldb::test::IsOK())
+#define ASSERT_LEVELDB_OK(expression) \
+ ASSERT_THAT(expression, leveldb::test::IsOK())
+
+// Returns the random seed used at the start of the current test run.
+ inline int RandomSeed() {
+ return testing::UnitTest::GetInstance()->random_seed();
+ }
+
+// Store in *dst a random string of length "len" and return a Slice that
+// references the generated data.
+ Slice RandomString(Random* rnd, int len, std::string* dst);
+
+// Return a random key with the specified length that may contain interesting
+// characters (e.g. \x00, \xff, etc.).
+ std::string RandomKey(Random* rnd, int len);
+
+// Store in *dst a string of length "len" that will compress to
+// "N*compressed_fraction" bytes and return a Slice that references
+// the generated data.
+ Slice CompressibleString(Random* rnd, double compressed_fraction, size_t len,
+ std::string* dst);
+
+// A wrapper that allows injection of errors.
+ class ErrorEnv : public EnvWrapper {
+ public:
+ bool writable_file_error_;
+ int num_writable_file_errors_;
+
+ ErrorEnv()
+ : EnvWrapper(NewMemEnv(Env::Default())),
+ writable_file_error_(false),
+ num_writable_file_errors_(0) {}
+ ~ErrorEnv() override { delete target(); }
+
+ Status NewWritableFile(const std::string& fname,
+ WritableFile** result) override {
+ if (writable_file_error_) {
+ ++num_writable_file_errors_;
+ *result = nullptr;
+ return Status::IOError(fname, "fake error");
+ }
+ return target()->NewWritableFile(fname, result);
+ }
+
+ Status NewAppendableFile(const std::string& fname,
+ WritableFile** result) override {
+ if (writable_file_error_) {
+ ++num_writable_file_errors_;
+ *result = nullptr;
+ return Status::IOError(fname, "fake error");
+ }
+ return target()->NewAppendableFile(fname, result);
+ }
+ };
+
+} // namespace test
+} // namespace leveldb
+
+#endif // STORAGE_LEVELDB_UTIL_TESTUTIL_H_
+
+#define CLEVELDB_TESTUTIL_CUH
+
+#endif //CLEVELDB_TESTUTIL_CUH