xrpld
Loading...
Searching...
No Matches
NuDBFactory_test.cpp
1#include <test/nodestore/TestBase.h>
2#include <test/unit_test/SuiteJournal.h>
3
4#include <xrpl/basics/ByteUtilities.h>
5#include <xrpl/basics/Number.h>
6#include <xrpl/beast/unit_test/suite.h>
7#include <xrpl/beast/utility/Journal.h>
8#include <xrpl/beast/utility/temp_dir.h>
9#include <xrpl/config/BasicConfig.h>
10#include <xrpl/config/Constants.h>
11#include <xrpl/nodestore/DummyScheduler.h>
12#include <xrpl/nodestore/Manager.h>
13#include <xrpl/nodestore/Types.h>
14
15#include <cstddef>
16#include <exception>
17#include <memory>
18#include <sstream>
19#include <string>
20#include <utility>
21#include <vector>
22
23namespace xrpl::NodeStore {
24
26{
27private:
28 // Helper function to create a Section with specified parameters
29 static Section
30 createSection(std::string const& path, std::string const& blockSize = "")
31 {
32 Section params;
33 params.set(Keys::kType, "nudb");
34 params.set(Keys::kPath, path);
35 if (!blockSize.empty())
36 params.set(Keys::kNudbBlockSize, blockSize);
37 return params;
38 }
39
40 // Helper function to create a backend and test basic functionality
41 bool
42 testBackendFunctionality(Section const& params, std::size_t expectedBlocksize)
43 {
44 try
45 {
46 DummyScheduler scheduler;
47 test::SuiteJournal journal("NuDBFactory_test", *this);
48
49 auto backend =
50 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
51
52 if (!BEAST_EXPECT(backend))
53 return false;
54
55 if (!BEAST_EXPECT(backend->getBlockSize() == expectedBlocksize))
56 return false;
57
58 backend->open();
59
60 if (!BEAST_EXPECT(backend->isOpen()))
61 return false;
62
63 // Test basic store/fetch functionality
64 auto batch = createPredictableBatch(10, 12345);
65 storeBatch(*backend, batch);
66
67 Batch copy;
68 fetchCopyOfBatch(*backend, &copy, batch);
69
70 backend->close();
71
72 return areBatchesEqual(batch, copy);
73 }
74 catch (...)
75 {
76 return false;
77 }
78 }
79
80 // Helper function to test log messages
81 void
82 testLogMessage(Section const& params, beast::Severity level, std::string const& expectedMessage)
83 {
84 test::StreamSink sink(level);
85 beast::Journal const journal(sink);
86
87 DummyScheduler scheduler;
88 auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
89
90 std::string const logOutput = sink.messages().str();
91 BEAST_EXPECT(logOutput.contains(expectedMessage));
92 }
93
94 // Helper function to test power of two validation
95 void
96 testPowerOfTwoValidation(std::string const& size, bool shouldWork)
97 {
98 beast::TempDir const tempDir;
99 auto params = createSection(tempDir.path(), size);
100
102 beast::Journal const journal(sink);
103
104 DummyScheduler scheduler;
105 auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
106
107 std::string const logOutput = sink.messages().str();
108 bool const hasWarning = logOutput.contains("Invalid nudb_block_size");
109
110 BEAST_EXPECT(hasWarning == !shouldWork);
111 }
112
113public:
114 void
116 {
117 testcase("Default block size (no nudb_block_size specified)");
118
119 beast::TempDir const tempDir;
120 auto params = createSection(tempDir.path());
121
122 // Should work with default 4096 block size
123 BEAST_EXPECT(testBackendFunctionality(params, 4096));
124 }
125
126 void
128 {
129 testcase("Valid block sizes");
130
131 std::vector<std::size_t> const validSizes = {4096, 8192, 16384, 32768};
132
133 for (auto const& size : validSizes)
134 {
135 beast::TempDir const tempDir;
136 auto params = createSection(tempDir.path(), to_string(size));
137
138 BEAST_EXPECT(testBackendFunctionality(params, size));
139 }
140 // Empty value is ignored by the config parser, so uses the
141 // default
142 beast::TempDir const tempDir;
143 auto params = createSection(tempDir.path(), "");
144
145 BEAST_EXPECT(testBackendFunctionality(params, 4096));
146 }
147
148 void
150 {
151 testcase("Invalid block sizes");
152
153 std::vector<std::string> const invalidSizes = {
154 "2048", // Too small
155 "1024", // Too small
156 "65536", // Too large
157 "131072", // Too large
158 "5000", // Not power of 2
159 "6000", // Not power of 2
160 "10000", // Not power of 2
161 "0", // Zero
162 "-1", // Negative
163 "abc", // Non-numeric
164 "4k", // Invalid format
165 "4096.5" // Decimal
166 };
167
168 for (auto const& size : invalidSizes)
169 {
170 beast::TempDir const tempDir;
171 auto params = createSection(tempDir.path(), size);
172
173 // Fails
174 BEAST_EXPECT(!testBackendFunctionality(params, 4096));
175 }
176
177 // Test whitespace cases separately since lexical_cast may handle them
178 std::vector<std::string> const whitespaceInvalidSizes = {
179 "4096 ", // Trailing space - might be handled by lexical_cast
180 " 4096" // Leading space - might be handled by lexical_cast
181 };
182
183 for (auto const& size : whitespaceInvalidSizes)
184 {
185 beast::TempDir const tempDir;
186 auto params = createSection(tempDir.path(), size);
187
188 // Fails
189 BEAST_EXPECT(!testBackendFunctionality(params, 4096));
190 }
191 }
192
193 void
195 {
196 testcase("Log message verification");
197
198 // Test valid custom block size logging
199 {
200 beast::TempDir const tempDir;
201 auto params = createSection(tempDir.path(), "8192");
202
203 testLogMessage(params, beast::Severity::Info, "Using custom NuDB block size: 8192");
204 }
205
206 // Test invalid block size failure
207 {
208 beast::TempDir const tempDir;
209 auto params = createSection(tempDir.path(), "5000");
210
212 beast::Journal const journal(sink);
213
214 DummyScheduler scheduler;
215 try
216 {
217 auto backend =
218 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
219 fail();
220 }
221 catch (std::exception const& e)
222 {
223 std::string const logOutput{e.what()};
224 BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size: 5000"));
225 BEAST_EXPECT(logOutput.contains("Must be power of 2 between 4096 and 32768"));
226 }
227 }
228
229 // Test non-numeric value failure
230 {
231 beast::TempDir const tempDir;
232 auto params = createSection(tempDir.path(), "invalid");
233
235 beast::Journal const journal(sink);
236
237 DummyScheduler scheduler;
238 try
239 {
240 auto backend =
241 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
242
243 fail();
244 }
245 catch (std::exception const& e)
246 {
247 std::string const logOutput{e.what()};
248 BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size value: invalid"));
249 }
250 }
251 }
252
253 void
255 {
256 testcase("Power of 2 validation logic");
257
258 // Test edge cases around valid range
260 {"4095", false}, // Just below minimum
261 {"4096", true}, // Minimum valid
262 {"4097", false}, // Just above minimum, not power of 2
263 {"8192", true}, // Valid power of 2
264 {"8193", false}, // Just above valid power of 2
265 {"16384", true}, // Valid power of 2
266 {"32768", true}, // Maximum valid
267 {"32769", false}, // Just above maximum
268 {"65536", false} // Power of 2 but too large
269 };
270
271 for (auto const& [size, shouldWork] : testCases)
272 {
273 beast::TempDir const tempDir;
274 auto params = createSection(tempDir.path(), size);
275
276 // We test the validation logic by catching exceptions for invalid
277 // values
279 beast::Journal const journal(sink);
280
281 DummyScheduler scheduler;
282 try
283 {
284 auto backend =
285 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
286 BEAST_EXPECT(shouldWork);
287 }
288 catch (std::exception const& e)
289 {
290 std::string const logOutput{e.what()};
291 BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size"));
292 }
293 }
294 }
295
296 void
298 {
299 testcase("Both constructor variants work with custom block size");
300
301 beast::TempDir const tempDir;
302 auto params = createSection(tempDir.path(), "16384");
303
304 DummyScheduler scheduler;
305 test::SuiteJournal journal("NuDBFactory_test", *this);
306
307 // Test first constructor (without nudb::context)
308 {
309 auto backend1 =
310 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
311 BEAST_EXPECT(backend1 != nullptr);
312 BEAST_EXPECT(testBackendFunctionality(params, 16384));
313 }
314
315 // Test second constructor (with nudb::context)
316 // Note: This would require access to nudb::context, which might not be
317 // easily testable without more complex setup. For now, we test that
318 // the factory can create backends with the first constructor.
319 }
320
321 void
323 {
324 testcase("Configuration parsing edge cases");
325
326 // Test that whitespace is handled correctly
327 std::vector<std::string> const validFormats = {
328 "8192" // Basic valid format
329 };
330
331 // Test whitespace handling separately since lexical_cast behavior may
332 // vary
333 std::vector<std::string> const whitespaceFormats = {
334 " 8192", // Leading space - may or may not be handled by
335 // lexical_cast
336 "8192 " // Trailing space - may or may not be handled by
337 // lexical_cast
338 };
339
340 // Test basic valid format
341 for (auto const& format : validFormats)
342 {
343 beast::TempDir const tempDir;
344 auto params = createSection(tempDir.path(), format);
345
347 beast::Journal const journal(sink);
348
349 DummyScheduler scheduler;
350 auto backend =
351 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
352
353 // Should log success message for valid values
354 std::string const logOutput = sink.messages().str();
355 bool const hasSuccessMessage = logOutput.contains("Using custom NuDB block size");
356 BEAST_EXPECT(hasSuccessMessage);
357 }
358
359 // Test whitespace formats - these should work if lexical_cast handles
360 // them
361 for (auto const& format : whitespaceFormats)
362 {
363 beast::TempDir const tempDir;
364 auto params = createSection(tempDir.path(), format);
365
366 // Use a lower threshold to capture both info and warning messages
368 beast::Journal const journal(sink);
369
370 DummyScheduler scheduler;
371 try
372 {
373 auto backend =
374 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
375 fail();
376 }
377 catch (...)
378 {
379 // Fails
380 BEAST_EXPECT(!testBackendFunctionality(params, 8192));
381 }
382 }
383 }
384
385 void
387 {
388 testcase("Data persistence with different block sizes");
389
390 std::vector<std::string> const blockSizes = {"4096", "8192", "16384", "32768"};
391
392 for (auto const& size : blockSizes)
393 {
394 beast::TempDir const tempDir;
395 auto params = createSection(tempDir.path(), size);
396
397 DummyScheduler scheduler;
398 test::SuiteJournal journal("NuDBFactory_test", *this);
399
400 // Create test data
401 auto batch = createPredictableBatch(50, 54321);
402
403 // Store data
404 {
405 auto backend =
406 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
407 backend->open();
408 storeBatch(*backend, batch);
409 backend->close();
410 }
411
412 // Retrieve data in new backend instance
413 {
414 auto backend =
415 Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
416 backend->open();
417
418 Batch copy;
419 fetchCopyOfBatch(*backend, &copy, batch);
420
421 BEAST_EXPECT(areBatchesEqual(batch, copy));
422 backend->close();
423 }
424 }
425 }
426
427 void
439};
440
442
443} // namespace xrpl::NodeStore
A generic endpoint for log messages.
Definition Journal.h:38
RAII temporary directory.
Definition temp_dir.h:15
std::string path() const
Get the native path for the temporary directory.
Definition temp_dir.h:47
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
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.
bool testBackendFunctionality(Section const &params, std::size_t expectedBlocksize)
static Section createSection(std::string const &path, std::string const &blockSize="")
void run() override
Runs the suite.
void testPowerOfTwoValidation(std::string const &size, bool shouldWork)
void testLogMessage(Section const &params, beast::Severity level, std::string const &expectedMessage)
static bool areBatchesEqual(Batch const &lhs, Batch const &rhs)
Definition TestBase.h:95
void fetchCopyOfBatch(Backend &backend, Batch *pCopy, Batch const &batch)
Definition TestBase.h:130
static void storeBatch(Backend &backend, Batch const &batch)
Definition TestBase.h:120
static Batch createPredictableBatch(int numObjects, std::uint64_t seed)
Definition TestBase.h:57
Holds a collection of configuration values.
Definition BasicConfig.h:24
void set(std::string const &key, std::string const &value)
Set a key/value pair.
std::stringstream const & messages() const
Severity
Severity level / threshold of a Journal message.
Definition Journal.h:11
BEAST_DEFINE_TESTSUITE(Backend, nodestore, xrpl)
std::vector< std::shared_ptr< NodeObject > > Batch
A batch of NodeObjects to write at once.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
constexpr auto megabytes(T value) noexcept
T str(T... args)
static constexpr auto kType
Definition Constants.h:170
static constexpr auto kPath
Definition Constants.h:140
static constexpr auto kNudbBlockSize
Definition Constants.h:132
T what(T... args)