added few more tests and doc

This commit is contained in:
Pratik Mankawde
2025-12-12 12:18:19 +00:00
parent db73390eff
commit 61be075ff8
4 changed files with 2425 additions and 0 deletions

171
src/doctest/MIGRATION.md Normal file
View File

@@ -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 <xrpl/beast/unit_test.h>` | `#include <doctest/doctest.h>` |
| `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<iterator, bool>
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,989 @@
#include <xrpl/protocol/MultiApiJson.h>
#include <doctest/doctest.h>
#include <cstdint>
#include <limits>
#include <optional>
#include <type_traits>
#include <utility>
using namespace xrpl;
namespace {
// This needs to be in a namespace because of deduction guide
template <typename... Ts>
struct Overload : Ts...
{
using Ts::operator()...;
};
template <typename... Ts>
Overload(Ts...) -> Overload<Ts...>;
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<decltype(subject.val), std::array<Json::Value, 3>>);
CHECK(subject.val.size() == 3);
CHECK(
(subject.val ==
std::array<Json::Value, 3>{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<decltype(v)>(v).visit(), //
[](Json::Value&, auto) {}); // missing const
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return !requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](Json::Value&) {}); // missing const
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return !requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[]() {}); // missing parameters
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return !requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](auto) {},
1); // missing parameters
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return !requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](auto, auto) {},
1); // missing parameters
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return !requires {
forAllApiVersions(
std::forward<decltype(v)>(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<decltype(v)>(v).visit(), //
[](auto) {});
};
}(s1));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](Json::Value const&) {});
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](auto...) {});
};
}(s1));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](Json::Value const&, auto...) {});
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](Json::Value&, auto, auto, auto...) {},
0,
"");
};
}(s1));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[]<unsigned int Version>(
Json::Value const&,
std::integral_constant<unsigned int, Version>,
int,
char const*) {},
0,
"");
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(v).visit(), //
[](auto...) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return requires {
forAllApiVersions(
std::forward<decltype(v)>(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<Json::Value, 3>{}));
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<unsigned, 1>{},
[](Json::Value&, std::integral_constant<unsigned, 1>) {});
};
}(s1));
CHECK(
s1.visitor(
s1,
std::integral_constant<unsigned, 1>{},
Overload{
[](Json::Value& v, std::integral_constant<unsigned, 1>) {
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<unsigned, 1>{}, [](Json::Value&) {});
};
}(s1));
CHECK(
s1.visitor(
s1,
std::integral_constant<unsigned, 1>{},
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<unsigned, 1>{},
[](Json::Value const&, std::integral_constant<unsigned, 1>) {});
};
}(std::as_const(s1)));
CHECK(
s1.visitor(
std::as_const(s1),
std::integral_constant<unsigned, 2>{},
Overload{
[](Json::Value const& v, std::integral_constant<unsigned, 2>) {
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<unsigned, 1>{},
[](Json::Value const&) {});
};
}(std::as_const(s1)));
CHECK(
s1.visitor(
std::as_const(s1),
std::integral_constant<unsigned, 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.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<unsigned, 1>{}, // to unsigned
[](Json::Value& v, unsigned) { return v["value"].asInt(); }) == 2);
CHECK(
s1.visitor(
std::as_const(s1),
std::integral_constant<unsigned, 2>{}, // to unsigned
[](Json::Value const& v, unsigned) {
return v["value"].asInt();
}) == 3);
CHECK(
s1.visitor(
s1, // to const
std::integral_constant<unsigned, 3>{},
[](Json::Value const& v, auto) { return v["value"].asInt(); }) ==
5);
CHECK(
s1.visitor(
s1, // to const
std::integral_constant<unsigned, 3>{},
[](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<unsigned, 2>{},
[](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<unsigned, 2>{},
[](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<unsigned, 1>{},
[](Json::Value&, std::integral_constant<unsigned, 1>) {});
};
}(s1));
CHECK(
s1.visit(
std::integral_constant<unsigned, 1>{},
Overload{
[](Json::Value& v, std::integral_constant<unsigned, 1>) {
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<unsigned, 1>{},
[](Json::Value&, std::integral_constant<unsigned, 1>) {});
};
}(s1));
CHECK(
s1.visit()(
std::integral_constant<unsigned, 1>{},
Overload{
[](Json::Value& v, std::integral_constant<unsigned, 1>) {
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<unsigned, 1>{}, [](Json::Value&) {});
};
}(s1));
CHECK(
s1.visit(
std::integral_constant<unsigned, 1>{},
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<unsigned, 1>{}, [](Json::Value&) {});
};
}(s1));
CHECK(
s1.visit()(
std::integral_constant<unsigned, 1>{},
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<unsigned, 1>{},
[](Json::Value const&, std::integral_constant<unsigned, 1>) {});
};
}(std::as_const(s1)));
CHECK(
std::as_const(s1).visit(
std::integral_constant<unsigned, 2>{},
Overload{
[](Json::Value const& v, std::integral_constant<unsigned, 2>) {
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<unsigned, 1>{},
[](Json::Value const&, std::integral_constant<unsigned, 1>) {});
};
}(std::as_const(s1)));
CHECK(
std::as_const(s1).visit()(
std::integral_constant<unsigned, 2>{},
Overload{
[](Json::Value const& v, std::integral_constant<unsigned, 2>) {
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<unsigned, 1>{},
[](Json::Value const&) {});
};
}(std::as_const(s1)));
CHECK(
std::as_const(s1).visit(
std::integral_constant<unsigned, 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()(
std::integral_constant<unsigned, 1>{},
[](Json::Value const&) {});
};
}(std::as_const(s1)));
CHECK(
std::as_const(s1).visit()(
std::integral_constant<unsigned, 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&, 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<decltype(v)>(v).visit(1, [](Json::Value&&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit(1, [](Json::Value const&&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit(1, [](Json::Value&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit(1, [](Json::Value const&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value&&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value const&&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value const&) {});
};
}(std::move(s1)));
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit(1, [](Json::Value const&&) {});
};
}(std::move(std::as_const(s1))));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit(1, [](Json::Value const&) {});
};
}(std::move(std::as_const(s1))));
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value const&&) {});
};
}(std::move(std::as_const(s1))));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value const&) {});
};
}(std::move(std::as_const(s1))));
// Missing const
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit(1, [](Json::Value&, auto) {});
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return !requires {
std::forward<decltype(v)>(v).visit()(1, [](Json::Value&, auto) {});
};
}(std::as_const(s1)));
// Missing parameter
static_assert([](auto&& v) {
return !requires { std::forward<decltype(v)>(v).visit(1, []() {}); };
}(s1));
static_assert([](auto&& v) {
return !requires { std::forward<decltype(v)>(v).visit()(1, []() {}); };
}(s1));
// Sanity checks
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit(1, [](auto...) {});
};
}(std::as_const(s1)));
static_assert([](auto&& v) {
return requires {
std::forward<decltype(v)>(v).visit()(1, [](auto...) {});
};
}(std::as_const(s1)));
}
TEST_SUITE_END();

View File

@@ -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<AccountID>(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<AccountID>(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