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