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