From 52c83684cda4d66a91097ae77ab8c863e06d5d87 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:35:21 +0000 Subject: [PATCH] unit tests + refactore --- src/libxrpl/basics/MallocTrim.cpp | 23 ++- src/tests/libxrpl/basics/MallocTrim.cpp | 207 ++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 src/tests/libxrpl/basics/MallocTrim.cpp diff --git a/src/libxrpl/basics/MallocTrim.cpp b/src/libxrpl/basics/MallocTrim.cpp index aee036863f..2c154503c2 100644 --- a/src/libxrpl/basics/MallocTrim.cpp +++ b/src/libxrpl/basics/MallocTrim.cpp @@ -21,16 +21,6 @@ namespace detail { #if defined(__GLIBC__) && BOOST_OS_LINUX -std::string -readFile(std::string const& path) -{ - std::ifstream ifs(path); - if (!ifs.is_open()) - return {}; - return std::string( - std::istreambuf_iterator(ifs), std::istreambuf_iterator()); -} - long parseVmRSSkB(std::string const& status) { @@ -68,16 +58,25 @@ mallocTrim( if (journal.debug()) { + auto readFile = [](std::string const& path) -> std::string { + std::ifstream ifs(path); + if (!ifs.is_open()) + return {}; + return std::string( + std::istreambuf_iterator(ifs), + std::istreambuf_iterator()); + }; + std::string const tagStr = tag.value_or("default"); std::string const statusPath = "/proc/" + std::to_string(cachedPid) + "/status"; - auto const statusBefore = detail::readFile(statusPath); + auto const statusBefore = readFile(statusPath); report.rssBeforeKB = detail::parseVmRSSkB(statusBefore); report.trimResult = ::malloc_trim(0); - auto const statusAfter = detail::readFile(statusPath); + auto const statusAfter = readFile(statusPath); report.rssAfterKB = detail::parseVmRSSkB(statusAfter); JLOG(journal.debug()) diff --git a/src/tests/libxrpl/basics/MallocTrim.cpp b/src/tests/libxrpl/basics/MallocTrim.cpp new file mode 100644 index 0000000000..dd691180d2 --- /dev/null +++ b/src/tests/libxrpl/basics/MallocTrim.cpp @@ -0,0 +1,207 @@ +#include + +#include + +#include + +using namespace ripple; + +#if defined(__GLIBC__) && BOOST_OS_LINUX +namespace ripple::detail { +long +parseVmRSSkB(std::string const& status); +} // namespace ripple::detail +#endif + +TEST_CASE("MallocTrimReport structure") +{ + // Test default construction + MallocTrimReport report; + CHECK(report.supported == false); + CHECK(report.trimResult == -1); + CHECK(report.rssBeforeKB == -1); + CHECK(report.rssAfterKB == -1); + CHECK(report.deltaKB() == 0); + + // Test deltaKB calculation - memory freed + report.rssBeforeKB = 1000; + report.rssAfterKB = 800; + CHECK(report.deltaKB() == -200); + + // Test deltaKB calculation - memory increased + report.rssBeforeKB = 500; + report.rssAfterKB = 600; + CHECK(report.deltaKB() == 100); + + // Test deltaKB calculation - no change + report.rssBeforeKB = 1234; + report.rssAfterKB = 1234; + CHECK(report.deltaKB() == 0); +} + +#if defined(__GLIBC__) && BOOST_OS_LINUX +TEST_CASE("parseVmRSSkB") +{ + using ripple::detail::parseVmRSSkB; + + // Test standard format + { + std::string status = "VmRSS: 123456 kB\n"; + long result = parseVmRSSkB(status); + CHECK(result == 123456); + } + + // Test with multiple lines + { + std::string status = + "Name: rippled\n" + "VmPeak: 1234567 kB\n" + "VmSize: 1234567 kB\n" + "VmRSS: 987654 kB\n" + "VmData: 123456 kB\n"; + long result = parseVmRSSkB(status); + CHECK(result == 987654); + } + + // Test with minimal whitespace + { + std::string status = "VmRSS: 42 kB"; + long result = parseVmRSSkB(status); + CHECK(result == 42); + } + + // Test with extra whitespace + { + std::string status = "VmRSS: 999999 kB"; + long result = parseVmRSSkB(status); + CHECK(result == 999999); + } + + // Test with tabs + { + std::string status = "VmRSS:\t\t12345 kB"; + long result = parseVmRSSkB(status); + // Note: tabs are not explicitly handled as spaces, this documents + // current behavior + CHECK(result == 12345); + } + + // Test zero value + { + std::string status = "VmRSS: 0 kB\n"; + long result = parseVmRSSkB(status); + CHECK(result == 0); + } + + // Test missing VmRSS + { + std::string status = + "Name: rippled\n" + "VmPeak: 1234567 kB\n" + "VmSize: 1234567 kB\n"; + long result = parseVmRSSkB(status); + CHECK(result == -1); + } + + // Test empty string + { + std::string status = ""; + long result = parseVmRSSkB(status); + CHECK(result == -1); + } + + // Test malformed data (VmRSS but no number) + { + std::string status = "VmRSS: \n"; + long result = parseVmRSSkB(status); + // sscanf should fail to parse and return -1 unchanged + CHECK(result == -1); + } + + // Test malformed data (VmRSS but invalid number) + { + std::string status = "VmRSS: abc kB\n"; + long result = parseVmRSSkB(status); + // sscanf should fail and return -1 unchanged + CHECK(result == -1); + } + + // Test partial match (should not match "NotVmRSS:") + { + std::string status = "NotVmRSS: 123456 kB\n"; + long result = parseVmRSSkB(status); + CHECK(result == -1); + } +} +#endif + +TEST_CASE("mallocTrim basic functionality") +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + // Test with no tag + { + MallocTrimReport report = mallocTrim(std::nullopt, journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + // On Linux with glibc, should be supported + CHECK(report.supported == true); + // trimResult should be 0 or 1 (success indicators) + CHECK(report.trimResult >= 0); +#else + // On other platforms, should be unsupported + CHECK(report.supported == false); + CHECK(report.trimResult == -1); + CHECK(report.rssBeforeKB == -1); + CHECK(report.rssAfterKB == -1); +#endif + } + + // Test with tag + { + MallocTrimReport report = + mallocTrim(std::optional("test_tag"), journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + CHECK(report.supported == true); + CHECK(report.trimResult >= 0); +#else + CHECK(report.supported == false); +#endif + } +} + +TEST_CASE("mallocTrim with debug logging") +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + MallocTrimReport report = + mallocTrim(std::optional("debug_test"), journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + CHECK(report.supported == true); + // The function should complete without crashing +#else + CHECK(report.supported == false); +#endif +} + +TEST_CASE("mallocTrim repeated calls") +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + // Call malloc_trim multiple times to ensure it's safe + for (int i = 0; i < 5; ++i) + { + MallocTrimReport report = mallocTrim( + std::optional("iteration_" + std::to_string(i)), + journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + CHECK(report.supported == true); + CHECK(report.trimResult >= 0); +#else + CHECK(report.supported == false); +#endif + } +}