From 61be075ff8fc8f380bb1bf40ed156a925135fa37 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:18:19 +0000 Subject: [PATCH] added few more tests and doc --- src/doctest/MIGRATION.md | 171 +++ .../beast/aged_associative_container.cpp | 1168 +++++++++++++++++ src/doctest/protocol/MultiApiJson.cpp | 989 ++++++++++++++ src/doctest/protocol/SecretKey.cpp | 97 ++ 4 files changed, 2425 insertions(+) create mode 100644 src/doctest/MIGRATION.md create mode 100644 src/doctest/beast/aged_associative_container.cpp create mode 100644 src/doctest/protocol/MultiApiJson.cpp diff --git a/src/doctest/MIGRATION.md b/src/doctest/MIGRATION.md new file mode 100644 index 0000000000..b4943b2d62 --- /dev/null +++ b/src/doctest/MIGRATION.md @@ -0,0 +1,171 @@ +# Doctest Migration Documentation + +This document describes the migration of unit tests from the beast `unit_test` framework to the doctest framework. + +## Overview + +Tests were migrated from `src/test/` (beast unit_test format) to `src/doctest/` (doctest format), following the pattern established in `src/tests/libxrpl/`. + +## Build Configuration + +### CMakeLists.txt Structure + +Created `src/doctest/CMakeLists.txt` with: +- Helper function `xrpl_add_doctest(name)` that creates per-module executables +- Compiler flags: `-m64 -g -std=c++20 -fPIE -Wno-unknown-warning-option -Wall -Wdeprecated -Wno-deprecated-declarations -Wextra -Wno-unused-parameter -Werror -fstack-protector -Wno-sign-compare -Wno-unused-but-set-variable -MD -MT -MF` +- Six module targets: `xrpl.doctest.basics`, `xrpl.doctest.beast`, `xrpl.doctest.core`, `xrpl.doctest.csf`, `xrpl.doctest.nodestore`, `xrpl.doctest.protocol` + +### Module Structure + +Each module has its own `main.cpp` following the pre-migrated test pattern: +``` +src/doctest/ +├── basics/main.cpp +├── beast/main.cpp +├── core/main.cpp +├── csf/main.cpp +├── nodestore/main.cpp +└── protocol/main.cpp +``` + +## Framework Conversion Patterns + +| Beast Unit Test | Doctest Equivalent | +|-----------------|-------------------| +| `#include ` | `#include ` | +| `BEAST_EXPECT(expr)` | `CHECK(expr)` | +| `BEAST_EXPECTS(expr, msg)` | `CHECK_MESSAGE(expr, msg)` | +| `testcase("name")` | `SUBCASE("name")` | +| `class X : public unit_test::suite { ... }` | Free functions with `TEST_CASE` | +| `BEAST_DEFINE_TESTSUITE(Name, Module, Lib)` | `TEST_CASE("Name")` | +| `pass()` / `fail()` | `CHECK(true)` / `CHECK(false)` | + +## Namespace Changes + +- Changed from `namespace ripple` to `namespace xrpl` where applicable +- Header paths changed from `xrpld/` to `xrpl/` (e.g., `xrpld/app/` → `xrpl/protocol/`) + +## Common Migration Issues and Solutions + +### 1. CHECK Macro with Complex Expressions + +**Problem**: Doctest's CHECK macro doesn't support `&&` or `||` in expressions. + +```cpp +// Doesn't work +CHECK(a && b); + +// Solution: Split into separate checks +CHECK(a); +CHECK(b); +``` + +### 2. CHECK Macro with Custom Iterator Comparisons + +**Problem**: CHECK wraps expressions in `Expression_lhs<>` which breaks template argument deduction for custom comparison operators (especially boost::intrusive iterators). + +```cpp +// Doesn't compile +CHECK(iter != container.end()); + +// Solution: Store result in bool first +bool notEnd = (iter != container.end()); +CHECK(notEnd); +``` + +### 3. Constructor Argument Order + +Some container constructors have different argument order than initially assumed: + +```cpp +// Wrong order +Container c(clock, first, last); + +// Correct order (iterators before clock) +Container c(first, last, clock); +``` + +### 4. Return Type Differences + +For map types with `P&&` insert overloads, return types differ: + +```cpp +// Use if constexpr to handle both cases +if constexpr (!IsMulti && IsMap) +{ + auto result = c.insert(c.end(), value); // returns pair + CHECK(result.first != c.end()); +} +else +{ + auto it = c.insert(c.end(), value); // returns iterator + CHECK(it != c.end()); +} +``` + +## Files Migrated + +### basics/ (13 files) +- Buffer.cpp, Expected.cpp, IOUAmount.cpp, KeyCache.cpp, Number.cpp +- StringUtilities.cpp, TaggedCache.cpp, Units.cpp, XRPAmount.cpp +- base58.cpp, base_uint.cpp, hardened_hash.cpp, join.cpp + +### beast/ (11 files) +- CurrentThreadName.cpp, IPEndpoint.cpp, Journal.cpp, LexicalCast.cpp +- PropertyStream.cpp, SemanticVersion.cpp, aged_associative_container.cpp +- basic_seconds_clock.cpp, beast_Zero.cpp, xxhasher.cpp + +### core/ (1 file) +- Workers.cpp + +### csf/ (4 files) +- BasicNetwork.cpp, Digraph.cpp, Histogram.cpp, Scheduler.cpp + +### nodestore/ (1 file) +- varint.cpp + +### protocol/ (14 files) +- ApiVersion.cpp, BuildInfo.cpp, Issue.cpp, MultiApiJson.cpp +- PublicKey.cpp, Quality.cpp, STAccount.cpp, STInteger.cpp +- STNumber.cpp, SecretKey.cpp, Seed.cpp, SeqProxy.cpp +- Serializer.cpp, TER.cpp + +## Test Results Summary + +| Module | Test Cases | Assertions | +|--------|------------|------------| +| basics | 61 | 2,638,582 | +| beast | 48 | 162,715 | +| core | 6 | 66 | +| csf | 8 | 101 | +| nodestore | 1 | 68 | +| protocol | 73 | 20,372 | +| **Total** | **197** | **2,821,904** | + +## Running Tests + +```bash +# Build all doctest targets +cd .build +cmake --build . --target xrpl.doctests + +# Run individual module +./src/doctest/xrpl.doctest.basics +./src/doctest/xrpl.doctest.beast +./src/doctest/xrpl.doctest.core +./src/doctest/xrpl.doctest.csf +./src/doctest/xrpl.doctest.nodestore +./src/doctest/xrpl.doctest.protocol + +# Run all tests +for test in src/doctest/xrpl.doctest.*; do ./$test; done +``` + +## Tests Not Migrated + +Some tests were not migrated due to dependencies: +- **Manual tests** requiring user interaction (e.g., `DetectCrash_test`) +- **Tests using xrpld infrastructure** (`test/jtx.h`, `unit_test/SuiteJournal.h`) +- **Complex async tests** using boost coroutines +- **Tests with FileDirGuard** or other test-specific utilities + diff --git a/src/doctest/beast/aged_associative_container.cpp b/src/doctest/beast/aged_associative_container.cpp new file mode 100644 index 0000000000..9c23d1565a --- /dev/null +++ b/src/doctest/beast/aged_associative_container.cpp @@ -0,0 +1,1168 @@ +// Migrated from src/test/beast/aged_associative_container_test.cpp to doctest + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifndef BEAST_AGED_UNORDERED_NO_ALLOC_DEFAULTCTOR +#ifdef _MSC_VER +#define BEAST_AGED_UNORDERED_NO_ALLOC_DEFAULTCTOR 0 +#else +#define BEAST_AGED_UNORDERED_NO_ALLOC_DEFAULTCTOR 1 +#endif +#endif + +#ifndef BEAST_CONTAINER_EXTRACT_NOREF +#ifdef _MSC_VER +#define BEAST_CONTAINER_EXTRACT_NOREF 1 +#else +#define BEAST_CONTAINER_EXTRACT_NOREF 1 +#endif +#endif + +namespace beast { +namespace { + +//------------------------------------------------------------------------------ +// Helper types + +template +struct CompT +{ + explicit CompT(int) + { + } + + CompT(CompT const&) + { + } + + bool + operator()(T const& lhs, T const& rhs) const + { + return m_less(lhs, rhs); + } + +private: + CompT() = delete; + std::less m_less; +}; + +template +class HashT +{ +public: + explicit HashT(int) + { + } + + std::size_t + operator()(T const& t) const + { + return m_hash(t); + } + +private: + HashT() = delete; + std::hash m_hash; +}; + +template +struct EqualT +{ +public: + explicit EqualT(int) + { + } + + bool + operator()(T const& lhs, T const& rhs) const + { + return m_eq(lhs, rhs); + } + +private: + EqualT() = delete; + std::equal_to m_eq; +}; + +template +struct AllocT +{ + using value_type = T; + + template + struct rebind + { + using other = AllocT; + }; + + explicit AllocT(int) + { + } + + AllocT(AllocT const&) = default; + + template + AllocT(AllocT const&) + { + } + + template + bool + operator==(AllocT const&) const + { + return true; + } + + template + bool + operator!=(AllocT const& o) const + { + return !(*this == o); + } + + T* + allocate(std::size_t n, T const* = 0) + { + return static_cast(::operator new(n * sizeof(T))); + } + + void + deallocate(T* p, std::size_t) + { + ::operator delete(p); + } + +#if !BEAST_AGED_UNORDERED_NO_ALLOC_DEFAULTCTOR + AllocT() + { + } +#else +private: + AllocT() = delete; +#endif +}; + +//------------------------------------------------------------------------------ +// Trait hierarchy + +// ordered +template +class MaybeUnordered : public Base +{ +public: + using Comp = std::less; + using MyComp = CompT; + +protected: + static std::string + name_ordered_part() + { + return ""; + } +}; + +// unordered +template +class MaybeUnordered : public Base +{ +public: + using Hash = std::hash; + using Equal = std::equal_to; + using MyHash = HashT; + using MyEqual = EqualT; + +protected: + static std::string + name_ordered_part() + { + return "unordered_"; + } +}; + +// unique +template +class MaybeMulti : public Base +{ +protected: + static std::string + name_multi_part() + { + return ""; + } +}; + +// multi +template +class MaybeMulti : public Base +{ +protected: + static std::string + name_multi_part() + { + return "multi"; + } +}; + +// set +template +class MaybeMap : public Base +{ +public: + using T = void; + using Value = typename Base::Key; + using Values = std::vector; + + static typename Base::Key const& + extract(Value const& value) + { + return value; + } + + static Values + values() + { + Values v{ + "apple", + "banana", + "cherry", + "grape", + "orange", + }; + return v; + } + +protected: + static std::string + name_map_part() + { + return "set"; + } +}; + +// map +template +class MaybeMap : public Base +{ +public: + using T = int; + using Value = std::pair; + using Values = std::vector; + + static typename Base::Key const& + extract(Value const& value) + { + return value.first; + } + + static Values + values() + { + Values v{ + std::make_pair("apple", 1), + std::make_pair("banana", 2), + std::make_pair("cherry", 3), + std::make_pair("grape", 4), + std::make_pair("orange", 5)}; + return v; + } + +protected: + static std::string + name_map_part() + { + return "map"; + } +}; + +//------------------------------------------------------------------------------ +// Container types + +// ordered +template +struct ContType +{ + template < + class Compare = std::less, + class Allocator = std::allocator> + using Cont = detail::aged_ordered_container< + Base::is_multi::value, + Base::is_map::value, + typename Base::Key, + typename Base::T, + typename Base::Clock, + Compare, + Allocator>; +}; + +// unordered +template +struct ContType +{ + template < + class Hash = std::hash, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> + using Cont = detail::aged_unordered_container< + Base::is_multi::value, + Base::is_map::value, + typename Base::Key, + typename Base::T, + typename Base::Clock, + Hash, + KeyEqual, + Allocator>; +}; + +//------------------------------------------------------------------------------ +// Test traits + +struct TestTraitsBase +{ + using Key = std::string; + using Clock = std::chrono::steady_clock; + using ManualClock = manual_clock; +}; + +template +struct TestTraitsHelper + : MaybeUnordered< + MaybeMulti, IsMulti>, + IsUnordered> +{ +private: + using Base = MaybeUnordered< + MaybeMulti, IsMulti>, + IsUnordered>; + +public: + using typename Base::Key; + + using is_unordered = std::integral_constant; + using is_multi = std::integral_constant; + using is_map = std::integral_constant; + + using Alloc = std::allocator; + using MyAlloc = AllocT; + + static std::string + name() + { + return std::string("aged_") + Base::name_ordered_part() + + Base::name_multi_part() + Base::name_map_part(); + } +}; + +template +struct TestTraits : TestTraitsHelper, + ContType> +{ +}; + +template +std::string +name(Cont const&) +{ + return TestTraits::name(); +} + +template +struct equal_value +{ + bool + operator()( + typename Traits::Value const& lhs, + typename Traits::Value const& rhs) + { + return Traits::extract(lhs) == Traits::extract(rhs); + } +}; + +template +std::vector +make_list(Cont const& c) +{ + return std::vector(c.begin(), c.end()); +} + +//------------------------------------------------------------------------------ +// Check contents helpers + +// Check contents via at() and operator[] +// map, unordered_map +template +typename std::enable_if< + Container::is_map::value && !Container::is_multi::value>::type +checkMapContents(Container& c, Values const& v) +{ + if (v.empty()) + { + CHECK(c.empty()); + CHECK(c.size() == 0); + return; + } + + try + { + // Make sure no exception is thrown + for (auto const& e : v) + c.at(e.first); + for (auto const& e : v) + CHECK(c.operator[](e.first) == e.second); + } + catch (std::out_of_range const&) + { + CHECK(false); // FAIL: caught exception + } +} + +template +typename std::enable_if< + !(Container::is_map::value && !Container::is_multi::value)>::type +checkMapContents(Container, Values const&) +{ +} + +// unordered +template +typename std::enable_if< + std::remove_reference::type::is_unordered::value>::type +checkUnorderedContentsRefRef(C&& c, Values const& v) +{ + using Cont = typename std::remove_reference::type; + using Traits = TestTraits< + Cont::is_unordered::value, + Cont::is_multi::value, + Cont::is_map::value>; + using size_type = typename Cont::size_type; + auto const hash(c.hash_function()); + auto const key_eq(c.key_eq()); + for (size_type i(0); i < c.bucket_count(); ++i) + { + auto const last(c.end(i)); + for (auto iter(c.begin(i)); iter != last; ++iter) + { + auto const match(std::find_if( + v.begin(), + v.end(), + [iter](typename Values::value_type const& e) { + return Traits::extract(*iter) == Traits::extract(e); + })); + bool found = (match != v.end()); + CHECK(found); + bool keysEqual = + key_eq(Traits::extract(*iter), Traits::extract(*match)); + CHECK(keysEqual); + bool hashesEqual = + (hash(Traits::extract(*iter)) == hash(Traits::extract(*match))); + CHECK(hashesEqual); + } + } +} + +template +typename std::enable_if< + !std::remove_reference::type::is_unordered::value>::type +checkUnorderedContentsRefRef(C&&, Values const&) +{ +} + +template +void +checkContentsRefRef(C&& c, Values const& v) +{ + using Cont = typename std::remove_reference::type; + using size_type = typename Cont::size_type; + + CHECK(c.size() == v.size()); + CHECK(size_type(std::distance(c.begin(), c.end())) == v.size()); + CHECK(size_type(std::distance(c.cbegin(), c.cend())) == v.size()); + CHECK( + size_type(std::distance( + c.chronological.begin(), c.chronological.end())) == v.size()); + CHECK( + size_type(std::distance( + c.chronological.cbegin(), c.chronological.cend())) == v.size()); + CHECK( + size_type(std::distance( + c.chronological.rbegin(), c.chronological.rend())) == v.size()); + CHECK( + size_type(std::distance( + c.chronological.crbegin(), c.chronological.crend())) == v.size()); + + checkUnorderedContentsRefRef(c, v); +} + +template +void +checkContents(Cont& c, Values const& v) +{ + checkContentsRefRef(c, v); + checkContentsRefRef(const_cast(c), v); + checkMapContents(c, v); +} + +template +void +checkContents(Cont& c) +{ + using Traits = TestTraits< + Cont::is_unordered::value, + Cont::is_multi::value, + Cont::is_map::value>; + using Values = typename Traits::Values; + checkContents(c, Values()); +} + +//------------------------------------------------------------------------------ +// Test Construction - Ordered containers + +template +typename std::enable_if::type +testConstructEmpty() +{ + using Traits = TestTraits; + using Comp = typename Traits::Comp; + using Alloc = typename Traits::Alloc; + using MyComp = typename Traits::MyComp; + using MyAlloc = typename Traits::MyAlloc; + typename Traits::ManualClock clock; + + SUBCASE("empty") + { + { + typename Traits::template Cont c(clock); + checkContents(c); + } + + { + typename Traits::template Cont c(clock, MyComp(1)); + checkContents(c); + } + + { + typename Traits::template Cont c(clock, MyAlloc(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, MyComp(1), MyAlloc(1)); + checkContents(c); + } + } +} + +// Test Construction - Unordered containers +template +typename std::enable_if::type +testConstructEmpty() +{ + using Traits = TestTraits; + using Hash = typename Traits::Hash; + using Equal = typename Traits::Equal; + using Alloc = typename Traits::Alloc; + using MyHash = typename Traits::MyHash; + using MyEqual = typename Traits::MyEqual; + using MyAlloc = typename Traits::MyAlloc; + typename Traits::ManualClock clock; + + SUBCASE("empty") + { + { + typename Traits::template Cont c(clock); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, MyHash(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, Hash(), MyEqual(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, MyAlloc(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, MyHash(1), MyEqual(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, MyHash(1), Equal(), MyAlloc(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, Hash(), MyEqual(1), MyAlloc(1)); + checkContents(c); + } + + { + typename Traits::template Cont c( + clock, MyHash(1), MyEqual(1), MyAlloc(1)); + checkContents(c); + } + } +} + +//------------------------------------------------------------------------------ +// Test Construction with range - Ordered containers + +template +typename std::enable_if::type +testConstructRange() +{ + using Traits = TestTraits; + using Comp = typename Traits::Comp; + using Alloc = typename Traits::Alloc; + using MyComp = typename Traits::MyComp; + using MyAlloc = typename Traits::MyAlloc; + typename Traits::ManualClock clock; + + SUBCASE("range") + { + auto const v(Traits::values()); + { + typename Traits::template Cont c( + v.begin(), v.end(), clock); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyComp(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyAlloc(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyComp(1), MyAlloc(1)); + checkContents(c, v); + } + } +} + +// Test Construction with range - Unordered containers +template +typename std::enable_if::type +testConstructRange() +{ + using Traits = TestTraits; + using Hash = typename Traits::Hash; + using Equal = typename Traits::Equal; + using Alloc = typename Traits::Alloc; + using MyHash = typename Traits::MyHash; + using MyEqual = typename Traits::MyEqual; + using MyAlloc = typename Traits::MyAlloc; + typename Traits::ManualClock clock; + + SUBCASE("range") + { + auto const v(Traits::values()); + { + typename Traits::template Cont c( + v.begin(), v.end(), clock); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyHash(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyEqual(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyAlloc(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyHash(1), MyEqual(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyHash(1), MyAlloc(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyEqual(1), MyAlloc(1)); + checkContents(c, v); + } + + { + typename Traits::template Cont c( + v.begin(), v.end(), clock, MyHash(1), MyEqual(1), MyAlloc(1)); + checkContents(c, v); + } + } +} + +//------------------------------------------------------------------------------ +// Test Iterator + +template +void +testIterator() +{ + using Traits = TestTraits; + typename Traits::ManualClock clock; + + SUBCASE("iterator") + { + using Cont = typename Traits::template Cont<>; + auto const v(Traits::values()); + Cont c(v.cbegin(), v.cend(), clock); + Cont const& cc(c); + CHECK(!c.empty()); + CHECK(c.size() == v.size()); + + { + auto i = c.begin(); + bool eq1 = (i == c.begin()); + CHECK(eq1); + bool ne1 = (i != c.end()); + CHECK(ne1); + ++i; + bool ne2 = (i != c.begin()); + CHECK(ne2); + } + + { + auto i = cc.begin(); + bool eq1 = (i == cc.begin()); + CHECK(eq1); + bool ne1 = (i != cc.end()); + CHECK(ne1); + ++i; + bool ne2 = (i != cc.begin()); + CHECK(ne2); + } + + { + auto i = c.cbegin(); + bool eq1 = (i == c.cbegin()); + CHECK(eq1); + bool ne1 = (i != c.cend()); + CHECK(ne1); + ++i; + bool ne2 = (i != c.cbegin()); + CHECK(ne2); + } + + { + auto i = cc.cbegin(); + bool eq1 = (i == cc.cbegin()); + CHECK(eq1); + bool ne1 = (i != cc.cend()); + CHECK(ne1); + ++i; + bool ne2 = (i != cc.cbegin()); + CHECK(ne2); + } + } +} + +// Unordered containers don't have reverse iterators +template +typename std::enable_if::type +testReverseIterator() +{ + using Traits = TestTraits; + typename Traits::ManualClock clock; + + SUBCASE("reverse_iterator") + { + using Cont = typename Traits::template Cont<>; + auto const v(Traits::values()); + Cont c(v.cbegin(), v.cend(), clock); + Cont const& cc(c); + + { + auto i = c.rbegin(); + bool eq1 = (i == c.rbegin()); + CHECK(eq1); + bool ne1 = (i != c.rend()); + CHECK(ne1); + ++i; + bool ne2 = (i != c.rbegin()); + CHECK(ne2); + } + + { + auto i = cc.rbegin(); + bool eq1 = (i == cc.rbegin()); + CHECK(eq1); + bool ne1 = (i != cc.rend()); + CHECK(ne1); + ++i; + bool ne2 = (i != cc.rbegin()); + CHECK(ne2); + } + + { + auto i = c.crbegin(); + bool eq1 = (i == c.crbegin()); + CHECK(eq1); + bool ne1 = (i != c.crend()); + CHECK(ne1); + ++i; + bool ne2 = (i != c.crbegin()); + CHECK(ne2); + } + + { + auto i = cc.crbegin(); + bool eq1 = (i == cc.crbegin()); + CHECK(eq1); + bool ne1 = (i != cc.crend()); + CHECK(ne1); + ++i; + bool ne2 = (i != cc.crbegin()); + CHECK(ne2); + } + } +} + +template +typename std::enable_if::type +testReverseIterator() +{ +} + +//------------------------------------------------------------------------------ +// Modifier test helpers + +template +void +checkInsertCopy(Container& c, Values const& v) +{ + for (auto const& e : v) + { + auto result = c.insert(e); + if constexpr (Container::is_multi::value) + { + bool isValid = (result != c.end()); + CHECK(isValid); + } + else + { + CHECK(result.second); + } + } +} + +template +void +checkInsertMove(Container& c, Values const& v) +{ + for (auto e : v) + { + auto result = c.insert(std::move(e)); + if constexpr (Container::is_multi::value) + { + bool isValid = (result != c.end()); + CHECK(isValid); + } + else + { + CHECK(result.second); + } + } +} + +template +void +checkInsertHintCopy(Container& c, Values const& v) +{ + for (auto const& e : v) + { + auto result = c.insert(c.cend(), e); + // For map types with P&& overload, result may be pair + // For other types, result is iterator + if constexpr (std::is_same_v< + decltype(result), + std::pair>) + { + bool isValid = (result.first != c.end()); + CHECK(isValid); + } + else + { + bool isValid = (result != c.end()); + CHECK(isValid); + } + } +} + +template +void +checkInsertHintMove(Container& c, Values const& v) +{ + for (auto e : v) + { + auto result = c.insert(c.cend(), std::move(e)); + // For map types with P&& overload, result may be pair + // For other types, result is iterator + if constexpr (std::is_same_v< + decltype(result), + std::pair>) + { + bool isValid = (result.first != c.end()); + CHECK(isValid); + } + else + { + bool isValid = (result != c.end()); + CHECK(isValid); + } + } +} + +template +void +testModifiers() +{ + using Traits = TestTraits; + typename Traits::ManualClock clock; + + SUBCASE("insert copy") + { + auto const v(Traits::values()); + typename Traits::template Cont<> c(clock); + checkInsertCopy(c, v); + checkContents(c, v); + } + + SUBCASE("insert move") + { + auto const v(Traits::values()); + typename Traits::template Cont<> c(clock); + checkInsertMove(c, v); + checkContents(c, v); + } + + SUBCASE("insert hint copy") + { + auto const v(Traits::values()); + typename Traits::template Cont<> c(clock); + checkInsertHintCopy(c, v); + checkContents(c, v); + } + + SUBCASE("insert hint move") + { + auto const v(Traits::values()); + typename Traits::template Cont<> c(clock); + checkInsertHintMove(c, v); + checkContents(c, v); + } +} + +//------------------------------------------------------------------------------ +// Chronological tests + +template +void +testChronological() +{ + using Traits = TestTraits; + typename Traits::ManualClock clock; + + SUBCASE("chronological") + { + using Cont = typename Traits::template Cont<>; + auto const v(Traits::values()); + Cont c(v.cbegin(), v.cend(), clock); + Cont const& cc(c); + + // Check chronological iterators + CHECK(!c.empty()); + bool ne1 = (c.chronological.begin() != c.chronological.end()); + CHECK(ne1); + bool ne2 = (cc.chronological.begin() != cc.chronological.end()); + CHECK(ne2); + bool ne3 = (c.chronological.cbegin() != c.chronological.cend()); + CHECK(ne3); + bool ne4 = (c.chronological.rbegin() != c.chronological.rend()); + CHECK(ne4); + bool ne5 = (cc.chronological.rbegin() != cc.chronological.rend()); + CHECK(ne5); + bool ne6 = (c.chronological.crbegin() != c.chronological.crend()); + CHECK(ne6); + + // Check touch updates + auto const before = c.clock().now(); + clock.advance(std::chrono::seconds(1)); + auto iter = c.begin(); + c.touch(iter); + bool isAfter = (iter.when() > before); + CHECK(isAfter); + } +} + +//------------------------------------------------------------------------------ +// Main test function for each container type + +template +void +testMaybeUnorderedMultiMap() +{ + testConstructEmpty(); + testConstructRange(); + testIterator(); + testReverseIterator(); + testModifiers(); + testChronological(); +} + +} // namespace + +//------------------------------------------------------------------------------ +// Static assertions (compile-time checks) + +using Key = std::string; +using T = int; + +static_assert( + std::is_same< + aged_set, + detail::aged_ordered_container>::value, + "bad alias: aged_set"); + +static_assert( + std::is_same< + aged_multiset, + detail::aged_ordered_container>::value, + "bad alias: aged_multiset"); + +static_assert( + std::is_same< + aged_map, + detail::aged_ordered_container>::value, + "bad alias: aged_map"); + +static_assert( + std::is_same< + aged_multimap, + detail::aged_ordered_container>::value, + "bad alias: aged_multimap"); + +static_assert( + std::is_same< + aged_unordered_set, + detail::aged_unordered_container>::value, + "bad alias: aged_unordered_set"); + +static_assert( + std::is_same< + aged_unordered_multiset, + detail::aged_unordered_container>::value, + "bad alias: aged_unordered_multiset"); + +static_assert( + std::is_same< + aged_unordered_map, + detail::aged_unordered_container>::value, + "bad alias: aged_unordered_map"); + +static_assert( + std::is_same< + aged_unordered_multimap, + detail::aged_unordered_container>::value, + "bad alias: aged_unordered_multimap"); + +} // namespace beast + +//------------------------------------------------------------------------------ +// TEST_CASE definitions + +TEST_SUITE_BEGIN("beast"); + +TEST_CASE("aged_set") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_map") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_multiset") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_multimap") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_unordered_set") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_unordered_map") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_unordered_multiset") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_CASE("aged_unordered_multimap") +{ + beast::testMaybeUnorderedMultiMap(); +} + +TEST_SUITE_END(); diff --git a/src/doctest/protocol/MultiApiJson.cpp b/src/doctest/protocol/MultiApiJson.cpp new file mode 100644 index 0000000000..1c9c2f0798 --- /dev/null +++ b/src/doctest/protocol/MultiApiJson.cpp @@ -0,0 +1,989 @@ +#include + +#include + +#include +#include +#include +#include +#include + +using namespace xrpl; + +namespace { + +// This needs to be in a namespace because of deduction guide +template +struct Overload : Ts... +{ + using Ts::operator()...; +}; +template +Overload(Ts...) -> Overload; + +auto +makeJson(char const* key, int val) +{ + Json::Value obj1(Json::objectValue); + obj1[key] = val; + return obj1; +} + +} // namespace + +TEST_SUITE_BEGIN("MultiApiJson"); + +TEST_CASE("forApiVersions, forAllApiVersions") +{ + using xrpl::detail::MultiApiJson; + + Json::Value const obj1 = makeJson("value", 1); + Json::Value const obj2 = makeJson("value", 2); + Json::Value const obj3 = makeJson("value", 3); + Json::Value const jsonNull{}; + + MultiApiJson<1, 3> subject{}; + static_assert(sizeof(subject) == sizeof(subject.val)); + static_assert(subject.size == subject.val.size()); + static_assert( + std::is_same_v>); + + CHECK(subject.val.size() == 3); + CHECK( + (subject.val == + std::array{jsonNull, jsonNull, jsonNull})); + + subject.val[0] = obj1; + subject.val[1] = obj2; + + // Some static data for test inputs + static int const primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, + 29, 31, 37, 41, 43, 47, 53, 59, 61, + 67, 71, 73, 79, 83, 89, 97}; + static_assert(std::size(primes) > RPC::apiMaximumValidVersion); + + MultiApiJson<1, 3> s1{}; + static_assert( + s1.size == + RPC::apiMaximumValidVersion + 1 - RPC::apiMinimumSupportedVersion); + + int productAllVersions = 1; + for (unsigned i = RPC::apiMinimumSupportedVersion; + i <= RPC::apiMaximumValidVersion; + ++i) + { + auto const index = i - RPC::apiMinimumSupportedVersion; + CHECK(index == s1.index(i)); + CHECK(s1.valid(i)); + s1.val[index] = makeJson("value", primes[i]); + productAllVersions *= primes[i]; + } + CHECK(!s1.valid(0)); + CHECK(!s1.valid(RPC::apiMaximumValidVersion + 1)); + CHECK(!s1.valid(std::numeric_limits< + decltype(RPC::apiMaximumValidVersion.value)>::max())); + + int result = 1; + static_assert( + RPC::apiMinimumSupportedVersion + 1 <= RPC::apiMaximumValidVersion); + forApiVersions< + RPC::apiMinimumSupportedVersion, + RPC::apiMinimumSupportedVersion + 1>( + std::as_const(s1).visit(), + [](Json::Value const& json, unsigned int version, int* result) { + CHECK(version >= RPC::apiMinimumSupportedVersion); + CHECK(version <= RPC::apiMinimumSupportedVersion + 1); + if (json.isMember("value")) + { + *result *= json["value"].asInt(); + } + }, + &result); + CHECK( + result == + primes[RPC::apiMinimumSupportedVersion] * + primes[RPC::apiMinimumSupportedVersion + 1]); + + // Check all the values with mutable data + forAllApiVersions(s1.visit(), [&s1](Json::Value& json, auto version) { + CHECK(s1.val[s1.index(version)] == json); + if (json.isMember("value")) + { + CHECK(json["value"].asInt() == primes[version]); + } + }); + + result = 1; + forAllApiVersions( + std::as_const(s1).visit(), + [](Json::Value const& json, unsigned int version, int* result) { + CHECK(version >= RPC::apiMinimumSupportedVersion); + CHECK(version <= RPC::apiMaximumValidVersion); + if (json.isMember("value")) + { + *result *= json["value"].asInt(); + } + }, + &result); + + CHECK(result == productAllVersions); + + // Several overloads we want to fail + static_assert([](auto&& v) { + return !requires { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value&, auto) {}); // missing const + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value&) {}); // missing const + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires { + forAllApiVersions( + std::forward(v).visit(), // + []() {}); // missing parameters + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto) {}, + 1); // missing parameters + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto, auto) {}, + 1); // missing parameters + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto, auto, char const*) {}, + 1); // parameter type mismatch + }; + }(std::as_const(s1))); + + // Sanity checks + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto) {}); + }; + }(s1)); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto...) {}); + }; + }(s1)); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value const&, auto...) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](Json::Value&, auto, auto, auto...) {}, + 0, + ""); + }; + }(s1)); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + []( + Json::Value const&, + std::integral_constant, + int, + char const*) {}, + 0, + ""); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto...) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires { + forAllApiVersions( + std::forward(v).visit(), // + [](auto...) {}); + }; + }(std::move(std::as_const(s1)))); +} + +TEST_CASE("default copy construction / assignment") +{ + using xrpl::detail::MultiApiJson; + + Json::Value const obj1 = makeJson("value", 1); + Json::Value const obj2 = makeJson("value", 2); + Json::Value const jsonNull{}; + + MultiApiJson<1, 3> subject{}; + subject.val[0] = obj1; + subject.val[1] = obj2; + + MultiApiJson<1, 3> x{subject}; + + CHECK(x.val.size() == subject.val.size()); + CHECK(x.val[0] == subject.val[0]); + CHECK(x.val[1] == subject.val[1]); + CHECK(x.val[2] == subject.val[2]); + CHECK(x.val == subject.val); + CHECK(&x.val[0] != &subject.val[0]); + CHECK(&x.val[1] != &subject.val[1]); + CHECK(&x.val[2] != &subject.val[2]); + + MultiApiJson<1, 3> y; + CHECK((y.val == std::array{})); + y = subject; + CHECK(y.val == subject.val); + CHECK(&y.val[0] != &subject.val[0]); + CHECK(&y.val[1] != &subject.val[1]); + CHECK(&y.val[2] != &subject.val[2]); + + y = std::move(x); + CHECK(y.val == subject.val); + CHECK(&y.val[0] != &subject.val[0]); + CHECK(&y.val[1] != &subject.val[1]); + CHECK(&y.val[2] != &subject.val[2]); +} + +TEST_CASE("set") +{ + using xrpl::detail::MultiApiJson; + + auto x = MultiApiJson<1, 2>{Json::objectValue}; + x.set("name1", 42); + CHECK(x.val[0].isMember("name1")); + CHECK(x.val[1].isMember("name1")); + CHECK(x.val[0]["name1"].isInt()); + CHECK(x.val[1]["name1"].isInt()); + CHECK(x.val[0]["name1"].asInt() == 42); + CHECK(x.val[1]["name1"].asInt() == 42); + + x.set("name2", "bar"); + CHECK(x.val[0].isMember("name2")); + CHECK(x.val[1].isMember("name2")); + CHECK(x.val[0]["name2"].isString()); + CHECK(x.val[1]["name2"].isString()); + CHECK(x.val[0]["name2"].asString() == "bar"); + CHECK(x.val[1]["name2"].asString() == "bar"); + + // Tests of requires clause - these are expected to match + static_assert([](auto&& v) { + return requires { v.set("name", Json::nullValue); }; + }(x)); + static_assert( + [](auto&& v) { return requires { v.set("name", "value"); }; }(x)); + static_assert( + [](auto&& v) { return requires { v.set("name", true); }; }(x)); + static_assert([](auto&& v) { return requires { v.set("name", 42); }; }(x)); + + // Tests of requires clause - these are expected NOT to match + struct foo_t final + { + }; + static_assert( + [](auto&& v) { return !requires { v.set("name", foo_t{}); }; }(x)); + static_assert( + [](auto&& v) { return !requires { v.set("name", std::nullopt); }; }(x)); +} + +TEST_CASE("isMember") +{ + using xrpl::detail::MultiApiJson; + + Json::Value const obj1 = makeJson("value", 1); + Json::Value const obj2 = makeJson("value", 2); + + MultiApiJson<1, 3> subject{}; + subject.val[0] = obj1; + subject.val[1] = obj2; + + // Well defined behaviour even if we have different types of members + CHECK(subject.isMember("foo") == decltype(subject)::none); + + { + // All variants have element "One", none have element "Two" + MultiApiJson<1, 2> s1{}; + s1.val[0] = makeJson("One", 12); + s1.val[1] = makeJson("One", 42); + CHECK(s1.isMember("One") == decltype(s1)::all); + CHECK(s1.isMember("Two") == decltype(s1)::none); + } + + { + // Some variants have element "One" and some have "Two" + MultiApiJson<1, 2> s2{}; + s2.val[0] = makeJson("One", 12); + s2.val[1] = makeJson("Two", 42); + CHECK(s2.isMember("One") == decltype(s2)::some); + CHECK(s2.isMember("Two") == decltype(s2)::some); + } + + { + // Not all variants have element "One", because last one is null + MultiApiJson<1, 3> s3{}; + s3.val[0] = makeJson("One", 12); + s3.val[1] = makeJson("One", 42); + CHECK(s3.isMember("One") == decltype(s3)::some); + CHECK(s3.isMember("Two") == decltype(s3)::none); + } +} + +TEST_CASE("visitor") +{ + using xrpl::detail::MultiApiJson; + + MultiApiJson<1, 3> s1{}; + s1.val[0] = makeJson("value", 2); + s1.val[1] = makeJson("value", 3); + s1.val[2] = makeJson("value", 5); + + CHECK(not s1.valid(0)); + CHECK(s1.index(0) == 0); + + CHECK(s1.valid(1)); + CHECK(s1.index(1) == 0); + + CHECK(not s1.valid(4)); + + // Test different overloads + static_assert([](auto&& v) { + return requires { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value&, std::integral_constant) {}); + }; + }(s1)); + CHECK( + s1.visitor( + s1, + std::integral_constant{}, + Overload{ + [](Json::Value& v, std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires { + v.visitor( + v, std::integral_constant{}, [](Json::Value&) {}); + }; + }(s1)); + CHECK( + s1.visitor( + s1, + std::integral_constant{}, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value const&, std::integral_constant) {}); + }; + }(std::as_const(s1))); + CHECK( + s1.visitor( + std::as_const(s1), + std::integral_constant{}, + Overload{ + [](Json::Value const& v, std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires { + v.visitor( + v, + std::integral_constant{}, + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + CHECK( + s1.visitor( + std::as_const(s1), + std::integral_constant{}, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](Json::Value&, unsigned) {}); }; + }(s1)); + CHECK( + s1.visitor( + s1, // + 3u, + Overload{ + [](Json::Value& v, unsigned) { return v["value"].asInt(); }, + [](Json::Value const&, unsigned) { return 0; }, + [](auto, auto) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](Json::Value&) {}); }; + }(s1)); + CHECK( + s1.visitor( + s1, // + 3, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires { + v.visitor(v, 1, [](Json::Value const&, unsigned) {}); + }; + }(std::as_const(s1))); + CHECK( + s1.visitor( + std::as_const(s1), // + 2u, + Overload{ + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](Json::Value const&) {}); }; + }(std::as_const(s1))); + CHECK( + s1.visitor( + std::as_const(s1), // + 2, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + // Test type conversions + CHECK( + s1.visitor( + s1, + std::integral_constant{}, // to unsigned + [](Json::Value& v, unsigned) { return v["value"].asInt(); }) == 2); + CHECK( + s1.visitor( + std::as_const(s1), + std::integral_constant{}, // to unsigned + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }) == 3); + CHECK( + s1.visitor( + s1, // to const + std::integral_constant{}, + [](Json::Value const& v, auto) { return v["value"].asInt(); }) == + 5); + CHECK( + s1.visitor( + s1, // to const + std::integral_constant{}, + [](Json::Value const& v) { return v["value"].asInt(); }) == 5); + CHECK( + s1.visitor( + s1, + 3, // to long + [](Json::Value& v, long) { return v["value"].asInt(); }) == 5); + CHECK( + s1.visitor( + std::as_const(s1), + 1, // to long + [](Json::Value const& v, long) { return v["value"].asInt(); }) == + 2); + CHECK( + s1.visitor( + s1, // to const + 2, + [](Json::Value const& v, auto) { return v["value"].asInt(); }) == + 3); + CHECK( + s1.visitor( + s1, // type deduction + 2, + [](auto& v, auto) { return v["value"].asInt(); }) == 3); + CHECK( + s1.visitor( + s1, // to const, type deduction + 2, + [](auto const& v, auto) { return v["value"].asInt(); }) == 3); + CHECK( + s1.visitor( + s1, // type deduction + 2, + [](auto& v) { return v["value"].asInt(); }) == 3); + CHECK( + s1.visitor( + s1, // to const, type deduction + 2, + [](auto const& v) { return v["value"].asInt(); }) == 3); + + // Test passing of additional arguments + CHECK( + s1.visitor( + s1, + std::integral_constant{}, + [](Json::Value& v, auto ver, auto a1, auto a2) { + return ver * a1 * a2 * v["value"].asInt(); + }, + 5, + 7) == 2 * 5 * 7 * 3); + CHECK( + s1.visitor( + s1, + std::integral_constant{}, + [](Json::Value& v, auto ver, auto... args) { + return ver * (1 * ... * args) * v["value"].asInt(); + }, + 5, + 7) == 2 * 5 * 7 * 3); + + // Several overloads we want to fail + static_assert([](auto&& v) { + return !requires { + v.visitor( + v, + 1, // + [](Json::Value&, auto) {}); // missing const + }; + }(std::as_const(s1))); + + static_assert([](auto&& v) { + return !requires { + v.visitor( + std::move(v), // cannot bind rvalue + 1, + [](Json::Value&, auto) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return !requires { + v.visitor( + v, + 1, // + []() {}); // missing parameter + }; + }(s1)); + + static_assert([](auto&& v) { + return !requires { + v.visitor( + v, + 1, // + [](Json::Value&, int, int) {}); // too many parameters + }; + }(s1)); + + // Want these to be unambiguous + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](auto) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](Json::Value&) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](Json::Value&, auto...) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](Json::Value const&) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { + v.visitor(v, 1, [](Json::Value const&, auto...) {}); + }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](auto...) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](auto, auto...) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](auto, auto, auto...) {}); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { v.visitor(v, 1, [](auto, auto, auto...) {}, ""); }; + }(s1)); + + static_assert([](auto&& v) { + return requires { + v.visitor(v, 1, [](auto, auto, auto, auto...) {}, ""); + }; + }(s1)); +} + +TEST_CASE("visit") +{ + using xrpl::detail::MultiApiJson; + + MultiApiJson<1, 3> s1{}; + s1.val[0] = makeJson("value", 2); + s1.val[1] = makeJson("value", 3); + s1.val[2] = makeJson("value", 5); + + // Test different overloads + static_assert([](auto&& v) { + return requires { + v.visit( + std::integral_constant{}, + [](Json::Value&, std::integral_constant) {}); + }; + }(s1)); + CHECK( + s1.visit( + std::integral_constant{}, + Overload{ + [](Json::Value& v, std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 2); + static_assert([](auto&& v) { + return requires { + v.visit()( + std::integral_constant{}, + [](Json::Value&, std::integral_constant) {}); + }; + }(s1)); + CHECK( + s1.visit()( + std::integral_constant{}, + Overload{ + [](Json::Value& v, std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires { + v.visit(std::integral_constant{}, [](Json::Value&) {}); + }; + }(s1)); + CHECK( + s1.visit( + std::integral_constant{}, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 2); + static_assert([](auto&& v) { + return requires { + v.visit()( + std::integral_constant{}, [](Json::Value&) {}); + }; + }(s1)); + CHECK( + s1.visit()( + std::integral_constant{}, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 2); + + static_assert([](auto&& v) { + return requires { + v.visit( + std::integral_constant{}, + [](Json::Value const&, std::integral_constant) {}); + }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit( + std::integral_constant{}, + Overload{ + [](Json::Value const& v, std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires { + v.visit()( + std::integral_constant{}, + [](Json::Value const&, std::integral_constant) {}); + }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit()( + std::integral_constant{}, + Overload{ + [](Json::Value const& v, std::integral_constant) { + return v["value"].asInt(); + }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires { + v.visit( + std::integral_constant{}, + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit( + std::integral_constant{}, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires { + v.visit()( + std::integral_constant{}, + [](Json::Value const&) {}); + }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit()( + std::integral_constant{}, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires { v.visit(1, [](Json::Value&, unsigned) {}); }; + }(s1)); + CHECK( + s1.visit( + 3u, + Overload{ + [](Json::Value& v, unsigned) { return v["value"].asInt(); }, + [](Json::Value const&, unsigned) { return 0; }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 5); + static_assert([](auto&& v) { + return requires { v.visit()(1, [](Json::Value&, unsigned) {}); }; + }(s1)); + CHECK( + s1.visit()( + 3u, + Overload{ + [](Json::Value& v, unsigned) { return v["value"].asInt(); }, + [](Json::Value const&, unsigned) { return 0; }, + [](Json::Value&, auto) { return 0; }, + [](auto, auto) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires { v.visit(1, [](Json::Value&) {}); }; + }(s1)); + CHECK( + s1.visit( + 3, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 5); + static_assert([](auto&& v) { + return requires { v.visit()(1, [](Json::Value&) {}); }; + }(s1)); + CHECK( + s1.visit()( + 3, + Overload{ + [](Json::Value& v) { return v["value"].asInt(); }, + [](Json::Value const&) { return 0; }, + [](auto...) { return 0; }}) == 5); + + static_assert([](auto&& v) { + return requires { v.visit(1, [](Json::Value const&, unsigned) {}); }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit( + 2u, + Overload{ + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](Json::Value&, unsigned) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires { v.visit()(1, [](Json::Value const&, unsigned) {}); }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit()( + 2u, + Overload{ + [](Json::Value const& v, unsigned) { + return v["value"].asInt(); + }, + [](Json::Value const&, auto) { return 0; }, + [](Json::Value&, unsigned) { return 0; }, + [](auto, auto) { return 0; }}) == 3); + + static_assert([](auto&& v) { + return requires { v.visit(1, [](Json::Value const&) {}); }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit( + 2, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + static_assert([](auto&& v) { + return requires { v.visit()(1, [](Json::Value const&) {}); }; + }(std::as_const(s1))); + CHECK( + std::as_const(s1).visit()( + 2, + Overload{ + [](Json::Value const& v) { return v["value"].asInt(); }, + [](Json::Value&) { return 0; }, + [](auto...) { return 0; }}) == 3); + + // Rvalue MultivarJson visitor only binds to regular reference + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit(1, [](Json::Value&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit(1, [](Json::Value const&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit(1, [](Json::Value&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit(1, [](Json::Value const&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit()(1, [](Json::Value&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit()(1, [](Json::Value const&&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit()(1, [](Json::Value&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit()(1, [](Json::Value const&) {}); + }; + }(std::move(s1))); + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit(1, [](Json::Value const&&) {}); + }; + }(std::move(std::as_const(s1)))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit(1, [](Json::Value const&) {}); + }; + }(std::move(std::as_const(s1)))); + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit()(1, [](Json::Value const&&) {}); + }; + }(std::move(std::as_const(s1)))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit()(1, [](Json::Value const&) {}); + }; + }(std::move(std::as_const(s1)))); + + // Missing const + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit(1, [](Json::Value&, auto) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return !requires { + std::forward(v).visit()(1, [](Json::Value&, auto) {}); + }; + }(std::as_const(s1))); + + // Missing parameter + static_assert([](auto&& v) { + return !requires { std::forward(v).visit(1, []() {}); }; + }(s1)); + static_assert([](auto&& v) { + return !requires { std::forward(v).visit()(1, []() {}); }; + }(s1)); + + // Sanity checks + static_assert([](auto&& v) { + return requires { + std::forward(v).visit(1, [](auto...) {}); + }; + }(std::as_const(s1))); + static_assert([](auto&& v) { + return requires { + std::forward(v).visit()(1, [](auto...) {}); + }; + }(std::as_const(s1))); +} + +TEST_SUITE_END(); diff --git a/src/doctest/protocol/SecretKey.cpp b/src/doctest/protocol/SecretKey.cpp index 641cd47c81..bf975baff5 100644 --- a/src/doctest/protocol/SecretKey.cpp +++ b/src/doctest/protocol/SecretKey.cpp @@ -158,3 +158,100 @@ testSigning(KeyType type) } } } + +TEST_CASE("secp256k1: signing & verification") +{ + testSigning(KeyType::secp256k1); +} + +TEST_CASE("ed25519: signing & verification") +{ + testSigning(KeyType::ed25519); +} + +TEST_CASE("secp256k1: key derivation") +{ + // clang-format off + static TestKeyData const secp256k1TestVectors[] = { + {{0xDE,0xDC,0xE9,0xCE,0x67,0xB4,0x51,0xD8,0x52,0xFD,0x4E,0x84,0x6F,0xCD,0xE3,0x1C}, + {0x03,0x30,0xE7,0xFC,0x9D,0x56,0xBB,0x25,0xD6,0x89,0x3B,0xA3,0xF3,0x17,0xAE,0x5B, + 0xCF,0x33,0xB3,0x29,0x1B,0xD6,0x3D,0xB3,0x26,0x54,0xA3,0x13,0x22,0x2F,0x7F,0xD0,0x20}, + {0x1A,0xCA,0xAE,0xDE,0xCE,0x40,0x5B,0x2A,0x95,0x82,0x12,0x62,0x9E,0x16,0xF2,0xEB, + 0x46,0xB1,0x53,0xEE,0xE9,0x4C,0xDD,0x35,0x0F,0xDE,0xFF,0x52,0x79,0x55,0x25,0xB7}, + "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"}, + {{0xF7,0x5C,0x48,0xFE,0xC4,0x6D,0x4D,0x64,0x92,0x8B,0x79,0x5F,0x3F,0xBA,0xBB,0xA0}, + {0x03,0xAF,0x53,0xE8,0x01,0x1E,0x85,0xB3,0x66,0x64,0xF1,0x71,0x08,0x90,0x50,0x1C, + 0x3E,0x86,0xFC,0x2C,0x66,0x58,0xC2,0xEE,0x83,0xCA,0x58,0x0D,0xC9,0x97,0x25,0x41,0xB1}, + {0x5B,0x8A,0xB0,0xE7,0xCD,0xAF,0x48,0x87,0x4D,0x5D,0x99,0x34,0xBF,0x3E,0x7B,0x2C, + 0xB0,0x6B,0xC4,0xC7,0xEA,0xAA,0xF7,0x62,0x68,0x2E,0xD8,0xD0,0xA3,0x1E,0x3C,0x70}, + "r9ZERztesFu3ZBs7zsWTeCvBg14GQ9zWF7"} + }; + // clang-format on + + for (auto const& v : secp256k1TestVectors) + { + auto const id = parseBase58(v.addr); + CHECK(id); + + auto kp = generateKeyPair(KeyType::secp256k1, Seed{makeSlice(v.seed)}); + + CHECK(kp.first == PublicKey{makeSlice(v.pubkey)}); + CHECK(kp.second == SecretKey{makeSlice(v.seckey)}); + CHECK(calcAccountID(kp.first) == *id); + } +} + +TEST_CASE("ed25519: key derivation") +{ + // clang-format off + static TestKeyData const ed25519TestVectors[] = { + {{0xAF,0x41,0xFF,0x66,0xF7,0x5E,0xBD,0x3A,0x6B,0x18,0xFB,0x7A,0x1D,0xF6,0x1C,0x97}, + {0xED,0x48,0xCB,0xBB,0xE0,0xEE,0x7B,0x86,0x86,0xA7,0xDE,0x9F,0x0A,0x01,0x59,0x73, + 0x4E,0x65,0xF9,0xC3,0x69,0x94,0x7F,0x2E,0x26,0x96,0x23,0x2B,0x46,0x1E,0x55,0x32,0x13}, + {0x1A,0x10,0x97,0xFC,0xD9,0xCE,0x4E,0x1D,0xA2,0x46,0x66,0xB6,0x98,0x87,0x97,0x66, + 0xE1,0x75,0x75,0x47,0xD1,0xD4,0xE3,0x64,0xB6,0x43,0x55,0xF7,0xC8,0x4B,0xA0,0xF3}, + "rVAEQBhWT6nZ4woEifdN3TMMdUZaxeXnR"}, + {{0x14,0x0C,0x1D,0x08,0x13,0x19,0x33,0x9C,0x79,0x9D,0xC6,0xA1,0x65,0x95,0x1B,0xE1}, + {0xED,0x3B,0xC8,0x2E,0xF4,0x5F,0x89,0x09,0xCC,0x00,0xF8,0xB7,0xAA,0xF0,0x59,0x31, + 0x68,0x14,0x11,0x75,0x8C,0x11,0x71,0x24,0x87,0x50,0x66,0xC2,0x83,0x98,0xFE,0x15,0x6D}, + {0xFE,0x3E,0x5A,0x82,0xB8,0x0D,0xD8,0x2E,0x91,0x5F,0x76,0x38,0x94,0x2A,0x33,0x2C, + 0xE3,0x06,0x88,0x79,0x74,0x0C,0x7E,0x90,0xE2,0x20,0xA4,0xFB,0x0B,0x37,0xCE,0xC8}, + "rK57dJ9533WtoY8NNwVWGY7ffuAc8WCcPE"} + }; + // clang-format on + + for (auto const& v : ed25519TestVectors) + { + auto const id = parseBase58(v.addr); + CHECK(id); + + auto kp = generateKeyPair(KeyType::ed25519, Seed{makeSlice(v.seed)}); + + CHECK(kp.first == PublicKey{makeSlice(v.pubkey)}); + CHECK(kp.second == SecretKey{makeSlice(v.seckey)}); + CHECK(calcAccountID(kp.first) == *id); + } +} + +TEST_CASE("secp256k1: cross-type key mismatch") +{ + auto const [pk1, sk1] = randomKeyPair(KeyType::secp256k1); + auto const [pk2, sk2] = randomKeyPair(KeyType::secp256k1); + + CHECK(pk1 != pk2); + CHECK(sk1 != sk2); + + auto const [pk3, sk3] = randomKeyPair(KeyType::ed25519); + auto const [pk4, sk4] = randomKeyPair(KeyType::ed25519); + + CHECK(pk3 != pk4); + CHECK(sk3 != sk4); + + // Cross-type comparisons + CHECK(pk1 != pk3); + CHECK(pk2 != pk4); +} + +TEST_SUITE_END(); + +} // namespace xrpl