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