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