xrpld
Loading...
Searching...
No Matches
tests/libxrpl/nodestore/Backend.cpp
1#include <xrpl/nodestore/Backend.h>
2
3#include <xrpl/basics/ByteUtilities.h>
4#include <xrpl/basics/FileUtilities.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/xor_shift_engine.h>
7#include <xrpl/config/BasicConfig.h>
8#include <xrpl/nodestore/DummyScheduler.h>
9#include <xrpl/nodestore/Manager.h>
10#include <xrpl/nodestore/NodeObject.h>
11#include <xrpl/nodestore/Types.h>
12
13#include <gtest/gtest.h>
14#include <helpers/TestSink.h>
15#include <nodestore/TestBase.h>
16
17#include <algorithm>
18#include <atomic>
19#include <cstddef>
20#include <memory>
21#include <ranges>
22#include <string>
23#include <thread>
24#include <vector>
25
26namespace xrpl::node_store {
27
28namespace {
29
30std::vector<std::string>
31backendTypes()
32{
33 std::vector<std::string> types{"nudb"};
34#if XRPL_ROCKSDB_AVAILABLE
35 types.emplace_back("rocksdb");
36#endif
37#ifdef XRPL_ENABLE_SQLITE_BACKEND_TESTS
38 types.emplace_back("sqlite");
39#endif
40 return types;
41}
42
43// Run work(i) for every i in [0, n) spread across numThreads threads, handing
44// out indices via a shared atomic counter (mirrors the old Timing_test
45// parallel-for so the N items are partitioned, not duplicated).
46template <class Work>
47void
48parallelFor(std::size_t n, std::size_t numThreads, Work work)
49{
50 std::atomic<std::size_t> next{0};
51 auto const runner = [&] {
52 for (std::size_t i = next++; i < n; i = next++)
53 work(i);
54 };
55
56 auto threads = std::views::iota(std::size_t{0}, numThreads) |
57 std::views::transform([&](std::size_t) { return std::thread{runner}; }) |
58 std::ranges::to<std::vector>();
59
61}
62
63} // namespace
64
65class BackendTypeTest : public ::testing::TestWithParam<std::string>
66{
67protected:
68 void
69 SetUp() override
70 {
71 params_.set("type", GetParam());
72 params_.set("path", tempDir_.path());
73
76 }
77
80 {
82 backend->open();
83 return backend;
84 }
85
91};
92
93TEST_P(BackendTypeTest, store_and_fetch)
94{
95 auto backend = makeOpenBackend();
96 storeBatch(*backend, batch_);
97
98 {
99 SCOPED_TRACE("read in original order");
100 auto const copy = fetchCopyOfBatch(*backend, batch_);
101 EXPECT_EQ(batch_, copy);
102 }
103
104 {
105 SCOPED_TRACE("read in shuffled order");
107 std::shuffle(batch_.begin(), batch_.end(), rng);
108 auto const copy = fetchCopyOfBatch(*backend, batch_);
109 EXPECT_EQ(batch_, copy);
110 }
111}
112
113TEST_P(BackendTypeTest, persists_after_reopen)
114{
115 {
116 auto backend = makeOpenBackend();
117 storeBatch(*backend, batch_);
118 }
119
120 // re-open a fresh backend instance over the same path
121 auto backend = makeOpenBackend();
122 auto copy = fetchCopyOfBatch(*backend, batch_);
123 std::ranges::sort(batch_, LessThan{});
125 EXPECT_EQ(batch_, copy);
126}
127
128// missing-key path. Replaces the correctness half of Timing_test::doMissing
129// (and the missing branch of doMixed): every fetch on an empty backend must
130// report Status::NotFound.
131TEST_P(BackendTypeTest, fetch_missing)
132{
133 auto backend = makeOpenBackend();
134 // deliberately do NOT store batch_ — every key must be absent
135 fetchMissing(*backend, batch_);
136}
137
138// concurrent store/fetch correctness. Replaces the correctness half of the
139// multi-threaded Timing_test workloads (which only ran manually, never in CI):
140// many threads store disjoint objects, then many threads fetch and verify each
141// round-trips. Doubles as a thread-safety smoke test for the backend.
142TEST_P(BackendTypeTest, concurrent_store_and_fetch)
143{
144 // The SQLite backend is not designed for concurrent writers (and the old
145 // Timing_test only exercised nudb/rocksdb under threads).
146 if (GetParam() == "sqlite")
147 GTEST_SKIP() << "sqlite backend is not exercised under concurrency";
148
149 for (auto const numThreads : {4uz, 8uz})
150 {
151 SCOPED_TRACE("threads=" + std::to_string(numThreads));
152
153 auto backend = makeOpenBackend();
154
155 // concurrent stores of disjoint objects
156 parallelFor(batch_.size(), numThreads, [&](std::size_t i) { backend->store(batch_[i]); });
157
158 // concurrent fetches, each verifying its object round-trips. Worker
159 // threads only touch an atomic counter; the EXPECT runs on the main
160 // thread after join to avoid relying on cross-thread assertion support.
161 std::atomic<std::size_t> mismatches{0};
162 parallelFor(batch_.size(), numThreads, [&](std::size_t i) {
163 std::shared_ptr<NodeObject> result;
164 if (backend->fetch(batch_[i]->getHash(), &result) != Status::Ok || !result ||
165 !isSame(result, batch_[i]))
166 {
167 ++mismatches;
168 }
169 });
170 EXPECT_EQ(mismatches.load(), 0u);
171
172 backend->close();
173 }
174}
175
177 BackendTypes,
179 ::testing::ValuesIn(backendTypes()),
180 [](::testing::TestParamInfo<std::string> const& info) { return info.param; });
181
182} // namespace xrpl::node_store
A generic endpoint for log messages.
Definition Journal.h:44
Holds a collection of configuration values.
Definition BasicConfig.h:29
RAII temporary directory.
static TestSink & instance()
Definition TestSink.h:12
Simple NodeStore Scheduler that just performs the tasks synchronously.
virtual std::unique_ptr< Backend > makeBackend(Section const &parameters, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)=0
Create a backend.
static Manager & instance()
Returns the instance of the manager singleton.
T emplace_back(T... args)
T for_each(T... args)
T join(T... args)
detail::XorShiftEngine<> xor_shift_engine
XOR-shift Generator.
constexpr std::uint64_t kSeedValue
Definition TestBase.h:28
TEST_P(BackendTypeTest, store_and_fetch)
INSTANTIATE_TEST_SUITE_P(BackendTypes, BackendTypeTest, ::testing::ValuesIn(backendTypes()), [](::testing::TestParamInfo< std::string > const &info) { return info.param;})
std::vector< std::shared_ptr< NodeObject > > Batch
A batch of NodeObjects to write at once.
Batch createPredictableBatch(std::size_t numObjects, std::uint64_t seed)
Definition TestBase.h:48
constexpr int kNumObjects
Definition TestBase.h:27
void storeBatch(Backend &backend, Batch const &batch)
Definition TestBase.h:85
void fetchMissing(Backend &backend, Batch const &batch)
Definition TestBase.h:113
Batch fetchCopyOfBatch(Backend &backend, Batch const &batch)
Definition TestBase.h:92
constexpr auto megabytes(T value) noexcept
T next(T... args)
T shuffle(T... args)
T sort(T... args)
T to_string(T... args)