Merge branch 'pratik/otel-phase8-log-correlation' into pratik/otel-phase9-metric-gap-fill

Resolves src/tests/libxrpl/CMakeLists.txt test_modules by keeping both
sides: develop's beast/nodestore/protocol additions (nodestore moved into
alphabetical position) and this branch's ledger module.
This commit is contained in:
Pratik Mankawde
2026-07-29 14:22:21 +01:00
22 changed files with 725 additions and 544 deletions

View File

@@ -75,6 +75,8 @@ Checks: "-*,
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
# ---
FormatStyle: file
CheckOptions:
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc

View File

@@ -184,7 +184,6 @@ words:
- mcmodel
- MEMORYSTATUSEX
- Merkle
- Metafuncton
- misprediction
- missingok
- MPTAMM

View File

@@ -266,7 +266,7 @@ jobs:
./xrpld --definitions | python3 -m json.tool >server_definitions.json
- name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'ubuntu-gcc-debug-amd64-coverage' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server-definitions

View File

@@ -95,7 +95,7 @@ jobs:
TARGETS: ${{ needs.determine-files.outputs.need_full_run != 'true' && needs.determine-files.outputs.cpp_changed_files || 'include src tests' }}
run: |
set -o pipefail
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -format -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
- name: Print filtered clang-tidy errors
if: ${{ steps.run_clang_tidy.outcome != 'success' }}

View File

@@ -348,12 +348,14 @@ run-clang-tidy -p build -allow-no-checks src tests
```
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix` to the above command:
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
```
run-clang-tidy -p build -quiet -fix -allow-no-checks src tests
run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
```
`-format` reformats the fixed code with [`.clang-format`](./.clang-format); without it the fixes are inserted in LLVM style and the `clang-format` hook rewrites them afterwards.
## Telemetry span attribute naming
OpenTelemetry span attribute keys follow these rules so they stay consistent

View File

@@ -144,7 +144,11 @@ def main():
+ files
)
canonicalize_fix_paths(Path(fixes_dir))
applied = subprocess.run([clang_apply_replacements, fixes_dir])
# `FormatStyle` in .clang-tidy does not reach this path,
# so ask for the repository style here.
applied = subprocess.run(
[clang_apply_replacements, "--format", "--style=file", fixes_dir]
)
return result.returncode or applied.returncode

View File

