From 5a3d5debfb271e48d40096e7001997249822014c Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 27 Jul 2026 13:25:04 +0100 Subject: [PATCH 1/8] Move lexical cast tests to gtest --- src/test/beast/LexicalCast_test.cpp | 280 ------------------------ src/tests/libxrpl/CMakeLists.txt | 1 + src/tests/libxrpl/beast/LexicalCast.cpp | 213 ++++++++++++++++++ 3 files changed, 214 insertions(+), 280 deletions(-) delete mode 100644 src/test/beast/LexicalCast_test.cpp create mode 100644 src/tests/libxrpl/beast/LexicalCast.cpp diff --git a/src/test/beast/LexicalCast_test.cpp b/src/test/beast/LexicalCast_test.cpp deleted file mode 100644 index b1d37daab8d..00000000000 --- a/src/test/beast/LexicalCast_test.cpp +++ /dev/null @@ -1,280 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include - -namespace beast { - -class LexicalCast_test : public unit_test::Suite -{ -public: - template - static IntType - nextRandomInt(xor_shift_engine& r) - { - return static_cast(r()); - } - - template - void - testInteger(IntType in) - { - std::string s; - auto out = static_cast(~in); // Ensure out != in - - expect(lexicalCastChecked(s, in)); - expect(lexicalCastChecked(out, s)); - expect(out == in); - } - - template - void - testIntegers(xor_shift_engine& r) - { - { - std::stringstream ss; - ss << "random " << typeid(IntType).name(); - testcase(ss.str()); - - for (int i = 0; i < 1000; ++i) - { - auto const value = nextRandomInt(r); - testInteger(value); - } - } - - { - std::stringstream ss; - ss << "numeric_limits <" << typeid(IntType).name() << ">"; - testcase(ss.str()); - - testInteger(std::numeric_limits::min()); - testInteger(std::numeric_limits::max()); - } - } - - void - testPathologies() - { - testcase("pathologies"); - try - { - lexicalCastThrow("\xef\xbc\x91\xef\xbc\x90"); // utf-8 encoded - } - catch (BadLexicalCast const&) - { - pass(); - } - } - - template - void - tryBadConvert(std::string const& s) - { - T out; - expect(!lexicalCastChecked(out, s), s); - } - - void - testConversionOverflows() - { - testcase("conversion overflows"); - - tryBadConvert("99999999999999999999"); - tryBadConvert("4294967300"); - tryBadConvert("75821"); - } - - void - testConversionUnderflows() - { - testcase("conversion underflows"); - - tryBadConvert("-1"); - - tryBadConvert("-99999999999999999999"); - tryBadConvert("-4294967300"); - tryBadConvert("-75821"); - } - - template - bool - tryEdgeCase(std::string const& s) - { - T ret; - - bool const result = lexicalCastChecked(ret, s); - - if (!result) - return false; - - return s == std::to_string(ret); - } - - void - testEdgeCases() - { - testcase("conversion edge cases"); - - expect(tryEdgeCase("18446744073709551614")); - expect(tryEdgeCase("18446744073709551615")); - expect(!tryEdgeCase("18446744073709551616")); - - expect(tryEdgeCase("9223372036854775806")); - expect(tryEdgeCase("9223372036854775807")); - expect(!tryEdgeCase("9223372036854775808")); - - expect(tryEdgeCase("-9223372036854775807")); - expect(tryEdgeCase("-9223372036854775808")); - expect(!tryEdgeCase("-9223372036854775809")); - - expect(tryEdgeCase("4294967294")); - expect(tryEdgeCase("4294967295")); - expect(!tryEdgeCase("4294967296")); - - expect(tryEdgeCase("2147483646")); - expect(tryEdgeCase("2147483647")); - expect(!tryEdgeCase("2147483648")); - - expect(tryEdgeCase("-2147483647")); - expect(tryEdgeCase("-2147483648")); - expect(!tryEdgeCase("-2147483649")); - - expect(tryEdgeCase("65534")); - expect(tryEdgeCase("65535")); - expect(!tryEdgeCase("65536")); - - expect(tryEdgeCase("32766")); - expect(tryEdgeCase("32767")); - expect(!tryEdgeCase("32768")); - - expect(tryEdgeCase("-32767")); - expect(tryEdgeCase("-32768")); - expect(!tryEdgeCase("-32769")); - } - - template - void - testThrowConvert(std::string const& s, bool success) - { - bool result = !success; - T out; - - try - { - out = lexicalCastThrow(s); - result = true; - } - catch (BadLexicalCast const&) - { - result = false; - } - - expect(result == success, s); - } - - void - testThrowingConversions() - { - testcase("throwing conversion"); - - testThrowConvert("99999999999999999999", false); - testThrowConvert("9223372036854775806", true); - - testThrowConvert("4294967290", true); - testThrowConvert("42949672900", false); - testThrowConvert("429496729000", false); - testThrowConvert("4294967290000", false); - - testThrowConvert("5294967295", false); - testThrowConvert("-2147483644", true); - - testThrowConvert("66666", false); - testThrowConvert("-5711", true); - } - - void - testZero() - { - testcase("zero conversion"); - - { - std::int32_t out = 0; - - expect(lexicalCastChecked(out, "-0"), "0"); - expect(lexicalCastChecked(out, "0"), "0"); - expect(lexicalCastChecked(out, "+0"), "0"); - } - - { - std::uint32_t out = 0; - - expect(!lexicalCastChecked(out, "-0"), "0"); - expect(lexicalCastChecked(out, "0"), "0"); - expect(lexicalCastChecked(out, "+0"), "0"); - } - } - - void - testEntireRange() - { - testcase("entire range"); - - std::int32_t i = std::numeric_limits::min(); - std::string const empty; - - while (i <= std::numeric_limits::max()) - { - auto const j = static_cast(i); - - auto actual = std::to_string(j); - - auto result = lexicalCast(j, empty); - - expect(result == actual, actual + " (string to integer)"); - - if (result == actual) - { - auto number = lexicalCast(result); - - if (number != j) - expect(false, actual + " (integer to string)"); - } - - i++; - } - } - - void - run() override - { - std::int64_t const seedValue = 50; - - xor_shift_engine r(seedValue); - - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - testIntegers(r); - - testPathologies(); - testConversionOverflows(); - testConversionUnderflows(); - testThrowingConversions(); - testZero(); - testEdgeCases(); - testEntireRange(); - } -}; - -BEAST_DEFINE_TESTSUITE(LexicalCast, beast, beast); - -} // namespace beast diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 4828e038154..e92df8b92b8 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries(xrpl_tests PRIVATE GTest::gtest GTest::gmock xrpl.libxrpl) # supported on Windows. set(test_modules basics + beast consensus crypto json diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp new file mode 100644 index 00000000000..d7288ece195 --- /dev/null +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -0,0 +1,213 @@ +#include + +#include + +#include + +#include +#include +#include +#include + +namespace beast { +namespace { + +template +[[nodiscard]] bool +parses(std::string const& text) +{ + T out{}; + return lexicalCastChecked(out, text); +} + +template +[[nodiscard]] bool +roundTrips(std::string const& text) +{ + T out{}; + return lexicalCastChecked(out, text) && std::to_string(out) == text; +} + +template +void +expectRoundTrip(T value) +{ + SCOPED_TRACE(::testing::Message() << "value: " << value); + + std::string text; + ASSERT_TRUE(lexicalCastChecked(text, value)); + EXPECT_EQ(text, std::to_string(value)); + + auto decoded = static_cast(~value); // ensure decoded != value + EXPECT_TRUE(lexicalCastChecked(decoded, text)); + EXPECT_EQ(decoded, value); +} + +} // namespace + +// int/unsigned/short/unsigned short are covered by the list below — they are +// these exact types everywhere we build. +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +using IntegerTypes = ::testing::Types< // + std::int16_t, + std::uint16_t, + std::int32_t, + std::uint32_t, + std::int64_t, + std::uint64_t>; + +struct IntegerTypeNames +{ + template + static std::string + // NOLINTNEXTLINE(readability-identifier-naming) - required by gtest + GetName(int) + { + return (std::is_signed_v ? "int" : "uint") + std::to_string(sizeof(T) * 8); + } +}; + +template +class LexicalCastIntegers : public ::testing::Test +{ +}; + +TYPED_TEST_SUITE(LexicalCastIntegers, IntegerTypes, IntegerTypeNames); + +TYPED_TEST(LexicalCastIntegers, round_trips_random_values) +{ + xor_shift_engine r{50}; // seeded per test so a failure reproduces on its own + + for (int i = 0; i < 1000; ++i) + expectRoundTrip(static_cast(r())); +} + +TYPED_TEST(LexicalCastIntegers, round_trips_numeric_limits) +{ + expectRoundTrip(std::numeric_limits::min()); + expectRoundTrip(std::numeric_limits::max()); +} + +TEST(LexicalCast, round_trips_every_int16_value) +{ + for (std::int32_t i = std::numeric_limits::min(); + i <= std::numeric_limits::max(); + ++i) + { + auto const value = static_cast(i); + + // ASSERT, or a broken cast reports all 65536 iterations. + auto const text = lexicalCast(value); + ASSERT_EQ(text, std::to_string(value)); + ASSERT_EQ(lexicalCast(text), value); + } +} + +TEST(LexicalCast, rejects_overflow) +{ + EXPECT_FALSE(parses("99999999999999999999")); + EXPECT_FALSE(parses("4294967300")); + EXPECT_FALSE(parses("75821")); +} + +TEST(LexicalCast, rejects_underflow) +{ + EXPECT_FALSE(parses("-1")); + + EXPECT_FALSE(parses("-99999999999999999999")); + EXPECT_FALSE(parses("-4294967300")); + EXPECT_FALSE(parses("-75821")); +} + +TEST(LexicalCast, accepts_up_to_the_maximum) +{ + EXPECT_TRUE(roundTrips("18446744073709551614")); + EXPECT_TRUE(roundTrips("18446744073709551615")); + EXPECT_FALSE(roundTrips("18446744073709551616")); + + EXPECT_TRUE(roundTrips("9223372036854775806")); + EXPECT_TRUE(roundTrips("9223372036854775807")); + EXPECT_FALSE(roundTrips("9223372036854775808")); + + EXPECT_TRUE(roundTrips("4294967294")); + EXPECT_TRUE(roundTrips("4294967295")); + EXPECT_FALSE(roundTrips("4294967296")); + + EXPECT_TRUE(roundTrips("2147483646")); + EXPECT_TRUE(roundTrips("2147483647")); + EXPECT_FALSE(roundTrips("2147483648")); + + EXPECT_TRUE(roundTrips("65534")); + EXPECT_TRUE(roundTrips("65535")); + EXPECT_FALSE(roundTrips("65536")); + + EXPECT_TRUE(roundTrips("32766")); + EXPECT_TRUE(roundTrips("32767")); + EXPECT_FALSE(roundTrips("32768")); +} + +TEST(LexicalCast, accepts_down_to_the_minimum) +{ + EXPECT_TRUE(roundTrips("-9223372036854775807")); + EXPECT_TRUE(roundTrips("-9223372036854775808")); + EXPECT_FALSE(roundTrips("-9223372036854775809")); + + EXPECT_TRUE(roundTrips("-2147483647")); + EXPECT_TRUE(roundTrips("-2147483648")); + EXPECT_FALSE(roundTrips("-2147483649")); + + EXPECT_TRUE(roundTrips("-32767")); + EXPECT_TRUE(roundTrips("-32768")); + EXPECT_FALSE(roundTrips("-32769")); +} + +// These two use the `char const*` overload, not the `std::string` one `parses` hits. +TEST(LexicalCast, accepts_signed_zero_in_every_form) +{ + std::int32_t out = 0; + + EXPECT_TRUE(lexicalCastChecked(out, "-0")); + EXPECT_TRUE(lexicalCastChecked(out, "0")); + EXPECT_TRUE(lexicalCastChecked(out, "+0")); +} + +TEST(LexicalCast, rejects_negative_zero_when_unsigned) +{ + std::uint32_t out = 0; + + EXPECT_FALSE(lexicalCastChecked(out, "-0")); + EXPECT_TRUE(lexicalCastChecked(out, "0")); + EXPECT_TRUE(lexicalCastChecked(out, "+0")); +} + +TEST(LexicalCast, throwing_cast_returns_in_range_values) +{ + EXPECT_EQ(lexicalCastThrow("9223372036854775806"), 9223372036854775806ULL); + EXPECT_EQ(lexicalCastThrow("4294967290"), 4294967290U); + EXPECT_EQ(lexicalCastThrow("-2147483644"), -2147483644); + EXPECT_EQ(lexicalCastThrow("-5711"), -5711); +} + +TEST(LexicalCast, throwing_cast_throws_on_out_of_range) +{ + EXPECT_THROW(lexicalCastThrow("99999999999999999999"), BadLexicalCast); + + EXPECT_THROW(lexicalCastThrow("42949672900"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow("429496729000"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow("4294967290000"), BadLexicalCast); + + EXPECT_THROW(lexicalCastThrow("5294967295"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow("66666"), BadLexicalCast); +} + +// Full-width digits, not ASCII ones. +TEST(LexicalCast, throwing_cast_throws_on_utf8_digits) +{ + EXPECT_THROW(lexicalCastThrow("\xef\xbc\x91\xef\xbc\x90"), BadLexicalCast); +} + +} // namespace beast From 94cdbc94271409d7eb17f59befd5f2f1f4f76613 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 27 Jul 2026 14:34:57 +0100 Subject: [PATCH 2/8] Remove useless assert and make const --- src/tests/libxrpl/beast/LexicalCast.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index d7288ece195..7240b3ddc03 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -34,8 +34,7 @@ expectRoundTrip(T value) { SCOPED_TRACE(::testing::Message() << "value: " << value); - std::string text; - ASSERT_TRUE(lexicalCastChecked(text, value)); + auto const text = lexicalCast(value); EXPECT_EQ(text, std::to_string(value)); auto decoded = static_cast(~value); // ensure decoded != value From 6c0688bb1ef43536e3d67e361125ac43001640f9 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 27 Jul 2026 14:37:11 +0100 Subject: [PATCH 3/8] Improve magic number and loop --- src/tests/libxrpl/beast/LexicalCast.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index 7240b3ddc03..6de3e8c46c4 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -79,9 +79,11 @@ TYPED_TEST_SUITE(LexicalCastIntegers, IntegerTypes, IntegerTypeNames); TYPED_TEST(LexicalCastIntegers, round_trips_random_values) { + static constexpr auto kSampleCount = 1000uz; + xor_shift_engine r{50}; // seeded per test so a failure reproduces on its own - for (int i = 0; i < 1000; ++i) + for (auto i = 0uz; i < kSampleCount; ++i) expectRoundTrip(static_cast(r())); } From fab27b987241e144edf8b6dba464330802222b17 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 28 Jul 2026 18:06:48 +0100 Subject: [PATCH 4/8] Lexical cast constexpr --- include/xrpl/beast/core/LexicalCast.h | 6 +- src/tests/libxrpl/beast/LexicalCast.cpp | 119 +++++++++++++++--------- 2 files changed, 76 insertions(+), 49 deletions(-) diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index 7cf21892bdd..23c9b6c3c07 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -58,7 +58,7 @@ struct LexicalCast "beast::LexicalCast can only be used with integral types"); template - bool + constexpr bool operator()(Integral& out, std::string_view in) const requires(std::is_integral_v && !std::is_same_v) { @@ -177,7 +177,7 @@ struct BadLexicalCast : public std::bad_cast * @return `false` if there was a parsing or range error */ template -bool +constexpr bool lexicalCastChecked(Out& out, In in) { return detail::LexicalCast()(out, in); @@ -207,7 +207,7 @@ lexicalCastThrow(In in) * @return The new type. */ template -Out +constexpr Out lexicalCast(In in, Out defaultValue = Out()) { if (Out out; lexicalCastChecked(out, in)) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index 6de3e8c46c4..386ac38753d 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -7,19 +7,28 @@ #include #include #include +#include #include namespace beast { namespace { template -[[nodiscard]] bool -parses(std::string const& text) +[[nodiscard]] constexpr bool +parses(std::string_view text) { T out{}; return lexicalCastChecked(out, text); } +template +[[nodiscard]] constexpr T +parsed(std::string_view text) +{ + T out{}; + return lexicalCastChecked(out, text) ? out : T{}; +} + template [[nodiscard]] bool roundTrips(std::string const& text) @@ -66,7 +75,7 @@ struct IntegerTypeNames // NOLINTNEXTLINE(readability-identifier-naming) - required by gtest GetName(int) { - return (std::is_signed_v ? "int" : "uint") + std::to_string(sizeof(T) * 8); + return (std::is_signed_v ? "int" : "uint") + std::to_string(sizeof(T) * 8) + "_t"; } }; @@ -110,79 +119,97 @@ TEST(LexicalCast, round_trips_every_int16_value) TEST(LexicalCast, rejects_overflow) { - EXPECT_FALSE(parses("99999999999999999999")); - EXPECT_FALSE(parses("4294967300")); - EXPECT_FALSE(parses("75821")); + static_assert(not parses("99999999999999999999")); + static_assert(not parses("4294967300")); + static_assert(not parses("75821")); } TEST(LexicalCast, rejects_underflow) { - EXPECT_FALSE(parses("-1")); + static_assert(not parses("-1")); - EXPECT_FALSE(parses("-99999999999999999999")); - EXPECT_FALSE(parses("-4294967300")); - EXPECT_FALSE(parses("-75821")); + static_assert(not parses("-99999999999999999999")); + static_assert(not parses("-4294967300")); + static_assert(not parses("-75821")); } TEST(LexicalCast, accepts_up_to_the_maximum) { - EXPECT_TRUE(roundTrips("18446744073709551614")); - EXPECT_TRUE(roundTrips("18446744073709551615")); - EXPECT_FALSE(roundTrips("18446744073709551616")); + static_assert(parsed("18446744073709551614") == 18446744073709551614ULL); + static_assert(parsed("18446744073709551615") == 18446744073709551615ULL); + static_assert(not parses("18446744073709551616")); - EXPECT_TRUE(roundTrips("9223372036854775806")); - EXPECT_TRUE(roundTrips("9223372036854775807")); - EXPECT_FALSE(roundTrips("9223372036854775808")); + static_assert(parsed("9223372036854775806") == 9223372036854775806LL); + static_assert(parsed("9223372036854775807") == 9223372036854775807LL); + static_assert(not parses("9223372036854775808")); - EXPECT_TRUE(roundTrips("4294967294")); - EXPECT_TRUE(roundTrips("4294967295")); - EXPECT_FALSE(roundTrips("4294967296")); + static_assert(parsed("4294967294") == 4294967294U); + static_assert(parsed("4294967295") == 4294967295U); + static_assert(not parses("4294967296")); - EXPECT_TRUE(roundTrips("2147483646")); - EXPECT_TRUE(roundTrips("2147483647")); - EXPECT_FALSE(roundTrips("2147483648")); + static_assert(parsed("2147483646") == 2147483646); + static_assert(parsed("2147483647") == 2147483647); + static_assert(not parses("2147483648")); - EXPECT_TRUE(roundTrips("65534")); - EXPECT_TRUE(roundTrips("65535")); - EXPECT_FALSE(roundTrips("65536")); + static_assert(parsed("65534") == 65534); + static_assert(parsed("65535") == 65535); + static_assert(not parses("65536")); - EXPECT_TRUE(roundTrips("32766")); - EXPECT_TRUE(roundTrips("32767")); - EXPECT_FALSE(roundTrips("32768")); + static_assert(parsed("32766") == 32766); + static_assert(parsed("32767") == 32767); + static_assert(not parses("32768")); } TEST(LexicalCast, accepts_down_to_the_minimum) { - EXPECT_TRUE(roundTrips("-9223372036854775807")); - EXPECT_TRUE(roundTrips("-9223372036854775808")); - EXPECT_FALSE(roundTrips("-9223372036854775809")); + static_assert(parsed("-9223372036854775807") == -9223372036854775807LL); + static_assert( + parsed("-9223372036854775808") == std::numeric_limits::min()); + static_assert(not parses("-9223372036854775809")); - EXPECT_TRUE(roundTrips("-2147483647")); - EXPECT_TRUE(roundTrips("-2147483648")); - EXPECT_FALSE(roundTrips("-2147483649")); + static_assert(parsed("-2147483647") == -2147483647); + static_assert(parsed("-2147483648") == std::numeric_limits::min()); + static_assert(not parses("-2147483649")); - EXPECT_TRUE(roundTrips("-32767")); + static_assert(parsed("-32767") == -32767); + static_assert(parsed("-32768") == std::numeric_limits::min()); + static_assert(not parses("-32769")); +} + +TEST(LexicalCast, limits_round_trip_through_to_string) +{ + EXPECT_TRUE(roundTrips("18446744073709551615")); + EXPECT_TRUE(roundTrips("9223372036854775807")); + EXPECT_TRUE(roundTrips("-9223372036854775808")); + EXPECT_TRUE(roundTrips("4294967295")); + EXPECT_TRUE(roundTrips("-2147483648")); + EXPECT_TRUE(roundTrips("65535")); EXPECT_TRUE(roundTrips("-32768")); - EXPECT_FALSE(roundTrips("-32769")); } -// These two use the `char const*` overload, not the `std::string` one `parses` hits. TEST(LexicalCast, accepts_signed_zero_in_every_form) { - std::int32_t out = 0; - - EXPECT_TRUE(lexicalCastChecked(out, "-0")); - EXPECT_TRUE(lexicalCastChecked(out, "0")); - EXPECT_TRUE(lexicalCastChecked(out, "+0")); + static_assert(parsed("-0") == 0); + static_assert(parsed("0") == 0); + static_assert(parsed("+0") == 0); } TEST(LexicalCast, rejects_negative_zero_when_unsigned) { - std::uint32_t out = 0; + static_assert(not parses("-0")); + static_assert(parsed("0") == 0); + static_assert(parsed("+0") == 0); +} + +TEST(LexicalCast, accepts_char_pointer_and_std_string_input) +{ + std::int32_t fromLiteral = 0; + EXPECT_TRUE(lexicalCastChecked(fromLiteral, "+42")); + EXPECT_EQ(fromLiteral, 42); - EXPECT_FALSE(lexicalCastChecked(out, "-0")); - EXPECT_TRUE(lexicalCastChecked(out, "0")); - EXPECT_TRUE(lexicalCastChecked(out, "+0")); + std::int32_t fromString = 0; + EXPECT_TRUE(lexicalCastChecked(fromString, std::string{"-42"})); + EXPECT_EQ(fromString, -42); } TEST(LexicalCast, throwing_cast_returns_in_range_values) From a8c955617ca444d73a349dd86fe98ff48b25b28e Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Tue, 28 Jul 2026 18:37:34 +0100 Subject: [PATCH 5/8] Less hardcoded magic strings --- src/tests/libxrpl/beast/LexicalCast.cpp | 201 +++++++++++++++--------- 1 file changed, 125 insertions(+), 76 deletions(-) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index 386ac38753d..fd1f1078dd4 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -4,6 +4,9 @@ #include +#include +#include +#include #include #include #include @@ -29,9 +32,53 @@ parsed(std::string_view text) return lexicalCastChecked(out, text) ? out : T{}; } +template +constexpr T kMax = std::numeric_limits::max(); + +template +constexpr T kMin = std::numeric_limits::min(); + +// Comfortably inside the range, not boundary values. +constexpr auto kNearMax32 = kMax - 5; +constexpr auto kNearMin32 = kMin + 4; +constexpr auto kUnderInt64Max = uint64_t{kMax} - 1; + +// No wider integer type can hold these, so ToString cannot produce them. +constexpr std::string_view kAboveUint64Max = "18446744073709551616"; +constexpr std::string_view kBelowInt64Min = "-9223372036854775809"; + +// The decimal text of a value, usable in a constant expression. +template +struct ToString +{ + std::array buffer{}; + std::size_t length{}; + + constexpr explicit ToString(T value) + { + auto const result = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value); + length = static_cast(result.ptr - buffer.data()); + } + + constexpr + operator std::string_view() const + { + return {buffer.data(), length}; + } +}; + +// lexicalCastThrow deduces its input type, so the text has to be an explicit +// string_view rather than a ToString. +template +[[nodiscard]] T +castThrow(Value value) +{ + return lexicalCastThrow(std::string_view{ToString{value}}); +} + template [[nodiscard]] bool -roundTrips(std::string const& text) +roundTrips(std::string_view text) { T out{}; return lexicalCastChecked(out, text) && std::to_string(out) == text; @@ -55,18 +102,18 @@ expectRoundTrip(T value) // int/unsigned/short/unsigned short are covered by the list below — they are // these exact types everywhere we build. -static_assert(std::is_same_v); -static_assert(std::is_same_v); -static_assert(std::is_same_v); -static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); using IntegerTypes = ::testing::Types< // - std::int16_t, - std::uint16_t, - std::int32_t, - std::uint32_t, - std::int64_t, - std::uint64_t>; + int16_t, + uint16_t, + int32_t, + uint32_t, + int64_t, + uint64_t>; struct IntegerTypeNames { @@ -104,132 +151,134 @@ TYPED_TEST(LexicalCastIntegers, round_trips_numeric_limits) TEST(LexicalCast, round_trips_every_int16_value) { - for (std::int32_t i = std::numeric_limits::min(); - i <= std::numeric_limits::max(); - ++i) + for (int32_t i = kMin; i <= kMax; ++i) { - auto const value = static_cast(i); + auto const value = static_cast(i); // ASSERT, or a broken cast reports all 65536 iterations. auto const text = lexicalCast(value); ASSERT_EQ(text, std::to_string(value)); - ASSERT_EQ(lexicalCast(text), value); + ASSERT_EQ(lexicalCast(text), value); } } TEST(LexicalCast, rejects_overflow) { - static_assert(not parses("99999999999999999999")); - static_assert(not parses("4294967300")); - static_assert(not parses("75821")); + static_assert(not parses(ToString{uint64_t{kMax} + 5})); + + // Well past the maximum rather than just over it. + static_assert(not parses("99999999999999999999")); + static_assert(not parses("75821")); } TEST(LexicalCast, rejects_underflow) { - static_assert(not parses("-1")); + static_assert(not parses("-1")); + static_assert(not parses(ToString{-(int64_t{kMax} + 5)})); - static_assert(not parses("-99999999999999999999")); - static_assert(not parses("-4294967300")); - static_assert(not parses("-75821")); + static_assert(not parses("-99999999999999999999")); + static_assert(not parses("-75821")); } TEST(LexicalCast, accepts_up_to_the_maximum) { - static_assert(parsed("18446744073709551614") == 18446744073709551614ULL); - static_assert(parsed("18446744073709551615") == 18446744073709551615ULL); - static_assert(not parses("18446744073709551616")); + static_assert(parsed(ToString{kMax - 1}) == kMax < uint16_t > -1); + static_assert(parsed(ToString{kMax}) == kMax); + static_assert(not parses(ToString{uint32_t{kMax} + 1})); - static_assert(parsed("9223372036854775806") == 9223372036854775806LL); - static_assert(parsed("9223372036854775807") == 9223372036854775807LL); - static_assert(not parses("9223372036854775808")); + static_assert(parsed(ToString{kMax - 1}) == kMax < int16_t > -1); + static_assert(parsed(ToString{kMax}) == kMax); + static_assert(not parses(ToString{int32_t{kMax} + 1})); - static_assert(parsed("4294967294") == 4294967294U); - static_assert(parsed("4294967295") == 4294967295U); - static_assert(not parses("4294967296")); + static_assert(parsed(ToString{kMax - 1}) == kMax < uint32_t > -1); + static_assert(parsed(ToString{kMax}) == kMax); + static_assert(not parses(ToString{uint64_t{kMax} + 1})); - static_assert(parsed("2147483646") == 2147483646); - static_assert(parsed("2147483647") == 2147483647); - static_assert(not parses("2147483648")); + static_assert(parsed(ToString{kMax - 1}) == kMax < int32_t > -1); + static_assert(parsed(ToString{kMax}) == kMax); + static_assert(not parses(ToString{int64_t{kMax} + 1})); - static_assert(parsed("65534") == 65534); - static_assert(parsed("65535") == 65535); - static_assert(not parses("65536")); + static_assert(parsed(ToString{kMax - 1}) == kMax < int64_t > -1); + static_assert(parsed(ToString{kMax}) == kMax); + static_assert(not parses(ToString{uint64_t{kMax} + 1})); - static_assert(parsed("32766") == 32766); - static_assert(parsed("32767") == 32767); - static_assert(not parses("32768")); + static_assert(parsed(ToString{kMax - 1}) == kMax < uint64_t > -1); + static_assert(parsed(ToString{kMax}) == kMax); + static_assert(not parses(kAboveUint64Max)); } TEST(LexicalCast, accepts_down_to_the_minimum) { - static_assert(parsed("-9223372036854775807") == -9223372036854775807LL); - static_assert( - parsed("-9223372036854775808") == std::numeric_limits::min()); - static_assert(not parses("-9223372036854775809")); + static_assert(parsed(ToString{kMin + 1}) == kMin + 1); + static_assert(parsed(ToString{kMin}) == kMin); + static_assert(not parses(ToString{int32_t{kMin} - 1})); - static_assert(parsed("-2147483647") == -2147483647); - static_assert(parsed("-2147483648") == std::numeric_limits::min()); - static_assert(not parses("-2147483649")); + static_assert(parsed(ToString{kMin + 1}) == kMin + 1); + static_assert(parsed(ToString{kMin}) == kMin); + static_assert(not parses(ToString{int64_t{kMin} - 1})); - static_assert(parsed("-32767") == -32767); - static_assert(parsed("-32768") == std::numeric_limits::min()); - static_assert(not parses("-32769")); + static_assert(parsed(ToString{kMin + 1}) == kMin + 1); + static_assert(parsed(ToString{kMin}) == kMin); + static_assert(not parses(kBelowInt64Min)); } TEST(LexicalCast, limits_round_trip_through_to_string) { - EXPECT_TRUE(roundTrips("18446744073709551615")); - EXPECT_TRUE(roundTrips("9223372036854775807")); - EXPECT_TRUE(roundTrips("-9223372036854775808")); - EXPECT_TRUE(roundTrips("4294967295")); - EXPECT_TRUE(roundTrips("-2147483648")); - EXPECT_TRUE(roundTrips("65535")); - EXPECT_TRUE(roundTrips("-32768")); + EXPECT_TRUE(roundTrips(ToString{kMax})); + EXPECT_TRUE(roundTrips(ToString{kMax})); + EXPECT_TRUE(roundTrips(ToString{kMin})); + EXPECT_TRUE(roundTrips(ToString{kMax})); + EXPECT_TRUE(roundTrips(ToString{kMin})); + EXPECT_TRUE(roundTrips(ToString{kMax})); + EXPECT_TRUE(roundTrips(ToString{kMin})); } TEST(LexicalCast, accepts_signed_zero_in_every_form) { - static_assert(parsed("-0") == 0); - static_assert(parsed("0") == 0); - static_assert(parsed("+0") == 0); + static_assert(parsed("-0") == 0); + static_assert(parsed("0") == 0); + static_assert(parsed("+0") == 0); } TEST(LexicalCast, rejects_negative_zero_when_unsigned) { - static_assert(not parses("-0")); - static_assert(parsed("0") == 0); - static_assert(parsed("+0") == 0); + static_assert(not parses("-0")); + static_assert(parsed("0") == 0); + static_assert(parsed("+0") == 0); } TEST(LexicalCast, accepts_char_pointer_and_std_string_input) { - std::int32_t fromLiteral = 0; + int32_t fromLiteral = 0; EXPECT_TRUE(lexicalCastChecked(fromLiteral, "+42")); EXPECT_EQ(fromLiteral, 42); - std::int32_t fromString = 0; + int32_t fromString = 0; EXPECT_TRUE(lexicalCastChecked(fromString, std::string{"-42"})); EXPECT_EQ(fromString, -42); } TEST(LexicalCast, throwing_cast_returns_in_range_values) { - EXPECT_EQ(lexicalCastThrow("9223372036854775806"), 9223372036854775806ULL); - EXPECT_EQ(lexicalCastThrow("4294967290"), 4294967290U); - EXPECT_EQ(lexicalCastThrow("-2147483644"), -2147483644); - EXPECT_EQ(lexicalCastThrow("-5711"), -5711); + EXPECT_EQ(castThrow(kUnderInt64Max), kUnderInt64Max); + EXPECT_EQ(castThrow(kNearMax32), kNearMax32); + EXPECT_EQ(castThrow(kNearMin32), kNearMin32); + EXPECT_EQ(lexicalCastThrow("-5711"), -5711); } TEST(LexicalCast, throwing_cast_throws_on_out_of_range) { - EXPECT_THROW(lexicalCastThrow("99999999999999999999"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow("99999999999999999999"), BadLexicalCast); - EXPECT_THROW(lexicalCastThrow("42949672900"), BadLexicalCast); - EXPECT_THROW(lexicalCastThrow("429496729000"), BadLexicalCast); - EXPECT_THROW(lexicalCastThrow("4294967290000"), BadLexicalCast); + // kNearMax32 with digits appended, so each is further past uint32_t's range. + for (auto const scale : {10, 100, 1000}) + { + auto const tooBig = ToString{uint64_t{kNearMax32} * scale}; + EXPECT_THROW(lexicalCastThrow(std::string_view{tooBig}), BadLexicalCast); + } - EXPECT_THROW(lexicalCastThrow("5294967295"), BadLexicalCast); - EXPECT_THROW(lexicalCastThrow("66666"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow("5294967295"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow("66666"), BadLexicalCast); } // Full-width digits, not ASCII ones. From 4232f897e85719b01c3f39c5a0c286253f5e5db0 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 29 Jul 2026 12:26:39 +0100 Subject: [PATCH 6/8] A bit better readability for lexical cast tests --- src/tests/libxrpl/beast/LexicalCast.cpp | 158 +++++++++++++++--------- 1 file changed, 103 insertions(+), 55 deletions(-) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index fd1f1078dd4..9bcfd7a1399 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -38,6 +38,12 @@ constexpr T kMax = std::numeric_limits::max(); template constexpr T kMin = std::numeric_limits::min(); +template +constexpr T kUnderMax = kMax - 1; + +template +constexpr T kOverMin = kMin + 1; + // Comfortably inside the range, not boundary values. constexpr auto kNearMax32 = kMax - 5; constexpr auto kNearMin32 = kMin + 4; @@ -47,6 +53,29 @@ constexpr auto kUnderInt64Max = uint64_t{kMax} - 1; constexpr std::string_view kAboveUint64Max = "18446744073709551616"; constexpr std::string_view kBelowInt64Min = "-9223372036854775809"; +// Out of range for every integer type we test. +constexpr std::string_view kTwentyNines = "99999999999999999999"; +constexpr std::string_view kNegativeTwentyNines = "-99999999999999999999"; + +// Arbitrary values chosen to sit well outside a type's range, not just over it. +constexpr std::string_view kAboveUint16Max = "75821"; +constexpr std::string_view kBelowInt16Min = "-75821"; +constexpr std::string_view kAboveInt32Max = "5294967295"; +constexpr std::string_view kAboveInt16Max = "66666"; + +// Arbitrary values comfortably inside a type's range. +constexpr std::string_view kInRangeInt16 = "-5711"; +constexpr std::string_view kPositiveInt32 = "+42"; +constexpr std::string_view kNegativeInt32 = "-42"; + +constexpr std::string_view kNegativeOne = "-1"; +constexpr std::string_view kNegativeZero = "-0"; +constexpr std::string_view kZero = "0"; +constexpr std::string_view kPositiveZero = "+0"; + +// Full-width digits one and zero, not ASCII ones. +constexpr std::string_view kFullWidthDigits = "\xef\xbc\x91\xef\xbc\x90"; + // The decimal text of a value, usable in a constant expression. template struct ToString @@ -67,6 +96,27 @@ struct ToString } }; +template +constexpr auto kMaxText = ToString{kMax}; + +template +constexpr auto kUnderMaxText = ToString{kUnderMax}; + +template +constexpr auto kOverMaxText = ToString{Wider{kMax} + 1}; + +template +constexpr auto kMinText = ToString{kMin}; + +template +constexpr auto kOverMinText = ToString{kOverMin}; + +template +constexpr auto kUnderMinText = ToString{Wider{kMin} - 1}; + +constexpr auto kOverUint32MaxText = ToString{uint64_t{kMax} + 5}; +constexpr auto kNegatedOverUint32MaxText = ToString{-(int64_t{kMax} + 5)}; + // lexicalCastThrow deduces its input type, so the text has to be an explicit // string_view rather than a ToString. template @@ -164,97 +214,95 @@ TEST(LexicalCast, round_trips_every_int16_value) TEST(LexicalCast, rejects_overflow) { - static_assert(not parses(ToString{uint64_t{kMax} + 5})); - - // Well past the maximum rather than just over it. - static_assert(not parses("99999999999999999999")); - static_assert(not parses("75821")); + static_assert(not parses(kOverUint32MaxText)); + static_assert(not parses(kTwentyNines)); + static_assert(not parses(kAboveUint16Max)); } TEST(LexicalCast, rejects_underflow) { - static_assert(not parses("-1")); - static_assert(not parses(ToString{-(int64_t{kMax} + 5)})); - - static_assert(not parses("-99999999999999999999")); - static_assert(not parses("-75821")); + static_assert(not parses(kNegativeOne)); + static_assert(not parses(kNegatedOverUint32MaxText)); + static_assert(not parses(kNegativeTwentyNines)); + static_assert(not parses(kBelowInt16Min)); } TEST(LexicalCast, accepts_up_to_the_maximum) { - static_assert(parsed(ToString{kMax - 1}) == kMax < uint16_t > -1); - static_assert(parsed(ToString{kMax}) == kMax); - static_assert(not parses(ToString{uint32_t{kMax} + 1})); + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); - static_assert(parsed(ToString{kMax - 1}) == kMax < int16_t > -1); - static_assert(parsed(ToString{kMax}) == kMax); - static_assert(not parses(ToString{int32_t{kMax} + 1})); + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); - static_assert(parsed(ToString{kMax - 1}) == kMax < uint32_t > -1); - static_assert(parsed(ToString{kMax}) == kMax); - static_assert(not parses(ToString{uint64_t{kMax} + 1})); + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); - static_assert(parsed(ToString{kMax - 1}) == kMax < int32_t > -1); - static_assert(parsed(ToString{kMax}) == kMax); - static_assert(not parses(ToString{int64_t{kMax} + 1})); + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); - static_assert(parsed(ToString{kMax - 1}) == kMax < int64_t > -1); - static_assert(parsed(ToString{kMax}) == kMax); - static_assert(not parses(ToString{uint64_t{kMax} + 1})); + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); + static_assert(not parses(kOverMaxText)); - static_assert(parsed(ToString{kMax - 1}) == kMax < uint64_t > -1); - static_assert(parsed(ToString{kMax}) == kMax); + static_assert(parsed(kUnderMaxText) == kUnderMax); + static_assert(parsed(kMaxText) == kMax); static_assert(not parses(kAboveUint64Max)); } TEST(LexicalCast, accepts_down_to_the_minimum) { - static_assert(parsed(ToString{kMin + 1}) == kMin + 1); - static_assert(parsed(ToString{kMin}) == kMin); - static_assert(not parses(ToString{int32_t{kMin} - 1})); + static_assert(parsed(kOverMinText) == kOverMin); + static_assert(parsed(kMinText) == kMin); + static_assert(not parses(kUnderMinText)); - static_assert(parsed(ToString{kMin + 1}) == kMin + 1); - static_assert(parsed(ToString{kMin}) == kMin); - static_assert(not parses(ToString{int64_t{kMin} - 1})); + static_assert(parsed(kOverMinText) == kOverMin); + static_assert(parsed(kMinText) == kMin); + static_assert(not parses(kUnderMinText)); - static_assert(parsed(ToString{kMin + 1}) == kMin + 1); - static_assert(parsed(ToString{kMin}) == kMin); + static_assert(parsed(kOverMinText) == kOverMin); + static_assert(parsed(kMinText) == kMin); static_assert(not parses(kBelowInt64Min)); } TEST(LexicalCast, limits_round_trip_through_to_string) { - EXPECT_TRUE(roundTrips(ToString{kMax})); - EXPECT_TRUE(roundTrips(ToString{kMax})); - EXPECT_TRUE(roundTrips(ToString{kMin})); - EXPECT_TRUE(roundTrips(ToString{kMax})); - EXPECT_TRUE(roundTrips(ToString{kMin})); - EXPECT_TRUE(roundTrips(ToString{kMax})); - EXPECT_TRUE(roundTrips(ToString{kMin})); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMinText)); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMinText)); + EXPECT_TRUE(roundTrips(kMaxText)); + EXPECT_TRUE(roundTrips(kMinText)); } TEST(LexicalCast, accepts_signed_zero_in_every_form) { - static_assert(parsed("-0") == 0); - static_assert(parsed("0") == 0); - static_assert(parsed("+0") == 0); + static_assert(parsed(kNegativeZero) == 0); + static_assert(parsed(kZero) == 0); + static_assert(parsed(kPositiveZero) == 0); } TEST(LexicalCast, rejects_negative_zero_when_unsigned) { - static_assert(not parses("-0")); - static_assert(parsed("0") == 0); - static_assert(parsed("+0") == 0); + static_assert(not parses(kNegativeZero)); + static_assert(parsed(kZero) == 0); + static_assert(parsed(kPositiveZero) == 0); } TEST(LexicalCast, accepts_char_pointer_and_std_string_input) { int32_t fromLiteral = 0; - EXPECT_TRUE(lexicalCastChecked(fromLiteral, "+42")); + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + EXPECT_TRUE(lexicalCastChecked(fromLiteral, kPositiveInt32.data())); EXPECT_EQ(fromLiteral, 42); int32_t fromString = 0; - EXPECT_TRUE(lexicalCastChecked(fromString, std::string{"-42"})); + EXPECT_TRUE(lexicalCastChecked(fromString, std::string{kNegativeInt32})); EXPECT_EQ(fromString, -42); } @@ -263,12 +311,12 @@ TEST(LexicalCast, throwing_cast_returns_in_range_values) EXPECT_EQ(castThrow(kUnderInt64Max), kUnderInt64Max); EXPECT_EQ(castThrow(kNearMax32), kNearMax32); EXPECT_EQ(castThrow(kNearMin32), kNearMin32); - EXPECT_EQ(lexicalCastThrow("-5711"), -5711); + EXPECT_EQ(lexicalCastThrow(kInRangeInt16), -5711); } TEST(LexicalCast, throwing_cast_throws_on_out_of_range) { - EXPECT_THROW(lexicalCastThrow("99999999999999999999"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow(kTwentyNines), BadLexicalCast); // kNearMax32 with digits appended, so each is further past uint32_t's range. for (auto const scale : {10, 100, 1000}) @@ -277,14 +325,14 @@ TEST(LexicalCast, throwing_cast_throws_on_out_of_range) EXPECT_THROW(lexicalCastThrow(std::string_view{tooBig}), BadLexicalCast); } - EXPECT_THROW(lexicalCastThrow("5294967295"), BadLexicalCast); - EXPECT_THROW(lexicalCastThrow("66666"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow(kAboveInt32Max), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow(kAboveInt16Max), BadLexicalCast); } // Full-width digits, not ASCII ones. TEST(LexicalCast, throwing_cast_throws_on_utf8_digits) { - EXPECT_THROW(lexicalCastThrow("\xef\xbc\x91\xef\xbc\x90"), BadLexicalCast); + EXPECT_THROW(lexicalCastThrow(kFullWidthDigits), BadLexicalCast); } } // namespace beast From 8497167aaade79b38a2754d0df1de47a0e87fab2 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 29 Jul 2026 14:45:17 +0100 Subject: [PATCH 7/8] More constexpr and constants --- include/xrpl/beast/core/LexicalCast.h | 10 ++--- src/tests/libxrpl/beast/LexicalCast.cpp | 53 +++++++++++++------------ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index 23c9b6c3c07..288c5d66730 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -110,7 +110,7 @@ struct LexicalCast> { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, boost::core::basic_string_view in) const { return LexicalCast()(out, in); @@ -123,7 +123,7 @@ struct LexicalCast { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, std::string in) const { return LexicalCast()(out, in); @@ -136,7 +136,7 @@ struct LexicalCast { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, char const* in) const { XRPL_ASSERT(in, "beast::detail::LexicalCast(char const*) : non-null input"); @@ -151,7 +151,7 @@ struct LexicalCast { explicit LexicalCast() = default; - bool + constexpr bool operator()(Out& out, char* in) const { XRPL_ASSERT(in, "beast::detail::LexicalCast(char*) : non-null input"); @@ -191,7 +191,7 @@ lexicalCastChecked(Out& out, In in) * @return The new type. */ template -Out +constexpr Out lexicalCastThrow(In in) { if (Out out; lexicalCastChecked(out, in)) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index 9bcfd7a1399..c25dbced2e3 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -48,30 +48,32 @@ constexpr T kOverMin = kMin + 1; constexpr auto kNearMax32 = kMax - 5; constexpr auto kNearMin32 = kMin + 4; constexpr auto kUnderInt64Max = uint64_t{kMax} - 1; +constexpr auto kInRangeInt16 = int16_t{-5711}; // No wider integer type can hold these, so ToString cannot produce them. -constexpr std::string_view kAboveUint64Max = "18446744073709551616"; -constexpr std::string_view kBelowInt64Min = "-9223372036854775809"; +constexpr auto kAboveUint64Max = "18446744073709551616"; +constexpr auto kBelowInt64Min = "-9223372036854775809"; // Out of range for every integer type we test. -constexpr std::string_view kTwentyNines = "99999999999999999999"; -constexpr std::string_view kNegativeTwentyNines = "-99999999999999999999"; +constexpr auto kTwentyNines = "99999999999999999999"; +constexpr auto kNegativeTwentyNines = "-99999999999999999999"; // Arbitrary values chosen to sit well outside a type's range, not just over it. -constexpr std::string_view kAboveUint16Max = "75821"; -constexpr std::string_view kBelowInt16Min = "-75821"; -constexpr std::string_view kAboveInt32Max = "5294967295"; -constexpr std::string_view kAboveInt16Max = "66666"; +constexpr auto kAboveUint16Max = "75821"; +constexpr auto kBelowInt16Min = "-75821"; +constexpr auto kAboveInt32Max = "5294967295"; +constexpr auto kAboveInt16Max = "66666"; -// Arbitrary values comfortably inside a type's range. -constexpr std::string_view kInRangeInt16 = "-5711"; -constexpr std::string_view kPositiveInt32 = "+42"; -constexpr std::string_view kNegativeInt32 = "-42"; +constexpr auto kPositiveInt32 = int32_t{42}; +constexpr auto kNegativeInt32 = int32_t{-42}; -constexpr std::string_view kNegativeOne = "-1"; -constexpr std::string_view kNegativeZero = "-0"; -constexpr std::string_view kZero = "0"; -constexpr std::string_view kPositiveZero = "+0"; +constexpr auto kPositiveInt32Text = "+42"; +constexpr auto kNegativeInt32Text = "-42"; + +constexpr auto kNegativeOne = "-1"; +constexpr auto kNegativeZero = "-0"; +constexpr auto kZero = "0"; +constexpr auto kPositiveZero = "+0"; // Full-width digits one and zero, not ASCII ones. constexpr std::string_view kFullWidthDigits = "\xef\xbc\x91\xef\xbc\x90"; @@ -120,7 +122,7 @@ constexpr auto kNegatedOverUint32MaxText = ToString{-(int64_t{kMax} + // lexicalCastThrow deduces its input type, so the text has to be an explicit // string_view rather than a ToString. template -[[nodiscard]] T +[[nodiscard]] constexpr T castThrow(Value value) { return lexicalCastThrow(std::string_view{ToString{value}}); @@ -297,21 +299,20 @@ TEST(LexicalCast, rejects_negative_zero_when_unsigned) TEST(LexicalCast, accepts_char_pointer_and_std_string_input) { int32_t fromLiteral = 0; - // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) - EXPECT_TRUE(lexicalCastChecked(fromLiteral, kPositiveInt32.data())); - EXPECT_EQ(fromLiteral, 42); + EXPECT_TRUE(lexicalCastChecked(fromLiteral, kPositiveInt32Text)); + EXPECT_EQ(fromLiteral, kPositiveInt32); int32_t fromString = 0; - EXPECT_TRUE(lexicalCastChecked(fromString, std::string{kNegativeInt32})); - EXPECT_EQ(fromString, -42); + EXPECT_TRUE(lexicalCastChecked(fromString, std::string{kNegativeInt32Text})); + EXPECT_EQ(fromString, kNegativeInt32); } TEST(LexicalCast, throwing_cast_returns_in_range_values) { - EXPECT_EQ(castThrow(kUnderInt64Max), kUnderInt64Max); - EXPECT_EQ(castThrow(kNearMax32), kNearMax32); - EXPECT_EQ(castThrow(kNearMin32), kNearMin32); - EXPECT_EQ(lexicalCastThrow(kInRangeInt16), -5711); + static_assert(castThrow(kUnderInt64Max) == kUnderInt64Max); + static_assert(castThrow(kNearMax32) == kNearMax32); + static_assert(castThrow(kNearMin32) == kNearMin32); + static_assert(castThrow(kInRangeInt16) == kInRangeInt16); } TEST(LexicalCast, throwing_cast_throws_on_out_of_range) From 9493bbd6158490a67284ce89a62046bcfcbeea25 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 29 Jul 2026 22:47:03 +0100 Subject: [PATCH 8/8] Fix unity --- src/tests/libxrpl/beast/LexicalCast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tests/libxrpl/beast/LexicalCast.cpp b/src/tests/libxrpl/beast/LexicalCast.cpp index c25dbced2e3..d18af4e1cd5 100644 --- a/src/tests/libxrpl/beast/LexicalCast.cpp +++ b/src/tests/libxrpl/beast/LexicalCast.cpp @@ -72,7 +72,7 @@ constexpr auto kNegativeInt32Text = "-42"; constexpr auto kNegativeOne = "-1"; constexpr auto kNegativeZero = "-0"; -constexpr auto kZero = "0"; +constexpr auto kBareZero = "0"; constexpr auto kPositiveZero = "+0"; // Full-width digits one and zero, not ASCII ones. @@ -285,14 +285,14 @@ TEST(LexicalCast, limits_round_trip_through_to_string) TEST(LexicalCast, accepts_signed_zero_in_every_form) { static_assert(parsed(kNegativeZero) == 0); - static_assert(parsed(kZero) == 0); + static_assert(parsed(kBareZero) == 0); static_assert(parsed(kPositiveZero) == 0); } TEST(LexicalCast, rejects_negative_zero_when_unsigned) { static_assert(not parses(kNegativeZero)); - static_assert(parsed(kZero) == 0); + static_assert(parsed(kBareZero) == 0); static_assert(parsed(kPositiveZero) == 0); }