diff --git a/.clang-tidy b/.clang-tidy index 68fc9e75fc..41df5470ff 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -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 diff --git a/.cspell.config.yaml b/.cspell.config.yaml index 6f8bd61ed0..24cacf5afa 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -183,7 +183,6 @@ words: - mcmodel - MEMORYSTATUSEX - Merkle - - Metafuncton - misprediction - missingok - MPTAMM diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index c1b8e4195b..3023f70cdf 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -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 diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index a6c1e6ae56..f1fdc0569a 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -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' }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31702e7c3c..098069f27c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/bin/pre-commit/clang_tidy_check.py b/bin/pre-commit/clang_tidy_check.py index cf4808d2ea..118d9619e2 100755 --- a/bin/pre-commit/clang_tidy_check.py +++ b/bin/pre-commit/clang_tidy_check.py @@ -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 diff --git a/include/xrpl/beast/core/SemanticVersion.h b/include/xrpl/beast/core/SemanticVersion.h index 338942c252..c2e395e3f4 100644 --- a/include/xrpl/beast/core/SemanticVersion.h +++ b/include/xrpl/beast/core/SemanticVersion.h @@ -17,14 +17,14 @@ namespace beast { class SemanticVersion { public: - using identifier_list = std::vector; + using IdentifierList = std::vector; int majorVersion; int minorVersion; int patchVersion; - identifier_list preReleaseIdentifiers; - identifier_list metaData; + IdentifierList preReleaseIdentifiers; + IdentifierList metaData; SemanticVersion(); diff --git a/include/xrpl/nodestore/detail/varint.h b/include/xrpl/nodestore/detail/Varint.h similarity index 91% rename from include/xrpl/nodestore/detail/varint.h rename to include/xrpl/nodestore/detail/Varint.h index afbf71cdea..5474cdc8b4 100644 --- a/include/xrpl/nodestore/detail/varint.h +++ b/include/xrpl/nodestore/detail/Varint.h @@ -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 > -struct varint_traits; +struct VarintTraits; template -struct varint_traits +struct VarintTraits { - 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 void read(nudb::detail::istream& is, std::size_t& u) - requires(std::is_same_v) + requires(std::is_same_v) { auto p0 = is(1); auto p1 = p0; @@ -118,7 +118,7 @@ read(nudb::detail::istream& is, std::size_t& u) template void write(nudb::detail::ostream& os, std::size_t t) - requires(std::is_same_v) + requires(std::is_same_v) { writeVarint(os.data(sizeVarint(t)), t); } diff --git a/include/xrpl/nodestore/detail/codec.h b/include/xrpl/nodestore/detail/codec.h index a3dfa7c944..47ad2da50a 100644 --- a/include/xrpl/nodestore/detail/codec.h +++ b/include/xrpl/nodestore/detail/codec.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include @@ -59,7 +59,7 @@ lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf) using std::runtime_error; using namespace nudb::detail; std::pair result; - std::array::kMax> vi{}; + std::array::kMax> vi{}; auto const n = writeVarint(vi.data(), inSize); auto const outMax = LZ4_compressBound(inSize); auto* out = reinterpret_cast(bf(n + outMax)); @@ -240,7 +240,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf) auto* out = reinterpret_cast(bf(result.second)); result.first = out; ostream os(out, result.second); - write(os, type); + write(os, type); write(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(bf(result.second)); result.first = out; ostream os(out, result.second); - write(os, type); + write(os, type); write(os, vh.data(), n * 32); return result; } } - std::array::kMax> vi{}; + std::array::kMax> vi{}; static constexpr std::size_t kCodecType = 1; auto const vn = writeVarint(vi.data(), kCodecType); diff --git a/src/libxrpl/beast/core/SemanticVersion.cpp b/src/libxrpl/beast/core/SemanticVersion.cpp index a99437f8f2..f902a14b07 100644 --- a/src/libxrpl/beast/core/SemanticVersion.cpp +++ b/src/libxrpl/beast/core/SemanticVersion.cpp @@ -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) { diff --git a/src/test/beast/SemanticVersion_test.cpp b/src/test/beast/SemanticVersion_test.cpp deleted file mode 100644 index d7079080c8..0000000000 --- a/src/test/beast/SemanticVersion_test.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include -#include - -#include - -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 diff --git a/src/test/beast/beast_Zero_test.cpp b/src/test/beast/beast_Zero_test.cpp deleted file mode 100644 index bb61844caa..0000000000 --- a/src/test/beast/beast_Zero_test.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include -#include - -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 diff --git a/src/test/protocol/ApiVersion_test.cpp b/src/test/protocol/ApiVersion_test.cpp deleted file mode 100644 index c41fa6f6c0..0000000000 --- a/src/test/protocol/ApiVersion_test.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -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 diff --git a/src/test/protocol/Serializer_test.cpp b/src/test/protocol/Serializer_test.cpp deleted file mode 100644 index b490e0476b..0000000000 --- a/src/test/protocol/Serializer_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include -#include -#include - -namespace xrpl { - -struct Serializer_test : public beast::unit_test::Suite -{ - void - run() override - { - { - std::initializer_list const values = { - std::numeric_limits::min(), - -1, - 0, - 1, - std::numeric_limits::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 const values = { - std::numeric_limits::min(), - -1, - 0, - 1, - std::numeric_limits::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 diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index f99b39de52..5e5d051e6f 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -27,15 +27,17 @@ target_link_libraries(xrpl_tests PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl) # supported on Windows. set(test_modules basics + beast consensus crypto json + nodestore peerfinder + protocol resource shamap tx protocol_autogen - nodestore telemetry ) if(NOT WIN32) diff --git a/src/tests/libxrpl/beast/SemanticVersion.cpp b/src/tests/libxrpl/beast/SemanticVersion.cpp new file mode 100644 index 0000000000..21b33c9476 --- /dev/null +++ b/src/tests/libxrpl/beast/SemanticVersion.cpp @@ -0,0 +1,333 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +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({"", "-1", "-a", "-a1", "-a1.b1", "-ab.cd", "--"}); +constexpr auto kInvalidPreRelease = + std::to_array({"+", "!", "-", "-!", "-.", "-a.!", "-0.a"}); +constexpr auto kValidMetaData = std::to_array({"", "+a", "+1", "+a.b", "+ab.cd"}); +constexpr auto kInvalidMetaData = + std::to_array({"!", "+", "++", "+!", "+.", "+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 const& info) +{ + return std::string{info.param.testName}; +} + +constexpr auto kParseCases = std::to_array({ + {.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 const& info) +{ + return std::string{info.param.testName}; +} + +std::vector 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 const& info) +{ + return identifierFor(info.param.lesser) + "_below_" + identifierFor(info.param.greater); +} + +constexpr auto kOrderCases = std::to_array({ + {.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 +{ +}; + +// 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 +{ +}; + +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 +{ +}; + +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({"", "+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 diff --git a/src/tests/libxrpl/beast/Zero.cpp b/src/tests/libxrpl/beast/Zero.cpp new file mode 100644 index 0000000000..2ac725509a --- /dev/null +++ b/src/tests/libxrpl/beast/Zero.cpp @@ -0,0 +1,92 @@ +#include + +#include + +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 diff --git a/src/tests/libxrpl/nodestore/Codec.cpp b/src/tests/libxrpl/nodestore/Codec.cpp new file mode 100644 index 0000000000..f31878f3c5 --- /dev/null +++ b/src/tests/libxrpl/nodestore/Codec.cpp @@ -0,0 +1,146 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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 +makeInnerNode(std::size_t nonEmptySlots) +{ + using namespace nudb::detail; + + static constexpr std::size_t kInnerNodeSize = 525; + + std::array 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((offset % 255) + 1); + } + } + + std::vector blob(kInnerNodeSize); + ostream os(blob.data(), blob.size()); + write(os, 0); // index + write(os, 0); // unused + write(os, static_cast(NodeObjectType::Unknown)); + write(os, static_cast(HashPrefix::InnerNode)); + write(os, hashes.data(), hashes.size()); + + return blob; +} + +std::uint8_t +codecType(std::pair const& compressed) +{ + return static_cast(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 blob(kNonInnerNodeSize); + for (auto i = 0uz; i < blob.size(); ++i) + blob[i] = static_cast(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({0xde, 0xad, 0xbe, 0xef, 0x2a}); + + std::vector 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); +} diff --git a/src/tests/libxrpl/nodestore/Varint.cpp b/src/tests/libxrpl/nodestore/Varint.cpp new file mode 100644 index 0000000000..3652fd5631 --- /dev/null +++ b/src/tests/libxrpl/nodestore/Varint.cpp @@ -0,0 +1,46 @@ +#include + +#include + +#include +#include +#include +#include + +using namespace xrpl::node_store; + +TEST(Varint, encode_decode) +{ + static constexpr auto kValues = std::to_array({ + 0, + 1, + 2, + 126, + 127, + 128, + 253, + 254, + 255, + 16127, + 16128, + 16129, + 0xff, + 0xffff, + 0xffffffff, + 0xffffffffffffUL, + std::numeric_limits::max(), + }); + + for (auto const value : kValues) + { + std::array::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); + } +} diff --git a/src/tests/libxrpl/nodestore/varint.cpp b/src/tests/libxrpl/nodestore/varint.cpp deleted file mode 100644 index fee96f314b..0000000000 --- a/src/tests/libxrpl/nodestore/varint.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include - -#include - -#include -#include -#include -#include -#include - -using namespace xrpl::node_store; - -TEST(varint, encode_decode) -{ - std::vector 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::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"; - } -} diff --git a/src/tests/libxrpl/protocol/ApiVersion.cpp b/src/tests/libxrpl/protocol/ApiVersion.cpp new file mode 100644 index 0000000000..8af7787102 --- /dev/null +++ b/src/tests/libxrpl/protocol/ApiVersion.cpp @@ -0,0 +1,26 @@ +#include + +#include + +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); +} diff --git a/src/tests/libxrpl/protocol/Serializer.cpp b/src/tests/libxrpl/protocol/Serializer.cpp new file mode 100644 index 0000000000..fc5742444a --- /dev/null +++ b/src/tests/libxrpl/protocol/Serializer.cpp @@ -0,0 +1,49 @@ +#include + +#include + +#include +#include +#include + +using namespace xrpl; + +TEST(Serializer, add32_roundtrip) +{ + static constexpr auto kValues = std::to_array({ + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::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::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::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); + } +}