@@ -17,14 +17,14 @@ namespace beast {
class SemanticVersion
{
public:
using identifier_list = std::vector<std::string>;
using IdentifierList = std::vector<std::string>;
int majorVersion;
int minorVersion;
int patchVersion;
identifier_list preReleaseIdentifiers;
identifier_list metaData;
IdentifierList preReleaseIdentifiers;
IdentifierList metaData;
SemanticVersion();

View File

@@ -13,18 +13,18 @@ namespace xrpl::node_store {
// https://developers.google.com/protocol-buffers/docs/encoding#varints
// field tag
struct varint;
struct Varint;
// Metafuncton to return largest
// Metafunction to return largest
// possible size of T represented as varint.
// T must be unsigned
template <class T, bool = std::is_unsigned_v<T>>
struct varint_traits;
struct VarintTraits;
template <class T>
struct varint_traits<T, true>
struct VarintTraits<T, true>
{
explicit varint_traits() = default;
explicit VarintTraits() = default;
static constexpr std::size_t kMax = ((8 * sizeof(T)) + 6) / 7;
};
@@ -104,7 +104,7 @@ writeVarint(void* p0, std::size_t v)
template <class T>
void
read(nudb::detail::istream& is, std::size_t& u)
requires(std::is_same_v<T, varint>)
requires(std::is_same_v<T, Varint>)
{
auto p0 = is(1);
auto p1 = p0;
@@ -118,7 +118,7 @@ read(nudb::detail::istream& is, std::size_t& u)
template <class T>
void
write(nudb::detail::ostream& os, std::size_t t)
requires(std::is_same_v<T, varint>)
requires(std::is_same_v<T, Varint>)
{
writeVarint(os.data(sizeVarint(t)), t);
}

View File

@@ -10,7 +10,7 @@
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/detail/varint.h>
#include <xrpl/nodestore/detail/Varint.h>
#include <xrpl/protocol/HashPrefix.h>
#include <nudb/detail/field.hpp>
@@ -59,7 +59,7 @@ lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf)
using std::runtime_error;
using namespace nudb::detail;
std::pair<void const*, std::size_t> result;
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> vi{};
auto const n = writeVarint(vi.data(), inSize);
auto const outMax = LZ4_compressBound(inSize);
auto* out = reinterpret_cast<std::uint8_t*>(bf(n + outMax));
@@ -240,7 +240,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
auto* out = reinterpret_cast<std::uint8_t*>(bf(result.second));
result.first = out;
ostream os(out, result.second);
write<varint>(os, type);
write<Varint>(os, type);
write<std::uint16_t>(os, mask);
write(os, vh.data(), n * 32);
return result;
@@ -252,13 +252,13 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
auto* out = reinterpret_cast<std::uint8_t*>(bf(result.second));
result.first = out;
ostream os(out, result.second);
write<varint>(os, type);
write<Varint>(os, type);
write(os, vh.data(), n * 32);
return result;
}
}
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> vi{};
static constexpr std::size_t kCodecType = 1;
auto const vn = writeVarint(vi.data(), kCodecType);

View File

@@ -15,7 +15,7 @@
namespace beast {
std::string
printIdentifiers(SemanticVersion::identifier_list const& list)
printIdentifiers(SemanticVersion::IdentifierList const& list)
{
std::string ret;
@@ -115,7 +115,7 @@ extractIdentifier(std::string& value, bool allowLeadingZeroes, std::string& inpu
bool
extractIdentifiers(
SemanticVersion::identifier_list& identifiers,
SemanticVersion::IdentifierList& identifiers,
bool allowLeadingZeroes,
std::string& input)
{

View File

@@ -1,266 +0,0 @@
#include <xrpl/beast/core/SemanticVersion.h>
#include <xrpl/beast/unit_test/suite.h>
#include <string>
namespace beast {
class SemanticVersion_test : public unit_test::Suite
{
using identifier_list = SemanticVersion::identifier_list;
public:
void
checkPass(std::string const& input, bool shouldPass = true)
{
SemanticVersion v;
if (shouldPass)
{
BEAST_EXPECT(v.parse(input));
BEAST_EXPECT(v.print() == input);
}
else
{
BEAST_EXPECT(!v.parse(input));
}
}
void
checkFail(std::string const& input)
{
checkPass(input, false);
}
// check input and input with appended metadata
void
checkMeta(std::string const& input, bool shouldPass)
{
checkPass(input, shouldPass);
checkPass(input + "+a", shouldPass);
checkPass(input + "+1", shouldPass);
checkPass(input + "+a.b", shouldPass);
checkPass(input + "+ab.cd", shouldPass);
checkFail(input + "!");
checkFail(input + "+");
checkFail(input + "++");
checkFail(input + "+!");
checkFail(input + "+.");
checkFail(input + "+a.!");
}
void
checkMetaFail(std::string const& input)
{
checkMeta(input, false);
}
// check input, input with appended release data,
// input with appended metadata, and input with both
// appended release data and appended metadata
//
void
checkRelease(std::string const& input, bool shouldPass = true)
{
checkMeta(input, shouldPass);
checkMeta(input + "-1", shouldPass);
checkMeta(input + "-a", shouldPass);
checkMeta(input + "-a1", shouldPass);
checkMeta(input + "-a1.b1", shouldPass);
checkMeta(input + "-ab.cd", shouldPass);
checkMeta(input + "--", shouldPass);
checkMetaFail(input + "+");
checkMetaFail(input + "!");
checkMetaFail(input + "-");
checkMetaFail(input + "-!");
checkMetaFail(input + "-.");
checkMetaFail(input + "-a.!");
checkMetaFail(input + "-0.a");
}
// Checks the major.minor.version string alone and with all
// possible combinations of release identifiers and metadata.
//
void
check(std::string const& input, bool shouldPass = true)
{
checkRelease(input, shouldPass);
}
void
negcheck(std::string const& input)
{
check(input, false);
}
void
testParse()
{
testcase("parsing");
check("0.0.0");
check("1.2.3");
check("2147483647.2147483647.2147483647"); // max int
// negative values
negcheck("-1.2.3");
negcheck("1.-2.3");
negcheck("1.2.-3");
// missing parts
negcheck("");
negcheck("1");
negcheck("1.");
negcheck("1.2");
negcheck("1.2.");
negcheck(".2.3");
// whitespace
negcheck(" 1.2.3");
negcheck("1 .2.3");
negcheck("1.2 .3");
negcheck("1.2.3 ");
// leading zeroes
negcheck("01.2.3");
negcheck("1.02.3");
negcheck("1.2.03");
}
static identifier_list
ids()
{
return identifier_list();
}
static identifier_list
ids(std::string const& s1)
{
identifier_list v;
v.push_back(s1);
return v;
}
static identifier_list
ids(std::string const& s1, std::string const& s2)
{
identifier_list v;
v.push_back(s1);
v.push_back(s2);
return v;
}
static identifier_list
ids(std::string const& s1, std::string const& s2, std::string const& s3)
{
identifier_list v;
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
return v;
}
// Checks the decomposition of the input into appropriate values
void
checkValues(
std::string const& input,
int majorVersion,
int minorVersion,
int patchVersion,
identifier_list const& preReleaseIdentifiers = identifier_list(),
identifier_list const& metaData = identifier_list())
{
SemanticVersion v;
BEAST_EXPECT(v.parse(input));
BEAST_EXPECT(v.majorVersion == majorVersion);
BEAST_EXPECT(v.minorVersion == minorVersion);
BEAST_EXPECT(v.patchVersion == patchVersion);
BEAST_EXPECT(v.preReleaseIdentifiers == preReleaseIdentifiers);
BEAST_EXPECT(v.metaData == metaData);
}
void
testValues()
{
testcase("values");
checkValues("0.1.2", 0, 1, 2);
checkValues("1.2.3", 1, 2, 3);
checkValues("1.2.3-rc1", 1, 2, 3, ids("rc1"));
checkValues("1.2.3-rc1.debug", 1, 2, 3, ids("rc1", "debug"));
checkValues("1.2.3-rc1.debug.asm", 1, 2, 3, ids("rc1", "debug", "asm"));
checkValues("1.2.3+full", 1, 2, 3, ids(), ids("full"));
checkValues("1.2.3+full.prod", 1, 2, 3, ids(), ids("full", "prod"));
checkValues("1.2.3+full.prod.x86", 1, 2, 3, ids(), ids("full", "prod", "x86"));
checkValues(
"1.2.3-rc1.debug.asm+full.prod.x86",
1,
2,
3,
ids("rc1", "debug", "asm"),
ids("full", "prod", "x86"));
}
// makes sure the left version is less than the right
void
checkLessInternal(std::string const& lhs, std::string const& rhs)
{
SemanticVersion left;
SemanticVersion right;
BEAST_EXPECT(left.parse(lhs));
BEAST_EXPECT(right.parse(rhs));
BEAST_EXPECT(compare(left, left) == 0);
BEAST_EXPECT(compare(right, right) == 0);
BEAST_EXPECT(compare(left, right) < 0);
BEAST_EXPECT(compare(right, left) > 0);
BEAST_EXPECT(left < right);
BEAST_EXPECT(right > left);
BEAST_EXPECT(left == left);
BEAST_EXPECT(right == right);
}
void
checkLess(std::string const& lhs, std::string const& rhs)
{
checkLessInternal(lhs, rhs);
checkLessInternal(lhs + "+meta", rhs);
checkLessInternal(lhs, rhs + "+meta");
checkLessInternal(lhs + "+meta", rhs + "+meta");
}
void
testCompare()
{
testcase("comparisons");
checkLess("1.0.0-alpha", "1.0.0-alpha.1");
checkLess("1.0.0-alpha.1", "1.0.0-alpha.beta");
checkLess("1.0.0-alpha.beta", "1.0.0-beta");
checkLess("1.0.0-beta", "1.0.0-beta.2");
checkLess("1.0.0-beta.2", "1.0.0-beta.11");
checkLess("1.0.0-beta.11", "1.0.0-rc.1");
checkLess("1.0.0-rc.1", "1.0.0");
checkLess("0.9.9", "1.0.0");
}
void
run() override
{
testParse();
testValues();
testCompare();
}
};
BEAST_DEFINE_TESTSUITE(SemanticVersion, beast, beast);
} // namespace beast

View File

@@ -1,115 +0,0 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Zero.h>
namespace beast {
struct AdlTester
{
};
int
signum(AdlTester)
{
return 0;
}
namespace inner_adl_test {
struct AdlTester2
{
};
int
signum(AdlTester2)
{
return 0;
}
} // namespace inner_adl_test
class Zero_test : public beast::unit_test::Suite
{
private:
struct IntegerWrapper
{
int value;
IntegerWrapper(int v) : value(v)
{
}
[[nodiscard]] int
signum() const
{
return value;
}
};
public:
void
expectSame(bool result, bool correct, char const* message)
{
expect(result == correct, message);
}
void
testLhsZero(IntegerWrapper x)
{
expectSame(x >= kZero, x.signum() >= 0, "lhs greater-than-or-equal-to");
expectSame(x > kZero, x.signum() > 0, "lhs greater than");
expectSame(x == kZero, x.signum() == 0, "lhs equal to");
expectSame(x != kZero, x.signum() != 0, "lhs not equal to");
expectSame(x < kZero, x.signum() < 0, "lhs less than");
expectSame(x <= kZero, x.signum() <= 0, "lhs less-than-or-equal-to");
}
void
testLhsZero()
{
testcase("lhs zero");
testLhsZero(-7);
testLhsZero(0);
testLhsZero(32);
}
void
testRhsZero(IntegerWrapper x)
{
expectSame(kZero >= x, 0 >= x.signum(), "rhs greater-than-or-equal-to");
expectSame(kZero > x, 0 > x.signum(), "rhs greater than");
expectSame(kZero == x, 0 == x.signum(), "rhs equal to");
expectSame(kZero != x, 0 != x.signum(), "rhs not equal to");
expectSame(kZero < x, 0 < x.signum(), "rhs less than");
expectSame(kZero <= x, 0 <= x.signum(), "rhs less-than-or-equal-to");
}
void
testRhsZero()
{
testcase("rhs zero");
testRhsZero(-4);
testRhsZero(0);
testRhsZero(64);
}
void
testAdl()
{
expect(AdlTester{} == kZero, "ADL failure!");
expect(inner_adl_test::AdlTester2{} == kZero, "ADL failure!");
}
void
run() override
{
testLhsZero();
testRhsZero();
testAdl();
}
};
BEAST_DEFINE_TESTSUITE(Zero, beast, beast);
} // namespace beast

View File

@@ -1,41 +0,0 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/ApiVersion.h>
namespace xrpl::test {
struct ApiVersion_test : beast::unit_test::Suite
{
void
run() override
{
{
testcase("API versions invariants");
static_assert(RPC::kApiMinimumSupportedVersion <= RPC::kApiMaximumSupportedVersion);
static_assert(RPC::kApiMinimumSupportedVersion <= RPC::kApiMaximumValidVersion);
static_assert(RPC::kApiMaximumSupportedVersion <= RPC::kApiMaximumValidVersion);
static_assert(RPC::kApiBetaVersion <= RPC::kApiMaximumValidVersion);
BEAST_EXPECT(true);
}
{
// Update when we change versions
testcase("API versions");
static_assert(RPC::kApiMinimumSupportedVersion >= 1);
static_assert(RPC::kApiMinimumSupportedVersion < 2);
static_assert(RPC::kApiMaximumSupportedVersion >= 2);
static_assert(RPC::kApiMaximumSupportedVersion < 3);
static_assert(RPC::kApiMaximumValidVersion >= 3);
static_assert(RPC::kApiMaximumValidVersion < 4);
static_assert(RPC::kApiBetaVersion >= 3);
static_assert(RPC::kApiBetaVersion < 4);
BEAST_EXPECT(true);
}
}
};
BEAST_DEFINE_TESTSUITE(ApiVersion, protocol, xrpl);
} // namespace xrpl::test

View File

@@ -1,52 +0,0 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/Serializer.h>
#include <cstdint>
#include <initializer_list>
#include <limits>
namespace xrpl {
struct Serializer_test : public beast::unit_test::Suite
{
void
run() override
{
{
std::initializer_list<std::int32_t> const values = {
std::numeric_limits<std::int32_t>::min(),
-1,
0,
1,
std::numeric_limits<std::int32_t>::max()};
for (std::int32_t const value : values)
{
Serializer s;
s.add32(value);
BEAST_EXPECT(s.size() == 4);
SerialIter sit(s.slice());
BEAST_EXPECT(sit.geti32() == value);
}
}
{
std::initializer_list<std::int64_t> const values = {
std::numeric_limits<std::int64_t>::min(),
-1,
0,
1,
std::numeric_limits<std::int64_t>::max()};
for (std::int64_t const value : values)
{
Serializer s;
s.add64(value);
BEAST_EXPECT(s.size() == 8);
SerialIter sit(s.slice());
BEAST_EXPECT(sit.geti64() == value);
}
}
}
};
BEAST_DEFINE_TESTSUITE(Serializer, protocol, xrpl);
} // namespace xrpl

View File

@@ -32,16 +32,18 @@ target_link_libraries(xrpl_tests PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl)
# supported on Windows.
set(test_modules
basics
beast
consensus
crypto
json
ledger
nodestore
peerfinder
protocol
resource
shamap
tx
protocol_autogen
nodestore
telemetry
)
if(NOT WIN32)

View File

@@ -0,0 +1,333 @@
#include <xrpl/beast/core/SemanticVersion.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <locale>
#include <string>
#include <string_view>
#include <vector>
namespace beast {
namespace {
using IdentifierList = SemanticVersion::IdentifierList;
// Version strings are not valid C++ identifiers, so squash their punctuation to
// turn one into a gtest parameter name.
std::string
identifierFor(std::string_view version)
{
std::string name{version};
std::ranges::replace_if(
name, [](char c) { return !std::isalnum(c, std::locale::classic()); }, '_');
if (!name.empty() && std::isdigit(name.front(), std::locale::classic()))
name.insert(0, "v_");
return name;
}
// Pre-release and metadata suffixes, each applied to a "major.minor.patch" base.
// The valid ones leave a well-formed base well-formed; the invalid ones make any
// base malformed.
constexpr auto kValidPreRelease =
std::to_array<std::string_view>({"", "-1", "-a", "-a1", "-a1.b1", "-ab.cd", "--"});
constexpr auto kInvalidPreRelease =
std::to_array<std::string_view>({"+", "!", "-", "-!", "-.", "-a.!", "-0.a"});
constexpr auto kValidMetaData = std::to_array<std::string_view>({"", "+a", "+1", "+a.b", "+ab.cd"});
constexpr auto kInvalidMetaData =
std::to_array<std::string_view>({"!", "+", "++", "+!", "+.", "+a.!"});
// Assembles base + preRelease + metaData and checks whether it parses. A version
// we accept must also round-trip through print().
void
expectParse(
std::string_view base,
std::string_view preRelease,
std::string_view metaData,
bool shouldPass)
{
auto const input = std::string{base}.append(preRelease).append(metaData);
SCOPED_TRACE(::testing::Message() << '"' << input << '"');
SemanticVersion v;
if (shouldPass)
{
EXPECT_TRUE(v.parse(input));
EXPECT_EQ(v.print(), input);
}
else
{
EXPECT_FALSE(v.parse(input));
}
}
struct ParseCase
{
std::string_view testName;
std::string_view base;
bool shouldPass;
};
std::string
parseCaseName(::testing::TestParamInfo<ParseCase> const& info)
{
return std::string{info.param.testName};
}
constexpr auto kParseCases = std::to_array<ParseCase>({
{.testName = "zeroes", .base = "0.0.0", .shouldPass = true},
{.testName = "simple", .base = "1.2.3", .shouldPass = true},
{.testName = "max_int", .base = "2147483647.2147483647.2147483647", .shouldPass = true},
// negative values
{.testName = "negative_major", .base = "-1.2.3", .shouldPass = false},
{.testName = "negative_minor", .base = "1.-2.3", .shouldPass = false},
{.testName = "negative_patch", .base = "1.2.-3", .shouldPass = false},
// missing parts
{.testName = "empty", .base = "", .shouldPass = false},
{.testName = "major_only", .base = "1", .shouldPass = false},
{.testName = "major_then_dot", .base = "1.", .shouldPass = false},
{.testName = "major_and_minor", .base = "1.2", .shouldPass = false},
{.testName = "major_minor_then_dot", .base = "1.2.", .shouldPass = false},
{.testName = "missing_major", .base = ".2.3", .shouldPass = false},
// whitespace
{.testName = "leading_space", .base = " 1.2.3", .shouldPass = false},
{.testName = "space_after_major", .base = "1 .2.3", .shouldPass = false},
{.testName = "space_after_minor", .base = "1.2 .3", .shouldPass = false},
{.testName = "trailing_space", .base = "1.2.3 ", .shouldPass = false},
// leading zeroes
{.testName = "leading_zero_in_major", .base = "01.2.3", .shouldPass = false},
{.testName = "leading_zero_in_minor", .base = "1.02.3", .shouldPass = false},
{.testName = "leading_zero_in_patch", .base = "1.2.03", .shouldPass = false},
});
struct ValuesCase
{
std::string_view testName;
std::string_view input;
int majorVersion;
int minorVersion;
int patchVersion;
IdentifierList preReleaseIdentifiers{}; // NOLINT(readability-redundant-member-init)
IdentifierList metaData{}; // NOLINT(readability-redundant-member-init)
};
std::string
valuesCaseName(::testing::TestParamInfo<ValuesCase> const& info)
{
return std::string{info.param.testName};
}
std::vector<ValuesCase> const kValuesCases{
{
.testName = "zero_major",
.input = "0.1.2",
.majorVersion = 0,
.minorVersion = 1,
.patchVersion = 2,
},
{
.testName = "simple",
.input = "1.2.3",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
},
{
.testName = "one_pre_release_identifier",
.input = "1.2.3-rc1",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.preReleaseIdentifiers = {"rc1"},
},
{
.testName = "two_pre_release_identifiers",
.input = "1.2.3-rc1.debug",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.preReleaseIdentifiers = {"rc1", "debug"},
},
{
.testName = "three_pre_release_identifiers",
.input = "1.2.3-rc1.debug.asm",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.preReleaseIdentifiers = {"rc1", "debug", "asm"},
},
{
.testName = "one_metadata_identifier",
.input = "1.2.3+full",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.metaData = {"full"},
},
{
.testName = "two_metadata_identifiers",
.input = "1.2.3+full.prod",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.metaData = {"full", "prod"},
},
{
.testName = "three_metadata_identifiers",
.input = "1.2.3+full.prod.x86",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.metaData = {"full", "prod", "x86"},
},
{
.testName = "pre_release_and_metadata",
.input = "1.2.3-rc1.debug.asm+full.prod.x86",
.majorVersion = 1,
.minorVersion = 2,
.patchVersion = 3,
.preReleaseIdentifiers = {"rc1", "debug", "asm"},
.metaData = {"full", "prod", "x86"},
},
};
struct OrderCase
{
std::string_view lesser;
std::string_view greater;
};
std::string
orderCaseName(::testing::TestParamInfo<OrderCase> const& info)
{
return identifierFor(info.param.lesser) + "_below_" + identifierFor(info.param.greater);
}
constexpr auto kOrderCases = std::to_array<OrderCase>({
{.lesser = "1.0.0-alpha", .greater = "1.0.0-alpha.1"},
{.lesser = "1.0.0-alpha.1", .greater = "1.0.0-alpha.beta"},
{.lesser = "1.0.0-alpha.beta", .greater = "1.0.0-beta"},
{.lesser = "1.0.0-beta", .greater = "1.0.0-beta.2"},
{.lesser = "1.0.0-beta.2", .greater = "1.0.0-beta.11"},
{.lesser = "1.0.0-beta.11", .greater = "1.0.0-rc.1"},
{.lesser = "1.0.0-rc.1", .greater = "1.0.0"},
{.lesser = "0.9.9", .greater = "1.0.0"},
});
} // namespace
class SemanticVersionParse : public ::testing::TestWithParam<ParseCase>
{
};
// Exercises the base string on its own and with every combination of appended
// pre-release identifiers and metadata.
TEST_P(SemanticVersionParse, pre_release_and_metadata_combinations)
{
auto const& [testName, base, shouldPass] = GetParam();
for (auto const preRelease : kValidPreRelease)
{
for (auto const metaData : kValidMetaData)
expectParse(base, preRelease, metaData, shouldPass);
for (auto const metaData : kInvalidMetaData)
expectParse(base, preRelease, metaData, false);
}
// A malformed pre-release section poisons the whole string, whatever
// metadata follows it.
for (auto const preRelease : kInvalidPreRelease)
{
for (auto const metaData : kValidMetaData)
expectParse(base, preRelease, metaData, false);
for (auto const metaData : kInvalidMetaData)
expectParse(base, preRelease, metaData, false);
}
}
INSTANTIATE_TEST_SUITE_P(
Inputs,
SemanticVersionParse,
::testing::ValuesIn(kParseCases),
parseCaseName);
class SemanticVersionValues : public ::testing::TestWithParam<ValuesCase>
{
};
TEST_P(SemanticVersionValues, decomposes_into_components)
{
auto const& expected = GetParam();
SemanticVersion v;
EXPECT_TRUE(v.parse(expected.input));
EXPECT_EQ(v.majorVersion, expected.majorVersion);
EXPECT_EQ(v.minorVersion, expected.minorVersion);
EXPECT_EQ(v.patchVersion, expected.patchVersion);
EXPECT_EQ(v.preReleaseIdentifiers, expected.preReleaseIdentifiers);
EXPECT_EQ(v.metaData, expected.metaData);
}
INSTANTIATE_TEST_SUITE_P(
Inputs,
SemanticVersionValues,
::testing::ValuesIn(kValuesCases),
valuesCaseName);
class SemanticVersionOrder : public ::testing::TestWithParam<OrderCase>
{
};
TEST_P(SemanticVersionOrder, lesser_precedes_greater)
{
auto const& [lesser, greater] = GetParam();
// Metadata takes no part in precedence, so attaching it to either side must
// leave the ordering untouched.
static constexpr auto kMetaData = std::to_array<std::string_view>({"", "+meta"});
for (auto const lesserMetaData : kMetaData)
{
for (auto const greaterMetaData : kMetaData)
{
auto const lesserInput = std::string{lesser}.append(lesserMetaData);
auto const greaterInput = std::string{greater}.append(greaterMetaData);
SCOPED_TRACE(
::testing::Message() << '"' << lesserInput << "\" < \"" << greaterInput << '"');
SemanticVersion lesserVersion;
SemanticVersion greaterVersion;
EXPECT_TRUE(lesserVersion.parse(lesserInput));
EXPECT_TRUE(greaterVersion.parse(greaterInput));
EXPECT_EQ(compare(lesserVersion, lesserVersion), 0);
EXPECT_EQ(compare(greaterVersion, greaterVersion), 0);
EXPECT_LT(compare(lesserVersion, greaterVersion), 0);
EXPECT_GT(compare(greaterVersion, lesserVersion), 0);
EXPECT_LT(lesserVersion, greaterVersion);
EXPECT_GT(greaterVersion, lesserVersion);
EXPECT_EQ(lesserVersion, lesserVersion);
EXPECT_EQ(greaterVersion, greaterVersion);
}
}
}
INSTANTIATE_TEST_SUITE_P(
Pairs,
SemanticVersionOrder,
::testing::ValuesIn(kOrderCases),
orderCaseName);
} // namespace beast

View File

@@ -0,0 +1,92 @@
#include <xrpl/beast/utility/Zero.h>
#include <gtest/gtest.h>
namespace beast {
struct AdlTester
{
};
int
signum(AdlTester)
{
return 0;
}
namespace inner_adl_test {
struct AdlTester2
{
};
int
signum(AdlTester2)
{
return 0;
}
} // namespace inner_adl_test
namespace {
struct IntegerWrapper
{
int value;
IntegerWrapper(int v) : value(v)
{
}
[[nodiscard]] int
signum() const
{
return value;
}
};
void
testLhsZero(IntegerWrapper x)
{
EXPECT_EQ(x >= kZero, x.signum() >= 0);
EXPECT_EQ(x > kZero, x.signum() > 0);
EXPECT_EQ(x == kZero, x.signum() == 0);
EXPECT_EQ(x != kZero, x.signum() != 0);
EXPECT_EQ(x < kZero, x.signum() < 0);
EXPECT_EQ(x <= kZero, x.signum() <= 0);
}
void
testRhsZero(IntegerWrapper x)
{
EXPECT_EQ(kZero >= x, 0 >= x.signum());
EXPECT_EQ(kZero > x, 0 > x.signum());
EXPECT_EQ(kZero == x, 0 == x.signum());
EXPECT_EQ(kZero != x, 0 != x.signum());
EXPECT_EQ(kZero < x, 0 < x.signum());
EXPECT_EQ(kZero <= x, 0 <= x.signum());
}
} // namespace
TEST(Zero, lhs)
{
testLhsZero(-7);
testLhsZero(0);
testLhsZero(32);
}
TEST(Zero, rhs)
{
testRhsZero(-4);
testRhsZero(0);
testRhsZero(64);
}
TEST(Zero, adl)
{
EXPECT_TRUE(AdlTester{} == kZero);
EXPECT_TRUE(inner_adl_test::AdlTester2{} == kZero);
}
} // namespace beast

View File

@@ -0,0 +1,146 @@
#include <xrpl/nodestore/detail/codec.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/protocol/HashPrefix.h>
#include <gtest/gtest.h>
#include <nudb/detail/buffer.hpp>
#include <nudb/detail/stream.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <utility>
#include <vector>
using namespace xrpl;
using namespace xrpl::node_store;
namespace {
// v1 inner-node layout: 16 hashes of 32 bytes each
constexpr std::size_t kHashCount = 16;
constexpr std::size_t kHashSize = 32;
std::vector<std::uint8_t>
makeInnerNode(std::size_t nonEmptySlots)
{
using namespace nudb::detail;
static constexpr std::size_t kInnerNodeSize = 525;
std::array<std::uint8_t, kHashCount * kHashSize> hashes{};
for (auto slot = 0uz; slot < nonEmptySlots; ++slot)
{
for (auto byte = 0uz; byte < kHashSize; ++byte)
{
std::size_t const offset = (slot * kHashSize) + byte;
hashes[offset] = static_cast<std::uint8_t>((offset % 255) + 1);
}
}
std::vector<std::uint8_t> blob(kInnerNodeSize);
ostream os(blob.data(), blob.size());
write<std::uint32_t>(os, 0); // index
write<std::uint32_t>(os, 0); // unused
write<std::uint8_t>(os, static_cast<std::uint8_t>(NodeObjectType::Unknown));
write<std::uint32_t>(os, static_cast<std::uint32_t>(HashPrefix::InnerNode));
write(os, hashes.data(), hashes.size());
return blob;
}
std::uint8_t
codecType(std::pair<void const*, std::size_t> const& compressed)
{
return static_cast<std::uint8_t const*>(compressed.first)[0];
}
} // namespace
// All 16 hash slots populated - "full v1 inner node"
TEST(Codec, inner_node_full_roundtrip)
{
static constexpr std::uint8_t kTypeInnerNodeFull = 3;
auto const blob = makeInnerNode(kHashCount);
nudb::detail::buffer compressBuf;
auto const compressed = nodeobjectCompress(blob.data(), blob.size(), compressBuf);
EXPECT_EQ(codecType(compressed), kTypeInnerNodeFull);
EXPECT_EQ(compressed.second, sizeVarint(kTypeInnerNodeFull) + (kHashCount * kHashSize));
nudb::detail::buffer decompressBuf;
auto const restored = nodeobjectDecompress(compressed.first, compressed.second, decompressBuf);
EXPECT_EQ(restored.second, blob.size());
EXPECT_EQ(std::memcmp(restored.first, blob.data(), blob.size()), 0);
}
// Some hash slots empty - "compressed v1 inner node"
TEST(Codec, inner_node_compressed_roundtrip)
{
static constexpr std::uint8_t kTypeInnerNodeCompressed = 2;
static constexpr std::size_t kNonEmpty = 5;
auto const blob = makeInnerNode(kNonEmpty);
nudb::detail::buffer compressBuf;
auto const compressed = nodeobjectCompress(blob.data(), blob.size(), compressBuf);
EXPECT_EQ(codecType(compressed), kTypeInnerNodeCompressed);
EXPECT_EQ(
compressed.second,
sizeVarint(kTypeInnerNodeCompressed) + sizeof(std::uint16_t) + (kNonEmpty * kHashSize));
EXPECT_LT(compressed.second, blob.size());
nudb::detail::buffer decompressBuf;
auto const restored = nodeobjectDecompress(compressed.first, compressed.second, decompressBuf);
EXPECT_EQ(restored.second, blob.size());
EXPECT_EQ(std::memcmp(restored.first, blob.data(), blob.size()), 0);
}
// Anything that is not a v1 inner node - lz4 compressed
TEST(Codec, lz4_roundtrip)
{
// A payload that is deliberately not a v1 inner node (any size other than 525), filled with a
// short repeating pattern so lz4 actually shrinks it.
static constexpr std::size_t kNonInnerNodeSize = 1000;
static constexpr std::size_t kBytePatternPeriod = 7;
static constexpr std::uint8_t kTypeLz4 = 1;
std::vector<std::uint8_t> blob(kNonInnerNodeSize);
for (auto i = 0uz; i < blob.size(); ++i)
blob[i] = static_cast<std::uint8_t>(i % kBytePatternPeriod);
nudb::detail::buffer compressBuf;
auto const compressed = nodeobjectCompress(blob.data(), blob.size(), compressBuf);
EXPECT_EQ(codecType(compressed), kTypeLz4);
nudb::detail::buffer decompressBuf;
auto const restored = nodeobjectDecompress(compressed.first, compressed.second, decompressBuf);
EXPECT_EQ(restored.second, blob.size());
EXPECT_EQ(std::memcmp(restored.first, blob.data(), blob.size()), 0);
}
// An uncompressed blob is never produced by the compressor but must still decode: leading varint 0
// followed by the raw payload.
TEST(Codec, uncompressed_passthrough)
{
static constexpr std::uint8_t kTypeUncompressed = 0;
static constexpr auto payload = std::to_array<std::uint8_t>({0xde, 0xad, 0xbe, 0xef, 0x2a});
std::vector<std::uint8_t> blob;
blob.push_back(kTypeUncompressed); // leading varint type tag
blob.insert(blob.end(), payload.begin(), payload.end());
nudb::detail::buffer decompressBuf;
auto const restored = nodeobjectDecompress(blob.data(), blob.size(), decompressBuf);
EXPECT_EQ(restored.second, payload.size());
EXPECT_EQ(std::memcmp(restored.first, payload.data(), payload.size()), 0);
}

View File

@@ -0,0 +1,46 @@
#include <xrpl/nodestore/detail/Varint.h>
#include <gtest/gtest.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
using namespace xrpl::node_store;
TEST(Varint, encode_decode)
{
static constexpr auto kValues = std::to_array<std::size_t>({
0,
1,
2,
126,
127,
128,
253,
254,
255,
16127,
16128,
16129,
0xff,
0xffff,
0xffffffff,
0xffffffffffffUL,
std::numeric_limits<std::size_t>::max(),
});
for (auto const value : kValues)
{
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> buffer{};
auto const bytesWritten = writeVarint(buffer.data(), value);
EXPECT_GT(bytesWritten, 0u);
EXPECT_EQ(bytesWritten, sizeVarint(value));
std::size_t decoded = 0;
auto const bytesRead = readVarint(buffer.data(), bytesWritten, decoded);
EXPECT_EQ(bytesRead, bytesWritten);
EXPECT_EQ(value, decoded);
}
}

View File

@@ -1,46 +0,0 @@
#include <xrpl/nodestore/detail/varint.h>
#include <gtest/gtest.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
using namespace xrpl::node_store;
TEST(varint, encode_decode)
{
std::vector<std::size_t> const kVALUES = {
0,
1,
2,
126,
127,
128,
253,
254,
255,
16127,
16128,
16129,
0xff,
0xffff,
0xffffffff,
0xffffffffffffUL,
0xffffffffffffffffUL};
for (auto const v : kVALUES)
{
SCOPED_TRACE("value=" + std::to_string(v));
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
auto const n0 = writeVarint(vi.data(), v);
EXPECT_GT(n0, 0u) << "write error";
EXPECT_EQ(n0, sizeVarint(v)) << "size error";
std::size_t v1 = 0;
auto const n1 = readVarint(vi.data(), n0, v1);
EXPECT_EQ(n1, n0) << "read error";
EXPECT_EQ(v1, v) << "wrong value";
}
}

View File

@@ -0,0 +1,26 @@
#include <xrpl/protocol/ApiVersion.h>
#include <gtest/gtest.h>
using namespace xrpl;
TEST(ApiVersion, invariants)
{
static_assert(RPC::kApiMinimumSupportedVersion <= RPC::kApiMaximumSupportedVersion);
static_assert(RPC::kApiMinimumSupportedVersion <= RPC::kApiMaximumValidVersion);
static_assert(RPC::kApiMaximumSupportedVersion <= RPC::kApiMaximumValidVersion);
static_assert(RPC::kApiBetaVersion <= RPC::kApiMaximumValidVersion);
}
// Update when we change versions
TEST(ApiVersion, versions)
{
static_assert(RPC::kApiMinimumSupportedVersion >= 1);
static_assert(RPC::kApiMinimumSupportedVersion < 2);
static_assert(RPC::kApiMaximumSupportedVersion >= 2);
static_assert(RPC::kApiMaximumSupportedVersion < 3);
static_assert(RPC::kApiMaximumValidVersion >= 3);
static_assert(RPC::kApiMaximumValidVersion < 4);
static_assert(RPC::kApiBetaVersion >= 3);
static_assert(RPC::kApiBetaVersion < 4);
}

View File

@@ -0,0 +1,49 @@
#include <xrpl/protocol/Serializer.h>
#include <gtest/gtest.h>
#include <array>
#include <cstdint>
#include <limits>
using namespace xrpl;
TEST(Serializer, add32_roundtrip)
{
static constexpr auto kValues = std::to_array<std::int32_t>({
std::numeric_limits<std::int32_t>::min(),
-1,
0,
1,
std::numeric_limits<std::int32_t>::max(),
});
for (std::int32_t const value : kValues)
{
Serializer s;
s.add32(value);
EXPECT_EQ(s.size(), 4);
SerialIter sit(s.slice());
EXPECT_EQ(sit.geti32(), value);
}
}
TEST(Serializer, add64_roundtrip)
{
static constexpr auto kValues = std::to_array<std::int64_t>({
std::numeric_limits<std::int64_t>::min(),
-1,
0,
1,
std::numeric_limits<std::int64_t>::max(),
});
for (std::int64_t const value : kValues)
{
Serializer s;
s.add64(value);
EXPECT_EQ(s.size(), 8);
SerialIter sit(s.slice());
EXPECT_EQ(sit.geti64(), value);
}
}