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