mirror of
https://github.com/XRPLF/rippled.git
synced 2026-01-09 17:25:35 +00:00
Compare commits
3 Commits
ximinez/fi
...
pratik/Mig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61be075ff8 | ||
|
|
db73390eff | ||
|
|
adc6ca6d11 |
@@ -1,3 +1,13 @@
|
||||
doctest.basics > xrpl.basics
|
||||
doctest.basics > xrpl.protocol
|
||||
doctest.beast > xrpl.basics
|
||||
doctest.core > xrpl.core
|
||||
doctest.core > xrpl.json
|
||||
doctest.csf > test.csf
|
||||
doctest.nodestore > xrpl.nodestore
|
||||
doctest.protocol > xrpl.basics
|
||||
doctest.protocol > xrpl.json
|
||||
doctest.protocol > xrpl.protocol
|
||||
libxrpl.basics > xrpl.basics
|
||||
libxrpl.core > xrpl.basics
|
||||
libxrpl.core > xrpl.core
|
||||
|
||||
@@ -147,4 +147,5 @@ include(XrplValidatorKeys)
|
||||
if(tests)
|
||||
include(CTest)
|
||||
add_subdirectory(src/tests/libxrpl)
|
||||
add_subdirectory(src/doctest)
|
||||
endif()
|
||||
|
||||
73
src/doctest/CMakeLists.txt
Normal file
73
src/doctest/CMakeLists.txt
Normal file
@@ -0,0 +1,73 @@
|
||||
# CMake configuration for doctest-based tests
|
||||
# These are converted from the beast unit_test framework
|
||||
|
||||
include(XrplAddTest)
|
||||
|
||||
find_package(doctest REQUIRED)
|
||||
|
||||
# Custom target for all doctest tests defined in this file
|
||||
add_custom_target(xrpl.doctests)
|
||||
|
||||
# Common library dependencies
|
||||
add_library(xrpl.imports.doctest INTERFACE)
|
||||
target_link_libraries(xrpl.imports.doctest INTERFACE
|
||||
doctest::doctest
|
||||
xrpl.libxrpl
|
||||
)
|
||||
|
||||
# Include xrpld sources for tests that need app-level functionality
|
||||
target_include_directories(xrpl.imports.doctest INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/src>
|
||||
)
|
||||
|
||||
# Link against xrpld libraries for tests that need app-level functionality
|
||||
target_link_libraries(xrpl.imports.doctest INTERFACE
|
||||
Xrpl::boost
|
||||
Xrpl::opts
|
||||
Xrpl::libs
|
||||
)
|
||||
|
||||
# Compiler flags for strict checking
|
||||
set(DOCTEST_COMPILE_FLAGS
|
||||
-m64
|
||||
-g
|
||||
-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
|
||||
)
|
||||
|
||||
# Helper function to add a doctest module
|
||||
function(xrpl_add_doctest name)
|
||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
|
||||
)
|
||||
|
||||
set(target xrpl.doctest.${name})
|
||||
add_executable(${target} ${sources})
|
||||
target_link_libraries(${target} PRIVATE xrpl.imports.doctest)
|
||||
target_compile_options(${target} PRIVATE ${DOCTEST_COMPILE_FLAGS})
|
||||
|
||||
set_target_properties(${target} PROPERTIES
|
||||
UNITY_BUILD_MODE GROUP
|
||||
UNITY_BUILD_BATCH_SIZE 0
|
||||
)
|
||||
|
||||
add_test(NAME ${target} COMMAND ${target})
|
||||
add_dependencies(xrpl.doctests ${target})
|
||||
endfunction()
|
||||
|
||||
# One test executable for each module
|
||||
xrpl_add_doctest(basics)
|
||||
xrpl_add_doctest(beast)
|
||||
xrpl_add_doctest(core)
|
||||
xrpl_add_doctest(csf)
|
||||
xrpl_add_doctest(nodestore)
|
||||
xrpl_add_doctest(protocol)
|
||||
171
src/doctest/MIGRATION.md
Normal file
171
src/doctest/MIGRATION.md
Normal 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
|
||||
|
||||
257
src/doctest/basics/Buffer.cpp
Normal file
257
src/doctest/basics/Buffer.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
namespace {
|
||||
bool
|
||||
sane(Buffer const& b)
|
||||
{
|
||||
if (b.size() == 0)
|
||||
return b.data() == nullptr;
|
||||
|
||||
return b.data() != nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_SUITE_BEGIN("Buffer");
|
||||
|
||||
TEST_CASE("basic operations")
|
||||
{
|
||||
std::uint8_t const data[] = {
|
||||
0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a,
|
||||
0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c,
|
||||
0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3};
|
||||
|
||||
Buffer b0;
|
||||
CHECK(sane(b0));
|
||||
CHECK(b0.empty());
|
||||
|
||||
Buffer b1{0};
|
||||
CHECK(sane(b1));
|
||||
CHECK(b1.empty());
|
||||
std::memcpy(b1.alloc(16), data, 16);
|
||||
CHECK(sane(b1));
|
||||
CHECK(!b1.empty());
|
||||
CHECK(b1.size() == 16);
|
||||
|
||||
Buffer b2{b1.size()};
|
||||
CHECK(sane(b2));
|
||||
CHECK(!b2.empty());
|
||||
CHECK(b2.size() == b1.size());
|
||||
std::memcpy(b2.data(), data + 16, 16);
|
||||
|
||||
Buffer b3{data, sizeof(data)};
|
||||
CHECK(sane(b3));
|
||||
CHECK(!b3.empty());
|
||||
CHECK(b3.size() == sizeof(data));
|
||||
CHECK(std::memcmp(b3.data(), data, b3.size()) == 0);
|
||||
|
||||
// Check equality and inequality comparisons
|
||||
CHECK(b0 == b0);
|
||||
CHECK(b0 != b1);
|
||||
CHECK(b1 == b1);
|
||||
CHECK(b1 != b2);
|
||||
CHECK(b2 != b3);
|
||||
|
||||
SUBCASE("Copy Construction / Assignment")
|
||||
{
|
||||
Buffer x{b0};
|
||||
CHECK(x == b0);
|
||||
CHECK(sane(x));
|
||||
Buffer y{b1};
|
||||
CHECK(y == b1);
|
||||
CHECK(sane(y));
|
||||
x = b2;
|
||||
CHECK(x == b2);
|
||||
CHECK(sane(x));
|
||||
x = y;
|
||||
CHECK(x == y);
|
||||
CHECK(sane(x));
|
||||
y = b3;
|
||||
CHECK(y == b3);
|
||||
CHECK(sane(y));
|
||||
x = b0;
|
||||
CHECK(x == b0);
|
||||
CHECK(sane(x));
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
#endif
|
||||
|
||||
x = x;
|
||||
CHECK(x == b0);
|
||||
CHECK(sane(x));
|
||||
y = y;
|
||||
CHECK(y == b3);
|
||||
CHECK(sane(y));
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
SUBCASE("Move Construction / Assignment")
|
||||
{
|
||||
static_assert(std::is_nothrow_move_constructible<Buffer>::value, "");
|
||||
static_assert(std::is_nothrow_move_assignable<Buffer>::value, "");
|
||||
|
||||
{ // Move-construct from empty buf
|
||||
Buffer x;
|
||||
Buffer y{std::move(x)};
|
||||
CHECK(sane(x));
|
||||
CHECK(x.empty());
|
||||
CHECK(sane(y));
|
||||
CHECK(y.empty());
|
||||
CHECK(x == y);
|
||||
}
|
||||
|
||||
{ // Move-construct from non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y{std::move(x)};
|
||||
CHECK(sane(x));
|
||||
CHECK(x.empty());
|
||||
CHECK(sane(y));
|
||||
CHECK(y == b1);
|
||||
}
|
||||
|
||||
{ // Move assign empty buf to empty buf
|
||||
Buffer x;
|
||||
Buffer y;
|
||||
|
||||
x = std::move(y);
|
||||
CHECK(sane(x));
|
||||
CHECK(x.empty());
|
||||
CHECK(sane(y));
|
||||
CHECK(y.empty());
|
||||
}
|
||||
|
||||
{ // Move assign non-empty buf to empty buf
|
||||
Buffer x;
|
||||
Buffer y{b1};
|
||||
|
||||
x = std::move(y);
|
||||
CHECK(sane(x));
|
||||
CHECK(x == b1);
|
||||
CHECK(sane(y));
|
||||
CHECK(y.empty());
|
||||
}
|
||||
|
||||
{ // Move assign empty buf to non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y;
|
||||
|
||||
x = std::move(y);
|
||||
CHECK(sane(x));
|
||||
CHECK(x.empty());
|
||||
CHECK(sane(y));
|
||||
CHECK(y.empty());
|
||||
}
|
||||
|
||||
{ // Move assign non-empty buf to non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y{b2};
|
||||
Buffer z{b3};
|
||||
|
||||
x = std::move(y);
|
||||
CHECK(sane(x));
|
||||
CHECK(!x.empty());
|
||||
CHECK(sane(y));
|
||||
CHECK(y.empty());
|
||||
|
||||
x = std::move(z);
|
||||
CHECK(sane(x));
|
||||
CHECK(!x.empty());
|
||||
CHECK(sane(z));
|
||||
CHECK(z.empty());
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Slice Conversion / Construction / Assignment")
|
||||
{
|
||||
Buffer w{static_cast<Slice>(b0)};
|
||||
CHECK(sane(w));
|
||||
CHECK(w == b0);
|
||||
|
||||
Buffer x{static_cast<Slice>(b1)};
|
||||
CHECK(sane(x));
|
||||
CHECK(x == b1);
|
||||
|
||||
Buffer y{static_cast<Slice>(b2)};
|
||||
CHECK(sane(y));
|
||||
CHECK(y == b2);
|
||||
|
||||
Buffer z{static_cast<Slice>(b3)};
|
||||
CHECK(sane(z));
|
||||
CHECK(z == b3);
|
||||
|
||||
// Assign empty slice to empty buffer
|
||||
w = static_cast<Slice>(b0);
|
||||
CHECK(sane(w));
|
||||
CHECK(w == b0);
|
||||
|
||||
// Assign non-empty slice to empty buffer
|
||||
w = static_cast<Slice>(b1);
|
||||
CHECK(sane(w));
|
||||
CHECK(w == b1);
|
||||
|
||||
// Assign non-empty slice to non-empty buffer
|
||||
x = static_cast<Slice>(b2);
|
||||
CHECK(sane(x));
|
||||
CHECK(x == b2);
|
||||
|
||||
// Assign non-empty slice to non-empty buffer
|
||||
y = static_cast<Slice>(z);
|
||||
CHECK(sane(y));
|
||||
CHECK(y == z);
|
||||
|
||||
// Assign empty slice to non-empty buffer:
|
||||
z = static_cast<Slice>(b0);
|
||||
CHECK(sane(z));
|
||||
CHECK(z == b0);
|
||||
}
|
||||
|
||||
SUBCASE("Allocation, Deallocation and Clearing")
|
||||
{
|
||||
auto test = [](Buffer const& b, std::size_t i) {
|
||||
Buffer x{b};
|
||||
|
||||
// Try to allocate some number of bytes, possibly
|
||||
// zero (which means clear) and sanity check
|
||||
x(i);
|
||||
CHECK(sane(x));
|
||||
CHECK(x.size() == i);
|
||||
CHECK((x.data() == nullptr) == (i == 0));
|
||||
|
||||
// Try to allocate some more data (always non-zero)
|
||||
x(i + 1);
|
||||
CHECK(sane(x));
|
||||
CHECK(x.size() == i + 1);
|
||||
CHECK(x.data() != nullptr);
|
||||
|
||||
// Try to clear:
|
||||
x.clear();
|
||||
CHECK(sane(x));
|
||||
CHECK(x.size() == 0);
|
||||
CHECK(x.data() == nullptr);
|
||||
|
||||
// Try to clear again:
|
||||
x.clear();
|
||||
CHECK(sane(x));
|
||||
CHECK(x.size() == 0);
|
||||
CHECK(x.data() == nullptr);
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < 16; ++i)
|
||||
{
|
||||
test(b0, i);
|
||||
test(b1, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
228
src/doctest/basics/Expected.cpp
Normal file
228
src/doctest/basics/Expected.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#if BOOST_VERSION >= 107500
|
||||
#include <boost/json.hpp> // Not part of boost before version 1.75
|
||||
#endif // BOOST_VERSION
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("Expected");
|
||||
|
||||
TEST_CASE("non-error const construction")
|
||||
{
|
||||
auto const expected = []() -> Expected<std::string, TER> {
|
||||
return "Valid value";
|
||||
}();
|
||||
CHECK(expected);
|
||||
CHECK(expected.has_value());
|
||||
CHECK(expected.value() == "Valid value");
|
||||
CHECK(*expected == "Valid value");
|
||||
CHECK(expected->at(0) == 'V');
|
||||
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no error, so should throw.
|
||||
[[maybe_unused]] TER const t = expected.error();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("non-error non-const construction")
|
||||
{
|
||||
auto expected = []() -> Expected<std::string, TER> {
|
||||
return "Valid value";
|
||||
}();
|
||||
CHECK(expected);
|
||||
CHECK(expected.has_value());
|
||||
CHECK(expected.value() == "Valid value");
|
||||
CHECK(*expected == "Valid value");
|
||||
CHECK(expected->at(0) == 'V');
|
||||
std::string mv = std::move(*expected);
|
||||
CHECK(mv == "Valid value");
|
||||
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no error, so should throw.
|
||||
[[maybe_unused]] TER const t = expected.error();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("non-error overlapping type construction")
|
||||
{
|
||||
auto expected = []() -> Expected<std::uint32_t, std::uint16_t> {
|
||||
return 1;
|
||||
}();
|
||||
CHECK(expected);
|
||||
CHECK(expected.has_value());
|
||||
CHECK(expected.value() == 1);
|
||||
CHECK(*expected == 1);
|
||||
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no error, so should throw.
|
||||
[[maybe_unused]] std::uint16_t const t = expected.error();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("error construction from rvalue")
|
||||
{
|
||||
auto const expected = []() -> Expected<std::string, TER> {
|
||||
return Unexpected(telLOCAL_ERROR);
|
||||
}();
|
||||
CHECK(!expected);
|
||||
CHECK(!expected.has_value());
|
||||
CHECK(expected.error() == telLOCAL_ERROR);
|
||||
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no result, so should throw.
|
||||
[[maybe_unused]] std::string const s = *expected;
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("error construction from lvalue")
|
||||
{
|
||||
auto const err(telLOCAL_ERROR);
|
||||
auto expected = [&err]() -> Expected<std::string, TER> {
|
||||
return Unexpected(err);
|
||||
}();
|
||||
CHECK(!expected);
|
||||
CHECK(!expected.has_value());
|
||||
CHECK(expected.error() == telLOCAL_ERROR);
|
||||
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no result, so should throw.
|
||||
[[maybe_unused]] std::size_t const s = expected->size();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("error construction from const char*")
|
||||
{
|
||||
auto const expected = []() -> Expected<int, char const*> {
|
||||
return Unexpected("Not what is expected!");
|
||||
}();
|
||||
CHECK(!expected);
|
||||
CHECK(!expected.has_value());
|
||||
CHECK(expected.error() == std::string("Not what is expected!"));
|
||||
}
|
||||
|
||||
TEST_CASE("error construction of string from const char*")
|
||||
{
|
||||
auto expected = []() -> Expected<int, std::string> {
|
||||
return Unexpected("Not what is expected!");
|
||||
}();
|
||||
CHECK(!expected);
|
||||
CHECK(!expected.has_value());
|
||||
CHECK(expected.error() == "Not what is expected!");
|
||||
std::string const s(std::move(expected.error()));
|
||||
CHECK(s == "Not what is expected!");
|
||||
}
|
||||
|
||||
TEST_CASE("non-error const construction of Expected<void, T>")
|
||||
{
|
||||
auto const expected = []() -> Expected<void, std::string> { return {}; }();
|
||||
CHECK(expected);
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no error, so should throw.
|
||||
[[maybe_unused]] std::size_t const s = expected.error().size();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("non-error non-const construction of Expected<void, T>")
|
||||
{
|
||||
auto expected = []() -> Expected<void, std::string> { return {}; }();
|
||||
CHECK(expected);
|
||||
bool throwOccurred = false;
|
||||
try
|
||||
{
|
||||
// There's no error, so should throw.
|
||||
[[maybe_unused]] std::size_t const s = expected.error().size();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("bad expected access"));
|
||||
throwOccurred = true;
|
||||
}
|
||||
CHECK(throwOccurred);
|
||||
}
|
||||
|
||||
TEST_CASE("error const construction of Expected<void, T>")
|
||||
{
|
||||
auto const expected = []() -> Expected<void, std::string> {
|
||||
return Unexpected("Not what is expected!");
|
||||
}();
|
||||
CHECK(!expected);
|
||||
CHECK(expected.error() == "Not what is expected!");
|
||||
}
|
||||
|
||||
TEST_CASE("error non-const construction of Expected<void, T>")
|
||||
{
|
||||
auto expected = []() -> Expected<void, std::string> {
|
||||
return Unexpected("Not what is expected!");
|
||||
}();
|
||||
CHECK(!expected);
|
||||
CHECK(expected.error() == "Not what is expected!");
|
||||
std::string const s(std::move(expected.error()));
|
||||
CHECK(s == "Not what is expected!");
|
||||
}
|
||||
|
||||
#if BOOST_VERSION >= 107500
|
||||
TEST_CASE("boost::json::value construction")
|
||||
{
|
||||
auto expected = []() -> Expected<boost::json::value, std::string> {
|
||||
return boost::json::object{{"oops", "me array now"}};
|
||||
}();
|
||||
CHECK(expected);
|
||||
CHECK(!expected.value().is_array());
|
||||
}
|
||||
#endif // BOOST_VERSION
|
||||
|
||||
TEST_SUITE_END();
|
||||
220
src/doctest/basics/IOUAmount.cpp
Normal file
220
src/doctest/basics/IOUAmount.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("IOUAmount");
|
||||
|
||||
TEST_CASE("zero")
|
||||
{
|
||||
IOUAmount const z(0, 0);
|
||||
|
||||
CHECK(z.mantissa() == 0);
|
||||
CHECK(z.exponent() == -100);
|
||||
CHECK(!z);
|
||||
CHECK(z.signum() == 0);
|
||||
CHECK(z == beast::zero);
|
||||
|
||||
CHECK((z + z) == z);
|
||||
CHECK((z - z) == z);
|
||||
CHECK(z == -z);
|
||||
|
||||
IOUAmount const zz(beast::zero);
|
||||
CHECK(z == zz);
|
||||
|
||||
// https://github.com/XRPLF/rippled/issues/5170
|
||||
IOUAmount const zzz{};
|
||||
CHECK(zzz == beast::zero);
|
||||
}
|
||||
|
||||
TEST_CASE("signum")
|
||||
{
|
||||
IOUAmount const neg(-1, 0);
|
||||
CHECK(neg.signum() < 0);
|
||||
|
||||
IOUAmount const zer(0, 0);
|
||||
CHECK(zer.signum() == 0);
|
||||
|
||||
IOUAmount const pos(1, 0);
|
||||
CHECK(pos.signum() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("beast::Zero Comparisons")
|
||||
{
|
||||
using beast::zero;
|
||||
|
||||
{
|
||||
IOUAmount z(zero);
|
||||
CHECK(z == zero);
|
||||
CHECK(z >= zero);
|
||||
CHECK(z <= zero);
|
||||
CHECK(!(z != zero));
|
||||
CHECK(!(z > zero));
|
||||
CHECK(!(z < zero));
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const neg(-2, 0);
|
||||
CHECK(neg < zero);
|
||||
CHECK(neg <= zero);
|
||||
CHECK(neg != zero);
|
||||
CHECK(!(neg == zero));
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const pos(2, 0);
|
||||
CHECK(pos > zero);
|
||||
CHECK(pos >= zero);
|
||||
CHECK(pos != zero);
|
||||
CHECK(!(pos == zero));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("IOU Comparisons")
|
||||
{
|
||||
IOUAmount const n(-2, 0);
|
||||
IOUAmount const z(0, 0);
|
||||
IOUAmount const p(2, 0);
|
||||
|
||||
CHECK(z == z);
|
||||
CHECK(z >= z);
|
||||
CHECK(z <= z);
|
||||
CHECK(z == -z);
|
||||
CHECK(!(z > z));
|
||||
CHECK(!(z < z));
|
||||
CHECK(!(z != z));
|
||||
CHECK(!(z != -z));
|
||||
|
||||
CHECK(n < z);
|
||||
CHECK(n <= z);
|
||||
CHECK(n != z);
|
||||
CHECK(!(n > z));
|
||||
CHECK(!(n >= z));
|
||||
CHECK(!(n == z));
|
||||
|
||||
CHECK(p > z);
|
||||
CHECK(p >= z);
|
||||
CHECK(p != z);
|
||||
CHECK(!(p < z));
|
||||
CHECK(!(p <= z));
|
||||
CHECK(!(p == z));
|
||||
|
||||
CHECK(n < p);
|
||||
CHECK(n <= p);
|
||||
CHECK(n != p);
|
||||
CHECK(!(n > p));
|
||||
CHECK(!(n >= p));
|
||||
CHECK(!(n == p));
|
||||
|
||||
CHECK(p > n);
|
||||
CHECK(p >= n);
|
||||
CHECK(p != n);
|
||||
CHECK(!(p < n));
|
||||
CHECK(!(p <= n));
|
||||
CHECK(!(p == n));
|
||||
|
||||
CHECK(p > -p);
|
||||
CHECK(p >= -p);
|
||||
CHECK(p != -p);
|
||||
|
||||
CHECK(n < -n);
|
||||
CHECK(n <= -n);
|
||||
CHECK(n != -n);
|
||||
}
|
||||
|
||||
TEST_CASE("IOU strings")
|
||||
{
|
||||
CHECK(to_string(IOUAmount(-2, 0)) == "-2");
|
||||
CHECK(to_string(IOUAmount(0, 0)) == "0");
|
||||
CHECK(to_string(IOUAmount(2, 0)) == "2");
|
||||
CHECK(to_string(IOUAmount(25, -3)) == "0.025");
|
||||
CHECK(to_string(IOUAmount(-25, -3)) == "-0.025");
|
||||
CHECK(to_string(IOUAmount(25, 1)) == "250");
|
||||
CHECK(to_string(IOUAmount(-25, 1)) == "-250");
|
||||
CHECK(to_string(IOUAmount(2, 20)) == "2000000000000000e5");
|
||||
CHECK(to_string(IOUAmount(-2, -20)) == "-2000000000000000e-35");
|
||||
}
|
||||
|
||||
TEST_CASE("mulRatio")
|
||||
{
|
||||
/* The range for the mantissa when normalized */
|
||||
constexpr std::int64_t minMantissa = 1000000000000000ull;
|
||||
constexpr std::int64_t maxMantissa = 9999999999999999ull;
|
||||
/* The range for the exponent when normalized */
|
||||
constexpr int minExponent = -96;
|
||||
constexpr int maxExponent = 80;
|
||||
constexpr auto maxUInt = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
{
|
||||
// multiply by a number that would overflow the mantissa, then
|
||||
// divide by the same number, and check we didn't lose any value
|
||||
IOUAmount bigMan(maxMantissa, 0);
|
||||
CHECK(bigMan == mulRatio(bigMan, maxUInt, maxUInt, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
CHECK(bigMan == mulRatio(bigMan, maxUInt, maxUInt, false));
|
||||
}
|
||||
{
|
||||
// Similar test as above, but for negative values
|
||||
IOUAmount bigMan(-maxMantissa, 0);
|
||||
CHECK(bigMan == mulRatio(bigMan, maxUInt, maxUInt, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
CHECK(bigMan == mulRatio(bigMan, maxUInt, maxUInt, false));
|
||||
}
|
||||
|
||||
{
|
||||
// small amounts
|
||||
IOUAmount tiny(minMantissa, minExponent);
|
||||
// Round up should give the smallest allowable number
|
||||
CHECK(tiny == mulRatio(tiny, 1, maxUInt, true));
|
||||
CHECK(tiny == mulRatio(tiny, maxUInt - 1, maxUInt, true));
|
||||
// rounding down should be zero
|
||||
CHECK(beast::zero == mulRatio(tiny, 1, maxUInt, false));
|
||||
CHECK(beast::zero == mulRatio(tiny, maxUInt - 1, maxUInt, false));
|
||||
|
||||
// tiny negative numbers
|
||||
IOUAmount tinyNeg(-minMantissa, minExponent);
|
||||
// Round up should give zero
|
||||
CHECK(beast::zero == mulRatio(tinyNeg, 1, maxUInt, true));
|
||||
CHECK(beast::zero == mulRatio(tinyNeg, maxUInt - 1, maxUInt, true));
|
||||
// rounding down should be tiny
|
||||
CHECK(tinyNeg == mulRatio(tinyNeg, 1, maxUInt, false));
|
||||
CHECK(tinyNeg == mulRatio(tinyNeg, maxUInt - 1, maxUInt, false));
|
||||
}
|
||||
|
||||
{ // rounding
|
||||
{
|
||||
IOUAmount one(1, 0);
|
||||
auto const rup = mulRatio(one, maxUInt - 1, maxUInt, true);
|
||||
auto const rdown = mulRatio(one, maxUInt - 1, maxUInt, false);
|
||||
CHECK(rup.mantissa() - rdown.mantissa() == 1);
|
||||
}
|
||||
{
|
||||
IOUAmount big(maxMantissa, maxExponent);
|
||||
auto const rup = mulRatio(big, maxUInt - 1, maxUInt, true);
|
||||
auto const rdown = mulRatio(big, maxUInt - 1, maxUInt, false);
|
||||
CHECK(rup.mantissa() - rdown.mantissa() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount negOne(-1, 0);
|
||||
auto const rup = mulRatio(negOne, maxUInt - 1, maxUInt, true);
|
||||
auto const rdown = mulRatio(negOne, maxUInt - 1, maxUInt, false);
|
||||
CHECK(rup.mantissa() - rdown.mantissa() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// division by zero
|
||||
IOUAmount one(1, 0);
|
||||
CHECK_THROWS([&] { mulRatio(one, 1, 0, true); }());
|
||||
}
|
||||
|
||||
{
|
||||
// overflow
|
||||
IOUAmount big(maxMantissa, maxExponent);
|
||||
CHECK_THROWS([&] { mulRatio(big, 2, 0, true); }());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
74
src/doctest/basics/KeyCache.cpp
Normal file
74
src/doctest/basics/KeyCache.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/TaggedCache.ipp>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("KeyCache");
|
||||
|
||||
TEST_CASE("KeyCache operations")
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch clock;
|
||||
clock.set(0);
|
||||
|
||||
using Key = std::string;
|
||||
using Cache = TaggedCache<Key, int, true>;
|
||||
|
||||
beast::Journal j{beast::Journal::getNullSink()};
|
||||
|
||||
SUBCASE("Insert, retrieve, and age item")
|
||||
{
|
||||
Cache c("test", LedgerIndex(1), 2s, clock, j);
|
||||
|
||||
CHECK(c.size() == 0);
|
||||
CHECK(c.insert("one"));
|
||||
CHECK(!c.insert("one"));
|
||||
CHECK(c.size() == 1);
|
||||
CHECK(c.touch_if_exists("one"));
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.size() == 1);
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.size() == 0);
|
||||
CHECK(!c.touch_if_exists("one"));
|
||||
}
|
||||
|
||||
SUBCASE("Insert two items, have one expire")
|
||||
{
|
||||
Cache c("test", LedgerIndex(2), 2s, clock, j);
|
||||
|
||||
CHECK(c.insert("one"));
|
||||
CHECK(c.size() == 1);
|
||||
CHECK(c.insert("two"));
|
||||
CHECK(c.size() == 2);
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.size() == 2);
|
||||
CHECK(c.touch_if_exists("two"));
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.size() == 1);
|
||||
}
|
||||
|
||||
SUBCASE("Insert three items (1 over limit), sweep")
|
||||
{
|
||||
Cache c("test", LedgerIndex(2), 3s, clock, j);
|
||||
|
||||
CHECK(c.insert("one"));
|
||||
++clock;
|
||||
CHECK(c.insert("two"));
|
||||
++clock;
|
||||
CHECK(c.insert("three"));
|
||||
++clock;
|
||||
CHECK(c.size() == 3);
|
||||
c.sweep();
|
||||
CHECK(c.size() < 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
416
src/doctest/basics/Number.cpp
Normal file
416
src/doctest/basics/Number.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("Number");
|
||||
|
||||
TEST_CASE("zero")
|
||||
{
|
||||
Number const z{0, 0};
|
||||
|
||||
CHECK(z.mantissa() == 0);
|
||||
CHECK(z.exponent() == Number{}.exponent());
|
||||
|
||||
CHECK((z + z) == z);
|
||||
CHECK((z - z) == z);
|
||||
CHECK(z == -z);
|
||||
}
|
||||
|
||||
TEST_CASE("limits")
|
||||
{
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
Number x{10'000'000'000'000'000, 32768};
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
Number x{10'000'000'000'000'000, 32767};
|
||||
CHECK((x == Number{1'000'000'000'000'000, 32768}));
|
||||
Number z{1'000'000'000'000'000, -32769};
|
||||
CHECK(z == Number{});
|
||||
Number y{1'000'000'000'000'001'500, 32000};
|
||||
CHECK((y == Number{1'000'000'000'000'002, 32003}));
|
||||
Number m{std::numeric_limits<std::int64_t>::min()};
|
||||
CHECK((m == Number{-9'223'372'036'854'776, 3}));
|
||||
Number M{std::numeric_limits<std::int64_t>::max()};
|
||||
CHECK((M == Number{9'223'372'036'854'776, 3}));
|
||||
caught = false;
|
||||
try
|
||||
{
|
||||
Number q{99'999'999'999'999'999, 32767};
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
TEST_CASE("add")
|
||||
{
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
Case c[]{
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'066, -15}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{-6'555'555'555'555'555, -29},
|
||||
Number{-1'000'000'000'000'066, -15}},
|
||||
{Number{-1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{-9'999'999'999'999'344, -16}},
|
||||
{Number{-6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'344, -16}},
|
||||
{Number{}, Number{5}, Number{5}},
|
||||
{Number{5'555'555'555'555'555, -32768},
|
||||
Number{-5'555'555'555'555'554, -32768},
|
||||
Number{0}},
|
||||
{Number{-9'999'999'999'999'999, -31},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{9'999'999'999'999'990, -16}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK(x + y == z);
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
Number{9'999'999'999'999'999, 32768} +
|
||||
Number{5'000'000'000'000'000, 32767};
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
TEST_CASE("sub")
|
||||
{
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
Case c[]{
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{6'555'555'555'555'555, -29},
|
||||
Number{9'999'999'999'999'344, -16}},
|
||||
{Number{6'555'555'555'555'555, -29},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{-9'999'999'999'999'344, -16}},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{0}},
|
||||
{Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'001, -15},
|
||||
Number{-1'000'000'000'000'000, -30}},
|
||||
{Number{1'000'000'000'000'001, -15},
|
||||
Number{1'000'000'000'000'000, -15},
|
||||
Number{1'000'000'000'000'000, -30}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK(x - y == z);
|
||||
}
|
||||
|
||||
TEST_CASE("mul")
|
||||
{
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
|
||||
{
|
||||
Case c[]{
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{2000000000000000, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{-2000000000000000, -15}},
|
||||
{Number{-1414213562373095, -15},
|
||||
Number{-1414213562373095, -15},
|
||||
Number{2000000000000000, -15}},
|
||||
{Number{3214285714285706, -15},
|
||||
Number{3111111111111119, -15},
|
||||
Number{1000000000000000, -14}},
|
||||
{Number{1000000000000000, -32768},
|
||||
Number{1000000000000000, -32768},
|
||||
Number{0}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK(x * y == z);
|
||||
}
|
||||
Number::setround(Number::towards_zero);
|
||||
{
|
||||
Case c[]{
|
||||
{Number{7}, Number{8}, Number{56}},
|
||||
{Number{1414213562373095, -15},
|
||||
Number{1414213562373095, -15},
|
||||
Number{1999999999999999, -15}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK(x * y == z);
|
||||
}
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
Number{9'999'999'999'999'999, 32768} *
|
||||
Number{5'000'000'000'000'000, 32767};
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
TEST_CASE("div")
|
||||
{
|
||||
using Case = std::tuple<Number, Number, Number>;
|
||||
saveNumberRoundMode save{Number::setround(Number::to_nearest)};
|
||||
{
|
||||
Case c[]{
|
||||
{Number{1}, Number{2}, Number{5, -1}},
|
||||
{Number{1}, Number{10}, Number{1, -1}},
|
||||
{Number{1}, Number{-10}, Number{-1, -1}},
|
||||
{Number{0}, Number{100}, Number{0}},
|
||||
{Number{1414213562373095, -10},
|
||||
Number{1414213562373095, -10},
|
||||
Number{1}},
|
||||
{Number{9'999'999'999'999'999},
|
||||
Number{1'000'000'000'000'000},
|
||||
Number{9'999'999'999'999'999, -15}},
|
||||
{Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}},
|
||||
{Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK(x / y == z);
|
||||
}
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
Number{1000000000000000, -15} / Number{0};
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
TEST_CASE("root")
|
||||
{
|
||||
using Case = std::tuple<Number, unsigned, Number>;
|
||||
Case c[]{
|
||||
{Number{2}, 2, Number{1414213562373095, -15}},
|
||||
{Number{2'000'000}, 2, Number{1414213562373095, -12}},
|
||||
{Number{2, -30}, 2, Number{1414213562373095, -30}},
|
||||
{Number{-27}, 3, Number{-3}},
|
||||
{Number{1}, 5, Number{1}},
|
||||
{Number{-1}, 0, Number{1}},
|
||||
{Number{5, -1}, 0, Number{0}},
|
||||
{Number{0}, 5, Number{0}},
|
||||
{Number{5625, -4}, 2, Number{75, -2}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK((root(x, y) == z));
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
(void)root(Number{-2}, 0);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
caught = false;
|
||||
try
|
||||
{
|
||||
(void)root(Number{-2}, 4);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
TEST_CASE("power1")
|
||||
{
|
||||
using Case = std::tuple<Number, unsigned, Number>;
|
||||
Case c[]{
|
||||
{Number{64}, 0, Number{1}},
|
||||
{Number{64}, 1, Number{64}},
|
||||
{Number{64}, 2, Number{4096}},
|
||||
{Number{-64}, 2, Number{4096}},
|
||||
{Number{64}, 3, Number{262144}},
|
||||
{Number{-64}, 3, Number{-262144}}};
|
||||
for (auto const& [x, y, z] : c)
|
||||
CHECK((power(x, y) == z));
|
||||
}
|
||||
|
||||
TEST_CASE("power2")
|
||||
{
|
||||
using Case = std::tuple<Number, unsigned, unsigned, Number>;
|
||||
Case c[]{
|
||||
{Number{1}, 3, 7, Number{1}},
|
||||
{Number{-1}, 1, 0, Number{1}},
|
||||
{Number{-1, -1}, 1, 0, Number{0}},
|
||||
{Number{16}, 0, 5, Number{1}},
|
||||
{Number{34}, 3, 3, Number{34}},
|
||||
{Number{4}, 3, 2, Number{8}}};
|
||||
for (auto const& [x, n, d, z] : c)
|
||||
CHECK((power(x, n, d) == z));
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
(void)power(Number{7}, 0, 0);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
caught = false;
|
||||
try
|
||||
{
|
||||
(void)power(Number{7}, 1, 0);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
caught = false;
|
||||
try
|
||||
{
|
||||
(void)power(Number{-1, -1}, 3, 2);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
TEST_CASE("conversions")
|
||||
{
|
||||
IOUAmount x{5, 6};
|
||||
Number y = x;
|
||||
CHECK((y == Number{5, 6}));
|
||||
IOUAmount z{y};
|
||||
CHECK(x == z);
|
||||
XRPAmount xrp{500};
|
||||
STAmount st = xrp;
|
||||
Number n = st;
|
||||
CHECK(XRPAmount{n} == xrp);
|
||||
IOUAmount x0{0, 0};
|
||||
Number y0 = x0;
|
||||
CHECK((y0 == Number{0}));
|
||||
IOUAmount z0{y0};
|
||||
CHECK(x0 == z0);
|
||||
XRPAmount xrp0{0};
|
||||
Number n0 = xrp0;
|
||||
CHECK(n0 == Number{0});
|
||||
XRPAmount xrp1{n0};
|
||||
CHECK(xrp1 == xrp0);
|
||||
}
|
||||
|
||||
TEST_CASE("squelch")
|
||||
{
|
||||
Number limit{1, -6};
|
||||
CHECK((squelch(Number{2, -6}, limit) == Number{2, -6}));
|
||||
CHECK((squelch(Number{1, -6}, limit) == Number{1, -6}));
|
||||
CHECK((squelch(Number{9, -7}, limit) == Number{0}));
|
||||
CHECK((squelch(Number{-2, -6}, limit) == Number{-2, -6}));
|
||||
CHECK((squelch(Number{-1, -6}, limit) == Number{-1, -6}));
|
||||
CHECK((squelch(Number{-9, -7}, limit) == Number{0}));
|
||||
}
|
||||
|
||||
TEST_CASE("toString")
|
||||
{
|
||||
CHECK(to_string(Number(-2, 0)) == "-2");
|
||||
CHECK(to_string(Number(0, 0)) == "0");
|
||||
CHECK(to_string(Number(2, 0)) == "2");
|
||||
CHECK(to_string(Number(25, -3)) == "0.025");
|
||||
CHECK(to_string(Number(-25, -3)) == "-0.025");
|
||||
CHECK(to_string(Number(25, 1)) == "250");
|
||||
CHECK(to_string(Number(-25, 1)) == "-250");
|
||||
CHECK(to_string(Number(2, 20)) == "2000000000000000e5");
|
||||
CHECK(to_string(Number(-2, -20)) == "-2000000000000000e-35");
|
||||
}
|
||||
|
||||
TEST_CASE("relationals")
|
||||
{
|
||||
CHECK(!(Number{100} < Number{10}));
|
||||
CHECK(Number{100} > Number{10});
|
||||
CHECK(Number{100} >= Number{10});
|
||||
CHECK(!(Number{100} <= Number{10}));
|
||||
}
|
||||
|
||||
TEST_CASE("stream")
|
||||
{
|
||||
Number x{100};
|
||||
std::ostringstream os;
|
||||
os << x;
|
||||
CHECK(os.str() == to_string(x));
|
||||
}
|
||||
|
||||
TEST_CASE("inc_dec")
|
||||
{
|
||||
Number x{100};
|
||||
Number y = +x;
|
||||
CHECK(x == y);
|
||||
CHECK(x++ == y);
|
||||
CHECK(x == Number{101});
|
||||
CHECK(x-- == Number{101});
|
||||
CHECK(x == y);
|
||||
}
|
||||
|
||||
TEST_CASE("toSTAmount")
|
||||
{
|
||||
NumberSO stNumberSO{true};
|
||||
Issue const issue;
|
||||
Number const n{7'518'783'80596, -5};
|
||||
saveNumberRoundMode const save{Number::setround(Number::to_nearest)};
|
||||
auto res2 = STAmount{issue, n.mantissa(), n.exponent()};
|
||||
CHECK(res2 == STAmount{7518784});
|
||||
|
||||
Number::setround(Number::towards_zero);
|
||||
res2 = STAmount{issue, n.mantissa(), n.exponent()};
|
||||
CHECK(res2 == STAmount{7518783});
|
||||
|
||||
Number::setround(Number::downward);
|
||||
res2 = STAmount{issue, n.mantissa(), n.exponent()};
|
||||
CHECK(res2 == STAmount{7518783});
|
||||
|
||||
Number::setround(Number::upward);
|
||||
res2 = STAmount{issue, n.mantissa(), n.exponent()};
|
||||
CHECK(res2 == STAmount{7518784});
|
||||
}
|
||||
|
||||
TEST_CASE("truncate")
|
||||
{
|
||||
CHECK(Number(25, +1).truncate() == Number(250, 0));
|
||||
CHECK(Number(25, 0).truncate() == Number(25, 0));
|
||||
CHECK(Number(25, -1).truncate() == Number(2, 0));
|
||||
CHECK(Number(25, -2).truncate() == Number(0, 0));
|
||||
CHECK(Number(99, -2).truncate() == Number(0, 0));
|
||||
|
||||
CHECK(Number(-25, +1).truncate() == Number(-250, 0));
|
||||
CHECK(Number(-25, 0).truncate() == Number(-25, 0));
|
||||
CHECK(Number(-25, -1).truncate() == Number(-2, 0));
|
||||
CHECK(Number(-25, -2).truncate() == Number(0, 0));
|
||||
CHECK(Number(-99, -2).truncate() == Number(0, 0));
|
||||
|
||||
CHECK(Number(0, 0).truncate() == Number(0, 0));
|
||||
CHECK(Number(0, 30000).truncate() == Number(0, 0));
|
||||
CHECK(Number(0, -30000).truncate() == Number(0, 0));
|
||||
CHECK(Number(100, -30000).truncate() == Number(0, 0));
|
||||
CHECK(Number(100, -30000).truncate() == Number(0, 0));
|
||||
CHECK(Number(-100, -30000).truncate() == Number(0, 0));
|
||||
CHECK(Number(-100, -30000).truncate() == Number(0, 0));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
284
src/doctest/basics/StringUtilities.cpp
Normal file
284
src/doctest/basics/StringUtilities.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/ToString.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
namespace {
|
||||
void
|
||||
testUnHexSuccess(std::string const& strIn, std::string const& strExpected)
|
||||
{
|
||||
auto rv = strUnHex(strIn);
|
||||
CHECK(rv);
|
||||
CHECK(makeSlice(*rv) == makeSlice(strExpected));
|
||||
}
|
||||
|
||||
void
|
||||
testUnHexFailure(std::string const& strIn)
|
||||
{
|
||||
auto rv = strUnHex(strIn);
|
||||
CHECK(!rv);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_SUITE_BEGIN("StringUtilities");
|
||||
|
||||
TEST_CASE("strUnHex")
|
||||
{
|
||||
testUnHexSuccess("526970706c6544", "RippleD");
|
||||
testUnHexSuccess("A", "\n");
|
||||
testUnHexSuccess("0A", "\n");
|
||||
testUnHexSuccess("D0A", "\r\n");
|
||||
testUnHexSuccess("0D0A", "\r\n");
|
||||
testUnHexSuccess("200D0A", " \r\n");
|
||||
testUnHexSuccess("282A2B2C2D2E2F29", "(*+,-./)");
|
||||
|
||||
// Check for things which contain some or only invalid characters
|
||||
testUnHexFailure("123X");
|
||||
testUnHexFailure("V");
|
||||
testUnHexFailure("XRP");
|
||||
}
|
||||
|
||||
TEST_CASE("parseUrl")
|
||||
{
|
||||
// Expected passes.
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain.empty());
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path.empty());
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme:///"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain.empty());
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "lower://domain"));
|
||||
CHECK(pUrl.scheme == "lower");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path.empty());
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "UPPER://domain:234/"));
|
||||
CHECK(pUrl.scheme == "upper");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(*pUrl.port == 234);
|
||||
CHECK(pUrl.path == "/");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "Mixed://domain/path"));
|
||||
CHECK(pUrl.scheme == "mixed");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/path");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://[::1]:123/path"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "::1");
|
||||
CHECK(*pUrl.port == 123);
|
||||
CHECK(pUrl.path == "/path");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://user:pass@domain:123/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username == "user");
|
||||
CHECK(pUrl.password == "pass");
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(*pUrl.port == 123);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://user@domain:123/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username == "user");
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(*pUrl.port == 123);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://:pass@domain:123/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password == "pass");
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(*pUrl.port == 123);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://domain:123/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(*pUrl.port == 123);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://user:pass@domain/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username == "user");
|
||||
CHECK(pUrl.password == "pass");
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://user@domain/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username == "user");
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://:pass@domain/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password == "pass");
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://domain/abc:321"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme:///path/to/file"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain.empty());
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/path/to/file");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://user:pass@domain/path/with/an@sign"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username == "user");
|
||||
CHECK(pUrl.password == "pass");
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/path/with/an@sign");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://domain/path/with/an@sign"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "domain");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/path/with/an@sign");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "scheme://:999/"));
|
||||
CHECK(pUrl.scheme == "scheme");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == ":999");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/");
|
||||
}
|
||||
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(parseUrl(pUrl, "http://::1:1234/validators"));
|
||||
CHECK(pUrl.scheme == "http");
|
||||
CHECK(pUrl.username.empty());
|
||||
CHECK(pUrl.password.empty());
|
||||
CHECK(pUrl.domain == "::0.1.18.52");
|
||||
CHECK(!pUrl.port);
|
||||
CHECK(pUrl.path == "/validators");
|
||||
}
|
||||
|
||||
// Expected fails.
|
||||
{
|
||||
parsedURL pUrl;
|
||||
CHECK(!parseUrl(pUrl, ""));
|
||||
CHECK(!parseUrl(pUrl, "nonsense"));
|
||||
CHECK(!parseUrl(pUrl, "://"));
|
||||
CHECK(!parseUrl(pUrl, ":///"));
|
||||
CHECK(!parseUrl(pUrl, "scheme://user:pass@domain:65536/abc:321"));
|
||||
CHECK(!parseUrl(pUrl, "UPPER://domain:23498765/"));
|
||||
CHECK(!parseUrl(pUrl, "UPPER://domain:0/"));
|
||||
CHECK(!parseUrl(pUrl, "UPPER://domain:+7/"));
|
||||
CHECK(!parseUrl(pUrl, "UPPER://domain:-7234/"));
|
||||
CHECK(!parseUrl(pUrl, "UPPER://domain:@#$56!/"));
|
||||
}
|
||||
|
||||
{
|
||||
std::string strUrl("s://" + std::string(8192, ':'));
|
||||
parsedURL pUrl;
|
||||
CHECK(!parseUrl(pUrl, strUrl));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("toString")
|
||||
{
|
||||
auto result = to_string("hello");
|
||||
CHECK(result == "hello");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
119
src/doctest/basics/TaggedCache.cpp
Normal file
119
src/doctest/basics/TaggedCache.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/TaggedCache.ipp>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("TaggedCache");
|
||||
|
||||
TEST_CASE("TaggedCache operations")
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TestStopwatch clock;
|
||||
clock.set(0);
|
||||
|
||||
using Key = LedgerIndex;
|
||||
using Value = std::string;
|
||||
using Cache = TaggedCache<Key, Value>;
|
||||
|
||||
beast::Journal j{beast::Journal::getNullSink()};
|
||||
|
||||
Cache c("test", 1, 1s, clock, j);
|
||||
|
||||
SUBCASE("Insert, retrieve, and age item")
|
||||
{
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 0);
|
||||
CHECK(!c.insert(1, "one"));
|
||||
CHECK(c.getCacheSize() == 1);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
|
||||
{
|
||||
std::string s;
|
||||
CHECK(c.retrieve(1, s));
|
||||
CHECK(s == "one");
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Insert item, maintain strong pointer, age it")
|
||||
{
|
||||
CHECK(!c.insert(2, "two"));
|
||||
CHECK(c.getCacheSize() == 1);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
|
||||
{
|
||||
auto p = c.fetch(2);
|
||||
CHECK(p != nullptr);
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
}
|
||||
|
||||
// Make sure its gone now that our reference is gone
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Insert same key/value pair and canonicalize")
|
||||
{
|
||||
CHECK(!c.insert(3, "three"));
|
||||
|
||||
{
|
||||
auto const p1 = c.fetch(3);
|
||||
auto p2 = std::make_shared<Value>("three");
|
||||
c.canonicalize_replace_client(3, p2);
|
||||
CHECK(p1.get() == p2.get());
|
||||
}
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 0);
|
||||
}
|
||||
|
||||
SUBCASE("Put object, keep strong pointer, advance clock, canonicalize")
|
||||
{
|
||||
// Put an object in
|
||||
CHECK(!c.insert(4, "four"));
|
||||
CHECK(c.getCacheSize() == 1);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
|
||||
{
|
||||
// Keep a strong pointer to it
|
||||
auto const p1 = c.fetch(4);
|
||||
CHECK(p1 != nullptr);
|
||||
CHECK(c.getCacheSize() == 1);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
// Canonicalize a new object with the same key
|
||||
auto p2 = std::make_shared<std::string>("four");
|
||||
CHECK(c.canonicalize_replace_client(4, p2));
|
||||
CHECK(c.getCacheSize() == 1);
|
||||
CHECK(c.getTrackSize() == 1);
|
||||
// Make sure we get the original object
|
||||
CHECK(p1.get() == p2.get());
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
CHECK(c.getCacheSize() == 0);
|
||||
CHECK(c.getTrackSize() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
332
src/doctest/basics/Units.cpp
Normal file
332
src/doctest/basics/Units.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("Units");
|
||||
|
||||
TEST_CASE("Initial XRP")
|
||||
{
|
||||
CHECK(INITIAL_XRP.drops() == 100'000'000'000'000'000);
|
||||
CHECK(INITIAL_XRP == XRPAmount{100'000'000'000'000'000});
|
||||
}
|
||||
|
||||
TEST_CASE("Types")
|
||||
{
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
SUBCASE("XRPAmount with uint32 FeeLevel")
|
||||
{
|
||||
XRPAmount x{100};
|
||||
CHECK(x.drops() == 100);
|
||||
CHECK((std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
CHECK(y.value() == 400);
|
||||
CHECK((std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
auto z = 4 * y;
|
||||
CHECK(z.value() == 1600);
|
||||
CHECK((std::is_same_v<decltype(z)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel32 f{10};
|
||||
FeeLevel32 baseFee{100};
|
||||
|
||||
auto drops = mulDiv(baseFee, x, f);
|
||||
|
||||
CHECK(drops);
|
||||
CHECK(drops.value() == 1000);
|
||||
CHECK((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
unit::dropTag>));
|
||||
|
||||
CHECK((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>,
|
||||
XRPAmount>));
|
||||
}
|
||||
|
||||
SUBCASE("XRPAmount with uint64 FeeLevel")
|
||||
{
|
||||
XRPAmount x{100};
|
||||
CHECK(x.value() == 100);
|
||||
CHECK((std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
CHECK(y.value() == 400);
|
||||
CHECK((std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel64 f{10};
|
||||
FeeLevel64 baseFee{100};
|
||||
|
||||
auto drops = mulDiv(baseFee, x, f);
|
||||
|
||||
CHECK(drops);
|
||||
CHECK(drops.value() == 1000);
|
||||
CHECK((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
unit::dropTag>));
|
||||
CHECK((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>,
|
||||
XRPAmount>));
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevel64 operations")
|
||||
{
|
||||
FeeLevel64 x{1024};
|
||||
CHECK(x.value() == 1024);
|
||||
CHECK((std::is_same_v<decltype(x)::unit_type, unit::feelevelTag>));
|
||||
std::uint64_t m = 4;
|
||||
auto y = m * x;
|
||||
CHECK(y.value() == 4096);
|
||||
CHECK((std::is_same_v<decltype(y)::unit_type, unit::feelevelTag>));
|
||||
|
||||
XRPAmount basefee{10};
|
||||
FeeLevel64 referencefee{256};
|
||||
|
||||
auto drops = mulDiv(x, basefee, referencefee);
|
||||
|
||||
CHECK(drops);
|
||||
CHECK(drops.value() == 40);
|
||||
CHECK((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
unit::dropTag>));
|
||||
CHECK((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>,
|
||||
XRPAmount>));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Json")
|
||||
{
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
SUBCASE("FeeLevel32 max")
|
||||
{
|
||||
FeeLevel32 x{std::numeric_limits<std::uint32_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::uintValue);
|
||||
CHECK(y == Json::Value{x.fee()});
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevel32 min")
|
||||
{
|
||||
FeeLevel32 x{std::numeric_limits<std::uint32_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::uintValue);
|
||||
CHECK(y == Json::Value{x.fee()});
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevel64 max")
|
||||
{
|
||||
FeeLevel64 x{std::numeric_limits<std::uint64_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::uintValue);
|
||||
CHECK(y == Json::Value{std::numeric_limits<std::uint32_t>::max()});
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevel64 min")
|
||||
{
|
||||
FeeLevel64 x{std::numeric_limits<std::uint64_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::uintValue);
|
||||
CHECK(y == Json::Value{0});
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevelDouble max")
|
||||
{
|
||||
FeeLevelDouble x{std::numeric_limits<double>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::realValue);
|
||||
CHECK(y == Json::Value{std::numeric_limits<double>::max()});
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevelDouble min")
|
||||
{
|
||||
FeeLevelDouble x{std::numeric_limits<double>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::realValue);
|
||||
CHECK(y == Json::Value{std::numeric_limits<double>::min()});
|
||||
}
|
||||
|
||||
SUBCASE("XRPAmount max")
|
||||
{
|
||||
XRPAmount x{std::numeric_limits<std::int64_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::intValue);
|
||||
CHECK(y == Json::Value{std::numeric_limits<std::int32_t>::max()});
|
||||
}
|
||||
|
||||
SUBCASE("XRPAmount min")
|
||||
{
|
||||
XRPAmount x{std::numeric_limits<std::int64_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
CHECK(y.type() == Json::intValue);
|
||||
CHECK(y == Json::Value{std::numeric_limits<std::int32_t>::min()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Functions")
|
||||
{
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
SUBCASE("FeeLevel64 functions")
|
||||
{
|
||||
auto make = [&](auto x) -> FeeLevel64 { return x; };
|
||||
auto explicitmake = [&](auto x) -> FeeLevel64 { return FeeLevel64{x}; };
|
||||
|
||||
[[maybe_unused]] FeeLevel64 defaulted;
|
||||
FeeLevel64 test{0};
|
||||
CHECK(test.fee() == 0);
|
||||
|
||||
test = explicitmake(beast::zero);
|
||||
CHECK(test.fee() == 0);
|
||||
|
||||
test = beast::zero;
|
||||
CHECK(test.fee() == 0);
|
||||
|
||||
test = explicitmake(100u);
|
||||
CHECK(test.fee() == 100);
|
||||
|
||||
FeeLevel64 const targetSame{200u};
|
||||
FeeLevel32 const targetOther{300u};
|
||||
test = make(targetSame);
|
||||
CHECK(test.fee() == 200);
|
||||
CHECK(test == targetSame);
|
||||
CHECK(test < FeeLevel64{1000});
|
||||
CHECK(test > FeeLevel64{100});
|
||||
test = make(targetOther);
|
||||
CHECK(test.fee() == 300);
|
||||
CHECK(test == targetOther);
|
||||
|
||||
test = std::uint64_t(200);
|
||||
CHECK(test.fee() == 200);
|
||||
test = std::uint32_t(300);
|
||||
CHECK(test.fee() == 300);
|
||||
|
||||
test = targetSame;
|
||||
CHECK(test.fee() == 200);
|
||||
test = targetOther.fee();
|
||||
CHECK(test.fee() == 300);
|
||||
CHECK(test == targetOther);
|
||||
|
||||
test = targetSame * 2;
|
||||
CHECK(test.fee() == 400);
|
||||
test = 3 * targetSame;
|
||||
CHECK(test.fee() == 600);
|
||||
test = targetSame / 10;
|
||||
CHECK(test.fee() == 20);
|
||||
|
||||
test += targetSame;
|
||||
CHECK(test.fee() == 220);
|
||||
|
||||
test -= targetSame;
|
||||
CHECK(test.fee() == 20);
|
||||
|
||||
test++;
|
||||
CHECK(test.fee() == 21);
|
||||
++test;
|
||||
CHECK(test.fee() == 22);
|
||||
test--;
|
||||
CHECK(test.fee() == 21);
|
||||
--test;
|
||||
CHECK(test.fee() == 20);
|
||||
|
||||
test *= 5;
|
||||
CHECK(test.fee() == 100);
|
||||
test /= 2;
|
||||
CHECK(test.fee() == 50);
|
||||
test %= 13;
|
||||
CHECK(test.fee() == 11);
|
||||
|
||||
CHECK(test);
|
||||
test = 0;
|
||||
CHECK(!test);
|
||||
CHECK(test.signum() == 0);
|
||||
test = targetSame;
|
||||
CHECK(test.signum() == 1);
|
||||
CHECK(to_string(test) == "200");
|
||||
}
|
||||
|
||||
SUBCASE("FeeLevelDouble functions")
|
||||
{
|
||||
auto make = [&](auto x) -> FeeLevelDouble { return x; };
|
||||
auto explicitmake = [&](auto x) -> FeeLevelDouble {
|
||||
return FeeLevelDouble{x};
|
||||
};
|
||||
|
||||
[[maybe_unused]] FeeLevelDouble defaulted;
|
||||
FeeLevelDouble test{0};
|
||||
CHECK(test.fee() == 0);
|
||||
|
||||
test = explicitmake(beast::zero);
|
||||
CHECK(test.fee() == 0);
|
||||
|
||||
test = beast::zero;
|
||||
CHECK(test.fee() == 0);
|
||||
|
||||
test = explicitmake(100.0);
|
||||
CHECK(test.fee() == 100);
|
||||
|
||||
FeeLevelDouble const targetSame{200.0};
|
||||
FeeLevel64 const targetOther{300};
|
||||
test = make(targetSame);
|
||||
CHECK(test.fee() == 200);
|
||||
CHECK(test == targetSame);
|
||||
CHECK(test < FeeLevelDouble{1000.0});
|
||||
CHECK(test > FeeLevelDouble{100.0});
|
||||
test = targetOther.fee();
|
||||
CHECK(test.fee() == 300);
|
||||
CHECK(test == targetOther);
|
||||
|
||||
test = 200.0;
|
||||
CHECK(test.fee() == 200);
|
||||
test = std::uint64_t(300);
|
||||
CHECK(test.fee() == 300);
|
||||
|
||||
test = targetSame;
|
||||
CHECK(test.fee() == 200);
|
||||
|
||||
test = targetSame * 2;
|
||||
CHECK(test.fee() == 400);
|
||||
test = 3 * targetSame;
|
||||
CHECK(test.fee() == 600);
|
||||
test = targetSame / 10;
|
||||
CHECK(test.fee() == 20);
|
||||
|
||||
test += targetSame;
|
||||
CHECK(test.fee() == 220);
|
||||
|
||||
test -= targetSame;
|
||||
CHECK(test.fee() == 20);
|
||||
|
||||
test++;
|
||||
CHECK(test.fee() == 21);
|
||||
++test;
|
||||
CHECK(test.fee() == 22);
|
||||
test--;
|
||||
CHECK(test.fee() == 21);
|
||||
--test;
|
||||
CHECK(test.fee() == 20);
|
||||
|
||||
test *= 5;
|
||||
CHECK(test.fee() == 100);
|
||||
test /= 2;
|
||||
CHECK(test.fee() == 50);
|
||||
|
||||
// legal with signed
|
||||
test = -test;
|
||||
CHECK(test.fee() == -50);
|
||||
CHECK(test.signum() == -1);
|
||||
CHECK(to_string(test) == "-50.000000");
|
||||
|
||||
CHECK(test);
|
||||
test = 0;
|
||||
CHECK(!test);
|
||||
CHECK(test.signum() == 0);
|
||||
test = targetSame;
|
||||
CHECK(test.signum() == 1);
|
||||
CHECK(to_string(test) == "200.000000");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
283
src/doctest/basics/XRPAmount.cpp
Normal file
283
src/doctest/basics/XRPAmount.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("XRPAmount");
|
||||
|
||||
TEST_CASE("signum")
|
||||
{
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
if (i < 0)
|
||||
CHECK(x.signum() < 0);
|
||||
else if (i > 0)
|
||||
CHECK(x.signum() > 0);
|
||||
else
|
||||
CHECK(x.signum() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("beast::Zero Comparisons")
|
||||
{
|
||||
using beast::zero;
|
||||
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
CHECK((i == 0) == (x == zero));
|
||||
CHECK((i != 0) == (x != zero));
|
||||
CHECK((i < 0) == (x < zero));
|
||||
CHECK((i > 0) == (x > zero));
|
||||
CHECK((i <= 0) == (x <= zero));
|
||||
CHECK((i >= 0) == (x >= zero));
|
||||
|
||||
CHECK((0 == i) == (zero == x));
|
||||
CHECK((0 != i) == (zero != x));
|
||||
CHECK((0 < i) == (zero < x));
|
||||
CHECK((0 > i) == (zero > x));
|
||||
CHECK((0 <= i) == (zero <= x));
|
||||
CHECK((0 >= i) == (zero >= x));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("XRP Comparisons")
|
||||
{
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
for (auto j : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const y(j);
|
||||
|
||||
CHECK((i == j) == (x == y));
|
||||
CHECK((i != j) == (x != y));
|
||||
CHECK((i < j) == (x < y));
|
||||
CHECK((i > j) == (x > y));
|
||||
CHECK((i <= j) == (x <= y));
|
||||
CHECK((i >= j) == (x >= y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Addition & Subtraction")
|
||||
{
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
for (auto j : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const y(j);
|
||||
|
||||
CHECK(XRPAmount(i + j) == (x + y));
|
||||
CHECK(XRPAmount(i - j) == (x - y));
|
||||
|
||||
CHECK((x + y) == (y + x)); // addition is commutative
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("decimalXRP")
|
||||
{
|
||||
// Tautology
|
||||
CHECK(DROPS_PER_XRP.decimalXRP() == 1);
|
||||
|
||||
XRPAmount test{1};
|
||||
CHECK(test.decimalXRP() == 0.000001);
|
||||
|
||||
test = -test;
|
||||
CHECK(test.decimalXRP() == -0.000001);
|
||||
|
||||
test = 100'000'000;
|
||||
CHECK(test.decimalXRP() == 100);
|
||||
|
||||
test = -test;
|
||||
CHECK(test.decimalXRP() == -100);
|
||||
}
|
||||
|
||||
TEST_CASE("functions")
|
||||
{
|
||||
// Explicitly test every defined function for the XRPAmount class
|
||||
// since some of them are templated, but not used anywhere else.
|
||||
auto make = [&](auto x) -> XRPAmount { return XRPAmount{x}; };
|
||||
|
||||
XRPAmount defaulted;
|
||||
(void)defaulted;
|
||||
XRPAmount test{0};
|
||||
CHECK(test.drops() == 0);
|
||||
|
||||
test = make(beast::zero);
|
||||
CHECK(test.drops() == 0);
|
||||
|
||||
test = beast::zero;
|
||||
CHECK(test.drops() == 0);
|
||||
|
||||
test = make(100);
|
||||
CHECK(test.drops() == 100);
|
||||
|
||||
test = make(100u);
|
||||
CHECK(test.drops() == 100);
|
||||
|
||||
XRPAmount const targetSame{200u};
|
||||
test = make(targetSame);
|
||||
CHECK(test.drops() == 200);
|
||||
CHECK(test == targetSame);
|
||||
CHECK(test < XRPAmount{1000});
|
||||
CHECK(test > XRPAmount{100});
|
||||
|
||||
test = std::int64_t(200);
|
||||
CHECK(test.drops() == 200);
|
||||
test = std::uint32_t(300);
|
||||
CHECK(test.drops() == 300);
|
||||
|
||||
test = targetSame;
|
||||
CHECK(test.drops() == 200);
|
||||
auto testOther = test.dropsAs<std::uint32_t>();
|
||||
CHECK(testOther);
|
||||
CHECK(*testOther == 200);
|
||||
test = std::numeric_limits<std::uint64_t>::max();
|
||||
testOther = test.dropsAs<std::uint32_t>();
|
||||
CHECK(!testOther);
|
||||
test = -1;
|
||||
testOther = test.dropsAs<std::uint32_t>();
|
||||
CHECK(!testOther);
|
||||
|
||||
test = targetSame * 2;
|
||||
CHECK(test.drops() == 400);
|
||||
test = 3 * targetSame;
|
||||
CHECK(test.drops() == 600);
|
||||
test = 20;
|
||||
CHECK(test.drops() == 20);
|
||||
|
||||
test += targetSame;
|
||||
CHECK(test.drops() == 220);
|
||||
|
||||
test -= targetSame;
|
||||
CHECK(test.drops() == 20);
|
||||
|
||||
test *= 5;
|
||||
CHECK(test.drops() == 100);
|
||||
test = 50;
|
||||
CHECK(test.drops() == 50);
|
||||
test -= 39;
|
||||
CHECK(test.drops() == 11);
|
||||
|
||||
// legal with signed
|
||||
test = -test;
|
||||
CHECK(test.drops() == -11);
|
||||
CHECK(test.signum() == -1);
|
||||
CHECK(to_string(test) == "-11");
|
||||
|
||||
CHECK(test);
|
||||
test = 0;
|
||||
CHECK(!test);
|
||||
CHECK(test.signum() == 0);
|
||||
test = targetSame;
|
||||
CHECK(test.signum() == 1);
|
||||
CHECK(to_string(test) == "200");
|
||||
}
|
||||
|
||||
TEST_CASE("mulRatio")
|
||||
{
|
||||
constexpr auto maxUInt32 = std::numeric_limits<std::uint32_t>::max();
|
||||
constexpr auto maxXRP = std::numeric_limits<XRPAmount::value_type>::max();
|
||||
constexpr auto minXRP = std::numeric_limits<XRPAmount::value_type>::min();
|
||||
|
||||
{
|
||||
// multiply by a number that would overflow then divide by the same
|
||||
// number, and check we didn't lose any value
|
||||
XRPAmount big(maxXRP);
|
||||
CHECK(big == mulRatio(big, maxUInt32, maxUInt32, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
CHECK(big == mulRatio(big, maxUInt32, maxUInt32, false));
|
||||
|
||||
// multiply and divide by values that would overflow if done
|
||||
// naively, and check that it gives the correct answer
|
||||
big -= 0xf; // Subtract a little so it's divisable by 4
|
||||
CHECK(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3);
|
||||
CHECK(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3);
|
||||
CHECK((big.value() * 3) / 4 != (big.value() / 4) * 3);
|
||||
}
|
||||
|
||||
{
|
||||
// Similar test as above, but for negative values
|
||||
XRPAmount big(minXRP);
|
||||
CHECK(big == mulRatio(big, maxUInt32, maxUInt32, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
CHECK(big == mulRatio(big, maxUInt32, maxUInt32, false));
|
||||
|
||||
// multiply and divide by values that would overflow if done
|
||||
// naively, and check that it gives the correct answer
|
||||
CHECK(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3);
|
||||
CHECK(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3);
|
||||
CHECK((big.value() * 3) / 4 != (big.value() / 4) * 3);
|
||||
}
|
||||
|
||||
{
|
||||
// small amounts
|
||||
XRPAmount tiny(1);
|
||||
// Round up should give the smallest allowable number
|
||||
CHECK(tiny == mulRatio(tiny, 1, maxUInt32, true));
|
||||
// rounding down should be zero
|
||||
CHECK(beast::zero == mulRatio(tiny, 1, maxUInt32, false));
|
||||
CHECK(beast::zero == mulRatio(tiny, maxUInt32 - 1, maxUInt32, false));
|
||||
|
||||
// tiny negative numbers
|
||||
XRPAmount tinyNeg(-1);
|
||||
// Round up should give zero
|
||||
CHECK(beast::zero == mulRatio(tinyNeg, 1, maxUInt32, true));
|
||||
CHECK(beast::zero == mulRatio(tinyNeg, maxUInt32 - 1, maxUInt32, true));
|
||||
// rounding down should be tiny
|
||||
CHECK(tinyNeg == mulRatio(tinyNeg, maxUInt32 - 1, maxUInt32, false));
|
||||
}
|
||||
|
||||
{ // rounding
|
||||
{
|
||||
XRPAmount one(1);
|
||||
auto const rup = mulRatio(one, maxUInt32 - 1, maxUInt32, true);
|
||||
auto const rdown = mulRatio(one, maxUInt32 - 1, maxUInt32, false);
|
||||
CHECK(rup.drops() - rdown.drops() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount big(maxXRP);
|
||||
auto const rup = mulRatio(big, maxUInt32 - 1, maxUInt32, true);
|
||||
auto const rdown = mulRatio(big, maxUInt32 - 1, maxUInt32, false);
|
||||
CHECK(rup.drops() - rdown.drops() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount negOne(-1);
|
||||
auto const rup = mulRatio(negOne, maxUInt32 - 1, maxUInt32, true);
|
||||
auto const rdown =
|
||||
mulRatio(negOne, maxUInt32 - 1, maxUInt32, false);
|
||||
CHECK(rup.drops() - rdown.drops() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// division by zero
|
||||
XRPAmount one(1);
|
||||
CHECK_THROWS([&] { mulRatio(one, 1, 0, true); }());
|
||||
}
|
||||
|
||||
{
|
||||
// overflow
|
||||
XRPAmount big(maxXRP);
|
||||
CHECK_THROWS([&] { mulRatio(big, 2, 1, true); }());
|
||||
}
|
||||
|
||||
{
|
||||
// underflow
|
||||
XRPAmount bigNegative(minXRP + 10);
|
||||
CHECK(mulRatio(bigNegative, 2, 1, true) == minXRP);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
432
src/doctest/basics/base58.cpp
Normal file
432
src/doctest/basics/base58.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
// base58 doctest - converted from src/test/basics/base58_test.cpp
|
||||
|
||||
#ifndef _MSC_VER
|
||||
|
||||
#include <xrpl/protocol/detail/b58_utils.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#include <boost/random.hpp>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <random>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
|
||||
namespace xrpl {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
randEngine() -> std::mt19937&
|
||||
{
|
||||
static std::mt19937 r = [] {
|
||||
std::random_device rd;
|
||||
return std::mt19937{rd()};
|
||||
}();
|
||||
return r;
|
||||
}
|
||||
|
||||
constexpr int numTokenTypeIndexes = 9;
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
tokenTypeAndSize(int i) -> std::tuple<xrpl::TokenType, std::size_t>
|
||||
{
|
||||
assert(i < numTokenTypeIndexes);
|
||||
|
||||
switch (i)
|
||||
{
|
||||
using enum xrpl::TokenType;
|
||||
case 0:
|
||||
return {None, 20};
|
||||
case 1:
|
||||
return {NodePublic, 32};
|
||||
case 2:
|
||||
return {NodePublic, 33};
|
||||
case 3:
|
||||
return {NodePrivate, 32};
|
||||
case 4:
|
||||
return {AccountID, 20};
|
||||
case 5:
|
||||
return {AccountPublic, 32};
|
||||
case 6:
|
||||
return {AccountPublic, 33};
|
||||
case 7:
|
||||
return {AccountSecret, 32};
|
||||
case 8:
|
||||
return {FamilySeed, 16};
|
||||
default:
|
||||
throw std::invalid_argument(
|
||||
"Invalid token selection passed to tokenTypeAndSize() "
|
||||
"in " __FILE__);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
randomTokenTypeAndSize() -> std::tuple<xrpl::TokenType, std::size_t>
|
||||
{
|
||||
using namespace xrpl;
|
||||
auto& rng = randEngine();
|
||||
std::uniform_int_distribution<> d(0, 8);
|
||||
return tokenTypeAndSize(d(rng));
|
||||
}
|
||||
|
||||
// Return the token type and subspan of `d` to use as test data.
|
||||
[[nodiscard]] inline auto
|
||||
randomB256TestData(std::span<std::uint8_t> d)
|
||||
-> std::tuple<xrpl::TokenType, std::span<std::uint8_t>>
|
||||
{
|
||||
auto& rng = randEngine();
|
||||
std::uniform_int_distribution<std::uint8_t> dist(0, 255);
|
||||
auto [tokType, tokSize] = randomTokenTypeAndSize();
|
||||
std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); });
|
||||
return {tokType, d.subspan(0, tokSize)};
|
||||
}
|
||||
|
||||
inline void
|
||||
printAsChar(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
|
||||
{
|
||||
auto asString = [](std::span<std::uint8_t> s) {
|
||||
std::string r;
|
||||
r.resize(s.size());
|
||||
std::copy(s.begin(), s.end(), r.begin());
|
||||
return r;
|
||||
};
|
||||
auto sa = asString(a);
|
||||
auto sb = asString(b);
|
||||
std::cerr << "\n\n" << sa << "\n" << sb << "\n";
|
||||
}
|
||||
|
||||
inline void
|
||||
printAsInt(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
|
||||
{
|
||||
auto asString = [](std::span<std::uint8_t> s) -> std::string {
|
||||
std::stringstream sstr;
|
||||
for (auto i : s)
|
||||
{
|
||||
sstr << std::setw(3) << int(i) << ',';
|
||||
}
|
||||
return sstr.str();
|
||||
};
|
||||
auto sa = asString(a);
|
||||
auto sb = asString(b);
|
||||
std::cerr << "\n\n" << sa << "\n" << sb << "\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace multiprecision_utils {
|
||||
|
||||
boost::multiprecision::checked_uint512_t
|
||||
toBoostMP(std::span<std::uint64_t> in)
|
||||
{
|
||||
boost::multiprecision::checked_uint512_t mbp = 0;
|
||||
for (auto i = in.rbegin(); i != in.rend(); ++i)
|
||||
{
|
||||
mbp <<= 64;
|
||||
mbp += *i;
|
||||
}
|
||||
return mbp;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t>
|
||||
randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5)
|
||||
{
|
||||
auto eng = randEngine();
|
||||
std::uniform_int_distribution<std::uint8_t> numCoeffDist(minSize, maxSize);
|
||||
std::uniform_int_distribution<std::uint64_t> dist;
|
||||
auto const numCoeff = numCoeffDist(eng);
|
||||
std::vector<std::uint64_t> coeffs;
|
||||
coeffs.reserve(numCoeff);
|
||||
for (int i = 0; i < numCoeff; ++i)
|
||||
{
|
||||
coeffs.push_back(dist(eng));
|
||||
}
|
||||
return coeffs;
|
||||
}
|
||||
} // namespace multiprecision_utils
|
||||
|
||||
} // namespace test
|
||||
} // namespace xrpl
|
||||
|
||||
TEST_SUITE_BEGIN("base58");
|
||||
|
||||
TEST_CASE("b58_multiprecision")
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
using namespace xrpl::test;
|
||||
using namespace xrpl;
|
||||
|
||||
constexpr std::size_t iters = 100000;
|
||||
auto eng = randEngine();
|
||||
std::uniform_int_distribution<std::uint64_t> dist;
|
||||
std::uniform_int_distribution<std::uint64_t> dist1(1);
|
||||
|
||||
for (int i = 0; i < iters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
if (!d)
|
||||
continue;
|
||||
auto bigInt = multiprecision_utils::randomBigInt();
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refDiv = boostBigInt / d;
|
||||
auto const refMod = boostBigInt % d;
|
||||
|
||||
auto const mod = b58_fast::detail::inplace_bigint_div_rem(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
auto const foundDiv = multiprecision_utils::toBoostMP(bigInt);
|
||||
CHECK(refMod.convert_to<std::uint64_t>() == mod);
|
||||
CHECK(foundDiv == refDiv);
|
||||
}
|
||||
for (int i = 0; i < iters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2);
|
||||
if (bigInt[bigInt.size() - 1] ==
|
||||
std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
bigInt[bigInt.size() - 1] -= 1; // Prevent overflow
|
||||
}
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refAdd = boostBigInt + d;
|
||||
|
||||
auto const result = b58_fast::detail::inplace_bigint_add(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
CHECK(result == TokenCodecErrc::success);
|
||||
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
|
||||
CHECK(refAdd == foundAdd);
|
||||
}
|
||||
for (int i = 0; i < iters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist1(eng);
|
||||
// Force overflow
|
||||
std::vector<std::uint64_t> bigInt(
|
||||
5, std::numeric_limits<std::uint64_t>::max());
|
||||
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refAdd = boostBigInt + d;
|
||||
|
||||
auto const result = b58_fast::detail::inplace_bigint_add(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
CHECK(result == TokenCodecErrc::overflowAdd);
|
||||
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
|
||||
CHECK(refAdd != foundAdd);
|
||||
}
|
||||
for (int i = 0; i < iters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
|
||||
// inplace mul requires the most significant coeff to be zero to
|
||||
// hold the result.
|
||||
bigInt[bigInt.size() - 1] = 0;
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refMul = boostBigInt * d;
|
||||
|
||||
auto const result = b58_fast::detail::inplace_bigint_mul(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
CHECK(result == TokenCodecErrc::success);
|
||||
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
|
||||
CHECK(refMul == foundMul);
|
||||
}
|
||||
for (int i = 0; i < iters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist1(eng);
|
||||
// Force overflow
|
||||
std::vector<std::uint64_t> bigInt(
|
||||
5, std::numeric_limits<std::uint64_t>::max());
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refMul = boostBigInt * d;
|
||||
|
||||
auto const result = b58_fast::detail::inplace_bigint_mul(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
CHECK(result == TokenCodecErrc::inputTooLarge);
|
||||
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
|
||||
CHECK(refMul != foundMul);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("fast_matches_ref")
|
||||
{
|
||||
using namespace xrpl::test;
|
||||
using namespace xrpl;
|
||||
|
||||
auto testRawEncode = [&](std::span<std::uint8_t> const& b256Data) {
|
||||
std::array<std::uint8_t, 64> b58ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b58Result;
|
||||
|
||||
std::array<std::uint8_t, 64> b256ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b256Result;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b58ResultBuf[i]};
|
||||
if (i == 0)
|
||||
{
|
||||
auto const r =
|
||||
xrpl::b58_fast::detail::b256_to_b58_be(b256Data, outBuf);
|
||||
REQUIRE(r);
|
||||
b58Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::array<std::uint8_t, 128> tmpBuf;
|
||||
std::string const s = xrpl::b58_ref::detail::encodeBase58(
|
||||
b256Data.data(),
|
||||
b256Data.size(),
|
||||
tmpBuf.data(),
|
||||
tmpBuf.size());
|
||||
REQUIRE(s.size());
|
||||
b58Result[i] = outBuf.subspan(0, s.size());
|
||||
std::copy(s.begin(), s.end(), b58Result[i].begin());
|
||||
}
|
||||
}
|
||||
REQUIRE(b58Result[0].size() == b58Result[1].size());
|
||||
CHECK(
|
||||
memcmp(
|
||||
b58Result[0].data(),
|
||||
b58Result[1].data(),
|
||||
b58Result[0].size()) == 0);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{
|
||||
b256ResultBuf[i].data(), b256ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
std::string const in(
|
||||
b58Result[i].data(),
|
||||
b58Result[i].data() + b58Result[i].size());
|
||||
auto const r =
|
||||
xrpl::b58_fast::detail::b58_to_b256_be(in, outBuf);
|
||||
REQUIRE(r);
|
||||
b256Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const st(b58Result[i].begin(), b58Result[i].end());
|
||||
std::string const s = xrpl::b58_ref::detail::decodeBase58(st);
|
||||
REQUIRE(s.size());
|
||||
b256Result[i] = outBuf.subspan(0, s.size());
|
||||
std::copy(s.begin(), s.end(), b256Result[i].begin());
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(b256Result[0].size() == b256Result[1].size());
|
||||
CHECK(
|
||||
memcmp(
|
||||
b256Result[0].data(),
|
||||
b256Result[1].data(),
|
||||
b256Result[0].size()) == 0);
|
||||
};
|
||||
|
||||
auto testTokenEncode = [&](xrpl::TokenType const tokType,
|
||||
std::span<std::uint8_t> const& b256Data) {
|
||||
std::array<std::uint8_t, 64> b58ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b58Result;
|
||||
|
||||
std::array<std::uint8_t, 64> b256ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b256Result;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{
|
||||
b58ResultBuf[i].data(), b58ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
auto const r = xrpl::b58_fast::encodeBase58Token(
|
||||
tokType, b256Data, outBuf);
|
||||
REQUIRE(r);
|
||||
b58Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const s = xrpl::b58_ref::encodeBase58Token(
|
||||
tokType, b256Data.data(), b256Data.size());
|
||||
REQUIRE(s.size());
|
||||
b58Result[i] = outBuf.subspan(0, s.size());
|
||||
std::copy(s.begin(), s.end(), b58Result[i].begin());
|
||||
}
|
||||
}
|
||||
REQUIRE(b58Result[0].size() == b58Result[1].size());
|
||||
CHECK(
|
||||
memcmp(
|
||||
b58Result[0].data(),
|
||||
b58Result[1].data(),
|
||||
b58Result[0].size()) == 0);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{
|
||||
b256ResultBuf[i].data(), b256ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
std::string const in(
|
||||
b58Result[i].data(),
|
||||
b58Result[i].data() + b58Result[i].size());
|
||||
auto const r =
|
||||
xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf);
|
||||
REQUIRE(r);
|
||||
b256Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const st(b58Result[i].begin(), b58Result[i].end());
|
||||
std::string const s =
|
||||
xrpl::b58_ref::decodeBase58Token(st, tokType);
|
||||
REQUIRE(s.size());
|
||||
b256Result[i] = outBuf.subspan(0, s.size());
|
||||
std::copy(s.begin(), s.end(), b256Result[i].begin());
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(b256Result[0].size() == b256Result[1].size());
|
||||
CHECK(
|
||||
memcmp(
|
||||
b256Result[0].data(),
|
||||
b256Result[1].data(),
|
||||
b256Result[0].size()) == 0);
|
||||
};
|
||||
|
||||
auto testIt = [&](xrpl::TokenType const tokType,
|
||||
std::span<std::uint8_t> const& b256Data) {
|
||||
testRawEncode(b256Data);
|
||||
testTokenEncode(tokType, b256Data);
|
||||
};
|
||||
|
||||
// test every token type with data where every byte is the same and the
|
||||
// bytes range from 0-255
|
||||
for (int i = 0; i < numTokenTypeIndexes; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 128> b256DataBuf;
|
||||
auto const [tokType, tokSize] = tokenTypeAndSize(i);
|
||||
for (int d = 0; d <= 255; ++d)
|
||||
{
|
||||
memset(b256DataBuf.data(), d, tokSize);
|
||||
testIt(tokType, std::span(b256DataBuf.data(), tokSize));
|
||||
}
|
||||
}
|
||||
|
||||
// test with random data
|
||||
constexpr std::size_t iters = 100000;
|
||||
for (int i = 0; i < iters; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 128> b256DataBuf;
|
||||
auto const [tokType, b256Data] = randomB256TestData(b256DataBuf);
|
||||
testIt(tokType, b256Data);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
#endif // _MSC_VER
|
||||
323
src/doctest/basics/base_uint.cpp
Normal file
323
src/doctest/basics/base_uint.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <complex>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
// a non-hashing Hasher that just copies the bytes.
|
||||
// Used to test hash_append in base_uint
|
||||
template <std::size_t Bits>
|
||||
struct nonhash
|
||||
{
|
||||
static constexpr auto const endian = boost::endian::order::big;
|
||||
static constexpr std::size_t WIDTH = Bits / 8;
|
||||
|
||||
std::array<std::uint8_t, WIDTH> data_;
|
||||
|
||||
nonhash() = default;
|
||||
|
||||
void
|
||||
operator()(void const* key, std::size_t len) noexcept
|
||||
{
|
||||
assert(len == WIDTH);
|
||||
memcpy(data_.data(), key, len);
|
||||
}
|
||||
|
||||
explicit
|
||||
operator std::size_t() noexcept
|
||||
{
|
||||
return WIDTH;
|
||||
}
|
||||
};
|
||||
|
||||
using test96 = base_uint<96>;
|
||||
static_assert(std::is_copy_constructible<test96>::value);
|
||||
static_assert(std::is_copy_assignable<test96>::value);
|
||||
|
||||
TEST_SUITE_BEGIN("base_uint");
|
||||
|
||||
TEST_CASE("comparisons 64-bit")
|
||||
{
|
||||
static constexpr std::
|
||||
array<std::pair<std::string_view, std::string_view>, 6>
|
||||
test_args{
|
||||
{{"0000000000000000", "0000000000000001"},
|
||||
{"0000000000000000", "ffffffffffffffff"},
|
||||
{"1234567812345678", "2345678923456789"},
|
||||
{"8000000000000000", "8000000000000001"},
|
||||
{"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"},
|
||||
{"fffffffffffffffe", "ffffffffffffffff"}}};
|
||||
|
||||
for (auto const& arg : test_args)
|
||||
{
|
||||
xrpl::base_uint<64> const u{arg.first}, v{arg.second};
|
||||
CHECK(u < v);
|
||||
CHECK(u <= v);
|
||||
CHECK(u != v);
|
||||
CHECK(!(u == v));
|
||||
CHECK(!(u > v));
|
||||
CHECK(!(u >= v));
|
||||
CHECK(!(v < u));
|
||||
CHECK(!(v <= u));
|
||||
CHECK(v != u);
|
||||
CHECK(!(v == u));
|
||||
CHECK(v > u);
|
||||
CHECK(v >= u);
|
||||
CHECK(u == u);
|
||||
CHECK(v == v);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("comparisons 96-bit")
|
||||
{
|
||||
static constexpr std::
|
||||
array<std::pair<std::string_view, std::string_view>, 6>
|
||||
test_args{{
|
||||
{"000000000000000000000000", "000000000000000000000001"},
|
||||
{"000000000000000000000000", "ffffffffffffffffffffffff"},
|
||||
{"0123456789ab0123456789ab", "123456789abc123456789abc"},
|
||||
{"555555555555555555555555", "55555555555a555555555555"},
|
||||
{"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
{"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"},
|
||||
}};
|
||||
|
||||
for (auto const& arg : test_args)
|
||||
{
|
||||
xrpl::base_uint<96> const u{arg.first}, v{arg.second};
|
||||
CHECK(u < v);
|
||||
CHECK(u <= v);
|
||||
CHECK(u != v);
|
||||
CHECK(!(u == v));
|
||||
CHECK(!(u > v));
|
||||
CHECK(!(u >= v));
|
||||
CHECK(!(v < u));
|
||||
CHECK(!(v <= u));
|
||||
CHECK(v != u);
|
||||
CHECK(!(v == u));
|
||||
CHECK(v > u);
|
||||
CHECK(v >= u);
|
||||
CHECK(u == u);
|
||||
CHECK(v == v);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("general purpose tests")
|
||||
{
|
||||
static_assert(!std::is_constructible<test96, std::complex<double>>::value);
|
||||
static_assert(!std::is_assignable<test96&, std::complex<double>>::value);
|
||||
|
||||
// used to verify set insertion (hashing required)
|
||||
std::unordered_set<test96, hardened_hash<>> uset;
|
||||
|
||||
Blob raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
CHECK(test96::bytes == raw.size());
|
||||
|
||||
test96 u{raw};
|
||||
uset.insert(u);
|
||||
CHECK(raw.size() == u.size());
|
||||
CHECK(to_string(u) == "0102030405060708090A0B0C");
|
||||
CHECK(to_short_string(u) == "01020304...");
|
||||
CHECK(*u.data() == 1);
|
||||
CHECK(u.signum() == 1);
|
||||
CHECK(!!u);
|
||||
CHECK(!u.isZero());
|
||||
CHECK(u.isNonZero());
|
||||
unsigned char t = 0;
|
||||
for (auto& d : u)
|
||||
{
|
||||
CHECK(d == ++t);
|
||||
}
|
||||
|
||||
// Test hash_append by "hashing" with a no-op hasher (h)
|
||||
// and then extracting the bytes that were written during hashing
|
||||
// back into another base_uint (w) for comparison with the original
|
||||
nonhash<96> h;
|
||||
hash_append(h, u);
|
||||
test96 w{std::vector<std::uint8_t>(h.data_.begin(), h.data_.end())};
|
||||
CHECK(w == u);
|
||||
|
||||
test96 v{~u};
|
||||
uset.insert(v);
|
||||
CHECK(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3");
|
||||
CHECK(to_short_string(v) == "FEFDFCFB...");
|
||||
CHECK(*v.data() == 0xfe);
|
||||
CHECK(v.signum() == 1);
|
||||
CHECK(!!v);
|
||||
CHECK(!v.isZero());
|
||||
CHECK(v.isNonZero());
|
||||
t = 0xff;
|
||||
for (auto& d : v)
|
||||
{
|
||||
CHECK(d == --t);
|
||||
}
|
||||
|
||||
CHECK(u < v);
|
||||
CHECK(v > u);
|
||||
|
||||
v = u;
|
||||
CHECK(v == u);
|
||||
|
||||
test96 z{beast::zero};
|
||||
uset.insert(z);
|
||||
CHECK(to_string(z) == "000000000000000000000000");
|
||||
CHECK(to_short_string(z) == "00000000...");
|
||||
CHECK(*z.data() == 0);
|
||||
CHECK(*z.begin() == 0);
|
||||
CHECK(*std::prev(z.end(), 1) == 0);
|
||||
CHECK(z.signum() == 0);
|
||||
CHECK(!z);
|
||||
CHECK(z.isZero());
|
||||
CHECK(!z.isNonZero());
|
||||
for (auto& d : z)
|
||||
{
|
||||
CHECK(d == 0);
|
||||
}
|
||||
|
||||
test96 n{z};
|
||||
n++;
|
||||
CHECK(n == test96(1));
|
||||
n--;
|
||||
CHECK(n == beast::zero);
|
||||
CHECK(n == z);
|
||||
n--;
|
||||
CHECK(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF");
|
||||
CHECK(to_short_string(n) == "FFFFFFFF...");
|
||||
n = beast::zero;
|
||||
CHECK(n == z);
|
||||
|
||||
test96 zp1{z};
|
||||
zp1++;
|
||||
test96 zm1{z};
|
||||
zm1--;
|
||||
test96 x{zm1 ^ zp1};
|
||||
uset.insert(x);
|
||||
CHECK(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE");
|
||||
CHECK(to_short_string(x) == "FFFFFFFF...");
|
||||
|
||||
CHECK(uset.size() == 4);
|
||||
|
||||
test96 tmp;
|
||||
CHECK(tmp.parseHex(to_string(u)));
|
||||
CHECK(tmp == u);
|
||||
tmp = z;
|
||||
|
||||
// fails with extra char
|
||||
CHECK(!tmp.parseHex("A" + to_string(u)));
|
||||
tmp = z;
|
||||
|
||||
// fails with extra char at end
|
||||
CHECK(!tmp.parseHex(to_string(u) + "A"));
|
||||
|
||||
// fails with a non-hex character at some point in the string:
|
||||
tmp = z;
|
||||
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string x = to_string(z);
|
||||
x[i] = ('G' + (i % 10));
|
||||
CHECK(!tmp.parseHex(x));
|
||||
}
|
||||
|
||||
// Walking 1s:
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string s1 = "000000000000000000000000";
|
||||
s1[i] = '1';
|
||||
|
||||
CHECK(tmp.parseHex(s1));
|
||||
CHECK(to_string(tmp) == s1);
|
||||
}
|
||||
|
||||
// Walking 0s:
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string s1 = "111111111111111111111111";
|
||||
s1[i] = '0';
|
||||
|
||||
CHECK(tmp.parseHex(s1));
|
||||
CHECK(to_string(tmp) == s1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("constexpr constructors")
|
||||
{
|
||||
static_assert(test96{}.signum() == 0);
|
||||
static_assert(test96("0").signum() == 0);
|
||||
static_assert(test96("000000000000000000000000").signum() == 0);
|
||||
static_assert(test96("000000000000000000000001").signum() == 1);
|
||||
static_assert(test96("800000000000000000000000").signum() == 1);
|
||||
|
||||
// Using the constexpr constructor in a non-constexpr context
|
||||
// with an error in the parsing throws an exception.
|
||||
{
|
||||
// Invalid length for string.
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
// Try to prevent constant evaluation.
|
||||
std::vector<char> str(23, '7');
|
||||
std::string_view sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 t96(sView);
|
||||
}
|
||||
catch (std::invalid_argument const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("invalid length for hex string"));
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
{
|
||||
// Invalid character in string.
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
// Try to prevent constant evaluation.
|
||||
std::vector<char> str(23, '7');
|
||||
str.push_back('G');
|
||||
std::string_view sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 t96(sView);
|
||||
}
|
||||
catch (std::range_error const& e)
|
||||
{
|
||||
CHECK(e.what() == std::string("invalid hex character"));
|
||||
caught = true;
|
||||
}
|
||||
CHECK(caught);
|
||||
}
|
||||
|
||||
// Verify that constexpr base_uints interpret a string the same
|
||||
// way parseHex() does.
|
||||
struct StrBaseUint
|
||||
{
|
||||
char const* const str;
|
||||
test96 tst;
|
||||
|
||||
constexpr StrBaseUint(char const* s) : str(s), tst(s)
|
||||
{
|
||||
}
|
||||
};
|
||||
constexpr StrBaseUint testCases[] = {
|
||||
"000000000000000000000000",
|
||||
"000000000000000000000001",
|
||||
"fedcba9876543210ABCDEF91",
|
||||
"19FEDCBA0123456789abcdef",
|
||||
"800000000000000000000000",
|
||||
"fFfFfFfFfFfFfFfFfFfFfFfF"};
|
||||
|
||||
for (StrBaseUint const& t : testCases)
|
||||
{
|
||||
test96 t96;
|
||||
CHECK(t96.parseHex(t.str));
|
||||
CHECK(t96 == t.tst);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
132
src/doctest/basics/hardened_hash.cpp
Normal file
132
src/doctest/basics/hardened_hash.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
namespace {
|
||||
|
||||
template <class T>
|
||||
class test_user_type_member
|
||||
{
|
||||
private:
|
||||
T t;
|
||||
|
||||
public:
|
||||
explicit test_user_type_member(T const& t_ = T()) : t(t_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
friend void
|
||||
hash_append(Hasher& h, test_user_type_member const& a) noexcept
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, a.t);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class test_user_type_free
|
||||
{
|
||||
private:
|
||||
T t;
|
||||
|
||||
public:
|
||||
explicit test_user_type_free(T const& t_ = T()) : t(t_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
friend void
|
||||
hash_append(Hasher& h, test_user_type_free const& a) noexcept
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, a.t);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using test_hardened_unordered_set = std::unordered_set<T, hardened_hash<>>;
|
||||
|
||||
template <class T>
|
||||
using test_hardened_unordered_map = std::unordered_map<T, int, hardened_hash<>>;
|
||||
|
||||
template <class T>
|
||||
using test_hardened_unordered_multiset =
|
||||
std::unordered_multiset<T, hardened_hash<>>;
|
||||
|
||||
template <class T>
|
||||
using test_hardened_unordered_multimap =
|
||||
std::unordered_multimap<T, int, hardened_hash<>>;
|
||||
|
||||
template <class T>
|
||||
void
|
||||
check()
|
||||
{
|
||||
T t{};
|
||||
hardened_hash<>()(t);
|
||||
}
|
||||
|
||||
template <template <class T> class U>
|
||||
void
|
||||
check_user_type()
|
||||
{
|
||||
check<U<bool>>();
|
||||
check<U<char>>();
|
||||
check<U<signed char>>();
|
||||
check<U<unsigned char>>();
|
||||
check<U<wchar_t>>();
|
||||
check<U<short>>();
|
||||
check<U<unsigned short>>();
|
||||
check<U<int>>();
|
||||
check<U<unsigned int>>();
|
||||
check<U<long>>();
|
||||
check<U<long long>>();
|
||||
check<U<unsigned long>>();
|
||||
check<U<unsigned long long>>();
|
||||
check<U<float>>();
|
||||
check<U<double>>();
|
||||
check<U<long double>>();
|
||||
}
|
||||
|
||||
template <template <class T> class C>
|
||||
void
|
||||
check_container()
|
||||
{
|
||||
{
|
||||
C<test_user_type_member<std::string>> c;
|
||||
(void)c;
|
||||
}
|
||||
|
||||
{
|
||||
C<test_user_type_free<std::string>> c;
|
||||
(void)c;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_SUITE_BEGIN("hardened_hash");
|
||||
|
||||
TEST_CASE("user types")
|
||||
{
|
||||
check_user_type<test_user_type_member>();
|
||||
check_user_type<test_user_type_free>();
|
||||
}
|
||||
|
||||
TEST_CASE("containers")
|
||||
{
|
||||
check_container<test_hardened_unordered_set>();
|
||||
check_container<test_hardened_unordered_map>();
|
||||
check_container<test_hardened_unordered_multiset>();
|
||||
check_container<test_hardened_unordered_multimap>();
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
77
src/doctest/basics/join.cpp
Normal file
77
src/doctest/basics/join.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/join.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("join");
|
||||
|
||||
TEST_CASE("CollectionAndDelimiter")
|
||||
{
|
||||
auto test = [](auto collectionanddelimiter, std::string expected) {
|
||||
std::stringstream ss;
|
||||
// Put something else in the buffer before and after to ensure that
|
||||
// the << operator returns the stream correctly.
|
||||
ss << "(" << collectionanddelimiter << ")";
|
||||
auto const str = ss.str();
|
||||
CHECK(str.substr(1, str.length() - 2) == expected);
|
||||
CHECK(str.front() == '(');
|
||||
CHECK(str.back() == ')');
|
||||
};
|
||||
|
||||
// C++ array
|
||||
test(
|
||||
CollectionAndDelimiter(std::array<int, 4>{2, -1, 5, 10}, "/"),
|
||||
"2/-1/5/10");
|
||||
// One item C++ array edge case
|
||||
test(
|
||||
CollectionAndDelimiter(std::array<std::string, 1>{"test"}, " & "),
|
||||
"test");
|
||||
// Empty C++ array edge case
|
||||
test(CollectionAndDelimiter(std::array<int, 0>{}, ","), "");
|
||||
{
|
||||
// C-style array
|
||||
char letters[4]{'w', 'a', 's', 'd'};
|
||||
test(CollectionAndDelimiter(letters, std::to_string(0)), "w0a0s0d");
|
||||
}
|
||||
{
|
||||
// Auto sized C-style array
|
||||
std::string words[]{"one", "two", "three", "four"};
|
||||
test(CollectionAndDelimiter(words, "\n"), "one\ntwo\nthree\nfour");
|
||||
}
|
||||
{
|
||||
// One item C-style array edge case
|
||||
std::string words[]{"thing"};
|
||||
test(CollectionAndDelimiter(words, "\n"), "thing");
|
||||
}
|
||||
// Initializer list
|
||||
test(
|
||||
CollectionAndDelimiter(std::initializer_list<size_t>{19, 25}, "+"),
|
||||
"19+25");
|
||||
// vector
|
||||
test(
|
||||
CollectionAndDelimiter(std::vector<int>{0, 42}, std::to_string(99)),
|
||||
"09942");
|
||||
// empty vector edge case
|
||||
test(CollectionAndDelimiter(std::vector<uint256>{}, ","), "");
|
||||
// C-style string
|
||||
test(CollectionAndDelimiter("string", " "), "s t r i n g");
|
||||
// Empty C-style string edge case
|
||||
test(CollectionAndDelimiter("", "*"), "");
|
||||
// Single char C-style string edge case
|
||||
test(CollectionAndDelimiter("x", "*"), "x");
|
||||
// std::string
|
||||
test(CollectionAndDelimiter(std::string{"string"}, "-"), "s-t-r-i-n-g");
|
||||
// Empty std::string edge case
|
||||
test(CollectionAndDelimiter(std::string{""}, "*"), "");
|
||||
// Single char std::string edge case
|
||||
test(CollectionAndDelimiter(std::string{"y"}, "*"), "y");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
2
src/doctest/basics/main.cpp
Normal file
2
src/doctest/basics/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
69
src/doctest/beast/CurrentThreadName.cpp
Normal file
69
src/doctest/beast/CurrentThreadName.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
TEST_SUITE_BEGIN("CurrentThreadName");
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
exerciseName(
|
||||
std::string myName,
|
||||
std::atomic<bool>* stop,
|
||||
std::atomic<int>* state)
|
||||
{
|
||||
// Verify that upon creation a thread has no name.
|
||||
auto const initialThreadName = beast::getCurrentThreadName();
|
||||
|
||||
// Set the new name.
|
||||
beast::setCurrentThreadName(myName);
|
||||
|
||||
// Indicate to caller that the name is set.
|
||||
*state = 1;
|
||||
|
||||
// If there is an initial thread name then we failed.
|
||||
if (!initialThreadName.empty())
|
||||
return;
|
||||
|
||||
// Wait until all threads have their names.
|
||||
while (!*stop)
|
||||
;
|
||||
|
||||
// Make sure the thread name that we set before is still there
|
||||
// (not overwritten by, for instance, another thread).
|
||||
if (beast::getCurrentThreadName() == myName)
|
||||
*state = 2;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Thread names are preserved")
|
||||
{
|
||||
// Make two different threads with two different names. Make sure
|
||||
// that the expected thread names are still there when the thread
|
||||
// exits.
|
||||
std::atomic<bool> stop{false};
|
||||
|
||||
std::atomic<int> stateA{0};
|
||||
std::thread tA(exerciseName, "tA", &stop, &stateA);
|
||||
|
||||
std::atomic<int> stateB{0};
|
||||
std::thread tB(exerciseName, "tB", &stop, &stateB);
|
||||
|
||||
// Wait until both threads have set their names.
|
||||
while (stateA == 0 || stateB == 0)
|
||||
;
|
||||
|
||||
stop = true;
|
||||
tA.join();
|
||||
tB.join();
|
||||
|
||||
// Both threads should still have the expected name when they exit.
|
||||
CHECK(stateA == 2);
|
||||
CHECK(stateB == 2);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
458
src/doctest/beast/IPEndpoint.cpp
Normal file
458
src/doctest/beast/IPEndpoint.cpp
Normal file
@@ -0,0 +1,458 @@
|
||||
// IPEndpoint doctest - converted from src/test/beast/IPEndpoint_test.cpp
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
TEST_SUITE_BEGIN("IPEndpoint");
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace beast::IP;
|
||||
|
||||
Endpoint
|
||||
randomEP(bool v4 = true)
|
||||
{
|
||||
using namespace xrpl;
|
||||
auto dv4 = []() -> AddressV4::bytes_type {
|
||||
return {
|
||||
{static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX))}};
|
||||
};
|
||||
auto dv6 = []() -> AddressV6::bytes_type {
|
||||
return {
|
||||
{static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX)),
|
||||
static_cast<std::uint8_t>(rand_int<int>(1, UINT8_MAX))}};
|
||||
};
|
||||
return Endpoint{
|
||||
v4 ? Address{AddressV4{dv4()}} : Address{AddressV6{dv6()}},
|
||||
rand_int<std::uint16_t>(1, UINT16_MAX)};
|
||||
}
|
||||
|
||||
void
|
||||
shouldParseAddrV4(
|
||||
std::string const& s,
|
||||
std::uint32_t value,
|
||||
std::string const& normal = "")
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
Address const result{boost::asio::ip::make_address(s, ec)};
|
||||
REQUIRE_MESSAGE(!ec, ec.message());
|
||||
REQUIRE_MESSAGE(result.is_v4(), s.c_str());
|
||||
REQUIRE_MESSAGE(result.to_v4().to_uint() == value, s.c_str());
|
||||
CHECK_MESSAGE(
|
||||
result.to_string() == (normal.empty() ? s : normal), s.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
failParseAddr(std::string const& s)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
auto a = boost::asio::ip::make_address(s, ec);
|
||||
CHECK_MESSAGE(ec, s.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
shouldParseEPV4(
|
||||
std::string const& s,
|
||||
AddressV4::bytes_type const& value,
|
||||
std::uint16_t p,
|
||||
std::string const& normal = "")
|
||||
{
|
||||
auto const result = Endpoint::from_string_checked(s);
|
||||
REQUIRE(result);
|
||||
REQUIRE(result->address().is_v4());
|
||||
REQUIRE(result->address().to_v4() == AddressV4{value});
|
||||
CHECK(result->port() == p);
|
||||
CHECK(to_string(*result) == (normal.empty() ? s : normal));
|
||||
}
|
||||
|
||||
void
|
||||
shouldParseEPV6(
|
||||
std::string const& s,
|
||||
AddressV6::bytes_type const& value,
|
||||
std::uint16_t p,
|
||||
std::string const& normal = "")
|
||||
{
|
||||
auto result = Endpoint::from_string_checked(s);
|
||||
REQUIRE(result);
|
||||
REQUIRE(result->address().is_v6());
|
||||
REQUIRE(result->address().to_v6() == AddressV6{value});
|
||||
CHECK(result->port() == p);
|
||||
CHECK(to_string(*result) == (normal.empty() ? s : normal));
|
||||
}
|
||||
|
||||
void
|
||||
failParseEP(std::string s)
|
||||
{
|
||||
auto a1 = Endpoint::from_string(s);
|
||||
CHECK_MESSAGE(is_unspecified(a1), s.c_str());
|
||||
|
||||
auto a2 = Endpoint::from_string(s);
|
||||
CHECK_MESSAGE(is_unspecified(a2), s.c_str());
|
||||
|
||||
boost::replace_last(s, ":", " ");
|
||||
auto a3 = Endpoint::from_string(s);
|
||||
CHECK_MESSAGE(is_unspecified(a3), s.c_str());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
parse(std::string const& text, T& t)
|
||||
{
|
||||
std::istringstream stream{text};
|
||||
stream >> t;
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
shouldPass(std::string const& text, std::string const& normal = "")
|
||||
{
|
||||
T t;
|
||||
CHECK(parse(text, t));
|
||||
CHECK_MESSAGE(
|
||||
to_string(t) == (normal.empty() ? text : normal), text.c_str());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
shouldFail(std::string const& text)
|
||||
{
|
||||
T t;
|
||||
CHECK_FALSE_MESSAGE(parse(text, t), text.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("AddressV4")
|
||||
{
|
||||
CHECK(AddressV4{}.to_uint() == 0);
|
||||
CHECK(is_unspecified(AddressV4{}));
|
||||
CHECK(AddressV4{0x01020304}.to_uint() == 0x01020304);
|
||||
|
||||
{
|
||||
AddressV4::bytes_type d = {{1, 2, 3, 4}};
|
||||
CHECK(AddressV4{d}.to_uint() == 0x01020304);
|
||||
CHECK_FALSE(is_unspecified(AddressV4{d}));
|
||||
}
|
||||
|
||||
AddressV4 const v1{1};
|
||||
CHECK(AddressV4{v1}.to_uint() == 1);
|
||||
|
||||
{
|
||||
AddressV4 v;
|
||||
v = v1;
|
||||
CHECK(v.to_uint() == v1.to_uint());
|
||||
}
|
||||
|
||||
{
|
||||
AddressV4 v;
|
||||
auto d = v.to_bytes();
|
||||
d[0] = 1;
|
||||
d[1] = 2;
|
||||
d[2] = 3;
|
||||
d[3] = 4;
|
||||
v = AddressV4{d};
|
||||
CHECK(v.to_uint() == 0x01020304);
|
||||
}
|
||||
|
||||
CHECK(AddressV4(0x01020304).to_string() == "1.2.3.4");
|
||||
|
||||
shouldParseAddrV4("1.2.3.4", 0x01020304);
|
||||
shouldParseAddrV4("255.255.255.255", 0xffffffff);
|
||||
shouldParseAddrV4("0.0.0.0", 0);
|
||||
|
||||
failParseAddr(".");
|
||||
failParseAddr("..");
|
||||
failParseAddr("...");
|
||||
failParseAddr("....");
|
||||
#if BOOST_OS_WINDOWS
|
||||
// WINDOWS bug in asio - I don't think these should parse
|
||||
// at all, and in-fact they do not on mac/linux
|
||||
shouldParseAddrV4("1", 0x00000001, "0.0.0.1");
|
||||
shouldParseAddrV4("1.2", 0x01000002, "1.0.0.2");
|
||||
shouldParseAddrV4("1.2.3", 0x01020003, "1.2.0.3");
|
||||
#else
|
||||
failParseAddr("1");
|
||||
failParseAddr("1.2");
|
||||
failParseAddr("1.2.3");
|
||||
#endif
|
||||
failParseAddr("1.");
|
||||
failParseAddr("1.2.");
|
||||
failParseAddr("1.2.3.");
|
||||
failParseAddr("256.0.0.0");
|
||||
failParseAddr("-1.2.3.4");
|
||||
}
|
||||
|
||||
TEST_CASE("AddressV4::Bytes")
|
||||
{
|
||||
AddressV4::bytes_type d1 = {{10, 0, 0, 1}};
|
||||
AddressV4 v4{d1};
|
||||
CHECK(v4.to_bytes()[0] == 10);
|
||||
CHECK(v4.to_bytes()[1] == 0);
|
||||
CHECK(v4.to_bytes()[2] == 0);
|
||||
CHECK(v4.to_bytes()[3] == 1);
|
||||
|
||||
CHECK((~((0xff) << 16)) == 0xff00ffff);
|
||||
|
||||
auto d2 = v4.to_bytes();
|
||||
d2[1] = 10;
|
||||
v4 = AddressV4{d2};
|
||||
CHECK(v4.to_bytes()[0] == 10);
|
||||
CHECK(v4.to_bytes()[1] == 10);
|
||||
CHECK(v4.to_bytes()[2] == 0);
|
||||
CHECK(v4.to_bytes()[3] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("Address")
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
Address result{boost::asio::ip::make_address("1.2.3.4", ec)};
|
||||
AddressV4::bytes_type d = {{1, 2, 3, 4}};
|
||||
CHECK(!ec);
|
||||
CHECK(result.is_v4());
|
||||
CHECK(result.to_v4() == AddressV4{d});
|
||||
}
|
||||
|
||||
TEST_CASE("Endpoint")
|
||||
{
|
||||
shouldParseEPV4("1.2.3.4", {{1, 2, 3, 4}}, 0);
|
||||
shouldParseEPV4("1.2.3.4:5", {{1, 2, 3, 4}}, 5);
|
||||
shouldParseEPV4("1.2.3.4 5", {{1, 2, 3, 4}}, 5, "1.2.3.4:5");
|
||||
// leading, trailing space
|
||||
shouldParseEPV4(" 1.2.3.4:5", {{1, 2, 3, 4}}, 5, "1.2.3.4:5");
|
||||
shouldParseEPV4("1.2.3.4:5 ", {{1, 2, 3, 4}}, 5, "1.2.3.4:5");
|
||||
shouldParseEPV4("1.2.3.4 ", {{1, 2, 3, 4}}, 0, "1.2.3.4");
|
||||
shouldParseEPV4(" 1.2.3.4", {{1, 2, 3, 4}}, 0, "1.2.3.4");
|
||||
shouldParseEPV6(
|
||||
"2001:db8:a0b:12f0::1",
|
||||
{{32, 01, 13, 184, 10, 11, 18, 240, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
0);
|
||||
shouldParseEPV6(
|
||||
"[2001:db8:a0b:12f0::1]:8",
|
||||
{{32, 01, 13, 184, 10, 11, 18, 240, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
8);
|
||||
shouldParseEPV6(
|
||||
"[2001:2002:2003:2004:2005:2006:2007:2008]:65535",
|
||||
{{32, 1, 32, 2, 32, 3, 32, 4, 32, 5, 32, 6, 32, 7, 32, 8}},
|
||||
65535);
|
||||
shouldParseEPV6(
|
||||
"2001:2002:2003:2004:2005:2006:2007:2008 65535",
|
||||
{{32, 1, 32, 2, 32, 3, 32, 4, 32, 5, 32, 6, 32, 7, 32, 8}},
|
||||
65535,
|
||||
"[2001:2002:2003:2004:2005:2006:2007:2008]:65535");
|
||||
|
||||
Endpoint ep;
|
||||
|
||||
AddressV4::bytes_type d = {{127, 0, 0, 1}};
|
||||
ep = Endpoint(AddressV4{d}, 80);
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(!is_public(ep));
|
||||
CHECK(is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(is_loopback(ep));
|
||||
CHECK(to_string(ep) == "127.0.0.1:80");
|
||||
// same address as v4 mapped in ipv6
|
||||
ep = Endpoint(
|
||||
boost::asio::ip::make_address_v6(
|
||||
boost::asio::ip::v4_mapped, AddressV4{d}),
|
||||
80);
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(!is_public(ep));
|
||||
CHECK(is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(!is_loopback(ep)); // mapped loopback is not a loopback
|
||||
CHECK(to_string(ep) == "[::ffff:127.0.0.1]:80");
|
||||
|
||||
d = {{10, 0, 0, 1}};
|
||||
ep = Endpoint(AddressV4{d});
|
||||
CHECK(get_class(ep.to_v4()) == 'A');
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(!is_public(ep));
|
||||
CHECK(is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(!is_loopback(ep));
|
||||
CHECK(to_string(ep) == "10.0.0.1");
|
||||
// same address as v4 mapped in ipv6
|
||||
ep = Endpoint(boost::asio::ip::make_address_v6(
|
||||
boost::asio::ip::v4_mapped, AddressV4{d}));
|
||||
CHECK(
|
||||
get_class(boost::asio::ip::make_address_v4(
|
||||
boost::asio::ip::v4_mapped, ep.to_v6())) == 'A');
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(!is_public(ep));
|
||||
CHECK(is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(!is_loopback(ep));
|
||||
CHECK(to_string(ep) == "::ffff:10.0.0.1");
|
||||
|
||||
d = {{166, 78, 151, 147}};
|
||||
ep = Endpoint(AddressV4{d});
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(is_public(ep));
|
||||
CHECK(!is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(!is_loopback(ep));
|
||||
CHECK(to_string(ep) == "166.78.151.147");
|
||||
// same address as v4 mapped in ipv6
|
||||
ep = Endpoint(boost::asio::ip::make_address_v6(
|
||||
boost::asio::ip::v4_mapped, AddressV4{d}));
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(is_public(ep));
|
||||
CHECK(!is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(!is_loopback(ep));
|
||||
CHECK(to_string(ep) == "::ffff:166.78.151.147");
|
||||
|
||||
// a private IPv6
|
||||
AddressV6::bytes_type d2 = {
|
||||
{253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}};
|
||||
ep = Endpoint(AddressV6{d2});
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(!is_public(ep));
|
||||
CHECK(is_private(ep));
|
||||
CHECK(!is_multicast(ep));
|
||||
CHECK(!is_loopback(ep));
|
||||
CHECK(to_string(ep) == "fd00::1");
|
||||
|
||||
{
|
||||
ep = Endpoint::from_string("192.0.2.112");
|
||||
CHECK(!is_unspecified(ep));
|
||||
CHECK(ep == Endpoint::from_string("192.0.2.112"));
|
||||
|
||||
auto const ep1 = Endpoint::from_string("192.0.2.112:2016");
|
||||
CHECK(!is_unspecified(ep1));
|
||||
CHECK(ep.address() == ep1.address());
|
||||
CHECK(ep1.port() == 2016);
|
||||
|
||||
auto const ep2 = Endpoint::from_string("192.0.2.112:2016");
|
||||
CHECK(!is_unspecified(ep2));
|
||||
CHECK(ep.address() == ep2.address());
|
||||
CHECK(ep2.port() == 2016);
|
||||
CHECK(ep1 == ep2);
|
||||
|
||||
auto const ep3 = Endpoint::from_string("192.0.2.112 2016");
|
||||
CHECK(!is_unspecified(ep3));
|
||||
CHECK(ep.address() == ep3.address());
|
||||
CHECK(ep3.port() == 2016);
|
||||
CHECK(ep2 == ep3);
|
||||
|
||||
auto const ep4 = Endpoint::from_string("192.0.2.112 2016");
|
||||
CHECK(!is_unspecified(ep4));
|
||||
CHECK(ep.address() == ep4.address());
|
||||
CHECK(ep4.port() == 2016);
|
||||
CHECK(ep3 == ep4);
|
||||
|
||||
CHECK(to_string(ep1) == to_string(ep2));
|
||||
CHECK(to_string(ep1) == to_string(ep3));
|
||||
CHECK(to_string(ep1) == to_string(ep4));
|
||||
}
|
||||
|
||||
{
|
||||
ep = Endpoint::from_string("[::]:2017");
|
||||
CHECK(is_unspecified(ep));
|
||||
CHECK(ep.port() == 2017);
|
||||
CHECK(ep.address() == AddressV6{});
|
||||
}
|
||||
|
||||
// Failures:
|
||||
failParseEP("192.0.2.112:port");
|
||||
failParseEP("ip:port");
|
||||
failParseEP("");
|
||||
failParseEP("1.2.3.256");
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
// windows asio bugs...false positives
|
||||
shouldParseEPV4("255", {{0, 0, 0, 255}}, 0, "0.0.0.255");
|
||||
shouldParseEPV4("512", {{0, 0, 2, 0}}, 0, "0.0.2.0");
|
||||
shouldParseEPV4("1.2.3:80", {{1, 2, 0, 3}}, 80, "1.2.0.3:80");
|
||||
#else
|
||||
failParseEP("255");
|
||||
failParseEP("512");
|
||||
failParseEP("1.2.3:80");
|
||||
#endif
|
||||
|
||||
failParseEP("1.2.3.4:65536");
|
||||
failParseEP("1.2.3.4:89119");
|
||||
failParseEP("1.2.3:89119");
|
||||
failParseEP("[::1]:89119");
|
||||
failParseEP("[::az]:1");
|
||||
failParseEP("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:1");
|
||||
failParseEP("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:12345");
|
||||
failParseEP("abcdef:12345");
|
||||
failParseEP("[abcdef]:12345");
|
||||
failParseEP("foo.org 12345");
|
||||
|
||||
// test with hashed container
|
||||
std::unordered_set<Endpoint> eps;
|
||||
constexpr auto items{100};
|
||||
float max_lf{0};
|
||||
for (auto i = 0; i < items; ++i)
|
||||
{
|
||||
eps.insert(randomEP(xrpl::rand_int(0, 1) == 1));
|
||||
max_lf = std::max(max_lf, eps.load_factor());
|
||||
}
|
||||
CHECK(eps.bucket_count() >= items);
|
||||
CHECK(max_lf > 0.90);
|
||||
}
|
||||
|
||||
TEST_CASE("Parse Endpoint")
|
||||
{
|
||||
shouldPass<Endpoint>("0.0.0.0");
|
||||
shouldPass<Endpoint>("192.168.0.1");
|
||||
shouldPass<Endpoint>("168.127.149.132");
|
||||
shouldPass<Endpoint>("168.127.149.132:80");
|
||||
shouldPass<Endpoint>("168.127.149.132:54321");
|
||||
shouldPass<Endpoint>("2001:db8:a0b:12f0::1");
|
||||
shouldPass<Endpoint>("[2001:db8:a0b:12f0::1]:8");
|
||||
shouldPass<Endpoint>("2001:db8:a0b:12f0::1 8", "[2001:db8:a0b:12f0::1]:8");
|
||||
shouldPass<Endpoint>("[::1]:8");
|
||||
shouldPass<Endpoint>("[2001:2002:2003:2004:2005:2006:2007:2008]:65535");
|
||||
|
||||
shouldFail<Endpoint>("1.2.3.256");
|
||||
shouldFail<Endpoint>("");
|
||||
#if BOOST_OS_WINDOWS
|
||||
// windows asio bugs...false positives
|
||||
shouldPass<Endpoint>("512", "0.0.2.0");
|
||||
shouldPass<Endpoint>("255", "0.0.0.255");
|
||||
shouldPass<Endpoint>("1.2.3:80", "1.2.0.3:80");
|
||||
#else
|
||||
shouldFail<Endpoint>("512");
|
||||
shouldFail<Endpoint>("255");
|
||||
shouldFail<Endpoint>("1.2.3:80");
|
||||
#endif
|
||||
shouldFail<Endpoint>("1.2.3:65536");
|
||||
shouldFail<Endpoint>("1.2.3:72131");
|
||||
shouldFail<Endpoint>("[::1]:89119");
|
||||
shouldFail<Endpoint>("[::az]:1");
|
||||
shouldFail<Endpoint>("[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:1");
|
||||
shouldFail<Endpoint>(
|
||||
"[1234:5678:90ab:cdef:1234:5678:90ab:cdef:1111]:12345");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
95
src/doctest/beast/Journal.cpp
Normal file
95
src/doctest/beast/Journal.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace beast;
|
||||
|
||||
TEST_SUITE_BEGIN("Journal");
|
||||
|
||||
namespace {
|
||||
|
||||
class TestSink : public Journal::Sink
|
||||
{
|
||||
private:
|
||||
int m_count;
|
||||
|
||||
public:
|
||||
TestSink() : Sink(severities::kWarning, false), m_count(0)
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
count() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
void
|
||||
reset()
|
||||
{
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
write(severities::Severity level, std::string const&) override
|
||||
{
|
||||
if (level >= threshold())
|
||||
++m_count;
|
||||
}
|
||||
|
||||
void
|
||||
writeAlways(severities::Severity level, std::string const&) override
|
||||
{
|
||||
++m_count;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Journal threshold kInfo")
|
||||
{
|
||||
TestSink sink;
|
||||
|
||||
using namespace beast::severities;
|
||||
sink.threshold(kInfo);
|
||||
|
||||
Journal j(sink);
|
||||
|
||||
j.trace() << " ";
|
||||
CHECK(sink.count() == 0);
|
||||
j.debug() << " ";
|
||||
CHECK(sink.count() == 0);
|
||||
j.info() << " ";
|
||||
CHECK(sink.count() == 1);
|
||||
j.warn() << " ";
|
||||
CHECK(sink.count() == 2);
|
||||
j.error() << " ";
|
||||
CHECK(sink.count() == 3);
|
||||
j.fatal() << " ";
|
||||
CHECK(sink.count() == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("Journal threshold kDebug")
|
||||
{
|
||||
TestSink sink;
|
||||
|
||||
using namespace beast::severities;
|
||||
sink.threshold(kDebug);
|
||||
|
||||
Journal j(sink);
|
||||
|
||||
j.trace() << " ";
|
||||
CHECK(sink.count() == 0);
|
||||
j.debug() << " ";
|
||||
CHECK(sink.count() == 1);
|
||||
j.info() << " ";
|
||||
CHECK(sink.count() == 2);
|
||||
j.warn() << " ";
|
||||
CHECK(sink.count() == 3);
|
||||
j.error() << " ";
|
||||
CHECK(sink.count() == 4);
|
||||
j.fatal() << " ";
|
||||
CHECK(sink.count() == 5);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
254
src/doctest/beast/LexicalCast.cpp
Normal file
254
src/doctest/beast/LexicalCast.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#include <xrpl/beast/core/LexicalCast.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace beast;
|
||||
|
||||
TEST_SUITE_BEGIN("LexicalCast");
|
||||
|
||||
namespace {
|
||||
|
||||
template <class IntType>
|
||||
IntType
|
||||
nextRandomInt(xor_shift_engine& r)
|
||||
{
|
||||
return static_cast<IntType>(r());
|
||||
}
|
||||
|
||||
template <class IntType>
|
||||
void
|
||||
testInteger(IntType in)
|
||||
{
|
||||
std::string s;
|
||||
IntType out(in + 1);
|
||||
|
||||
CHECK(lexicalCastChecked(s, in));
|
||||
CHECK(lexicalCastChecked(out, s));
|
||||
CHECK(out == in);
|
||||
}
|
||||
|
||||
template <class IntType>
|
||||
void
|
||||
testIntegers(xor_shift_engine& r)
|
||||
{
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
IntType const value(nextRandomInt<IntType>(r));
|
||||
testInteger(value);
|
||||
}
|
||||
|
||||
testInteger(std::numeric_limits<IntType>::min());
|
||||
testInteger(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
tryBadConvert(std::string const& s)
|
||||
{
|
||||
T out;
|
||||
CHECK_FALSE(lexicalCastChecked(out, s));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
tryEdgeCase(std::string const& s)
|
||||
{
|
||||
T ret;
|
||||
|
||||
bool const result = lexicalCastChecked(ret, s);
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
return s == std::to_string(ret);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
testThrowConvert(std::string const& s, bool success)
|
||||
{
|
||||
bool result = !success;
|
||||
T out;
|
||||
|
||||
try
|
||||
{
|
||||
out = lexicalCastThrow<T>(s);
|
||||
result = true;
|
||||
}
|
||||
catch (BadLexicalCast const&)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
CHECK(result == success);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("random integers")
|
||||
{
|
||||
std::int64_t const seedValue = 50;
|
||||
xor_shift_engine r(seedValue);
|
||||
|
||||
SUBCASE("int")
|
||||
{
|
||||
testIntegers<int>(r);
|
||||
}
|
||||
SUBCASE("unsigned int")
|
||||
{
|
||||
testIntegers<unsigned int>(r);
|
||||
}
|
||||
SUBCASE("short")
|
||||
{
|
||||
testIntegers<short>(r);
|
||||
}
|
||||
SUBCASE("unsigned short")
|
||||
{
|
||||
testIntegers<unsigned short>(r);
|
||||
}
|
||||
SUBCASE("int32_t")
|
||||
{
|
||||
testIntegers<std::int32_t>(r);
|
||||
}
|
||||
SUBCASE("uint32_t")
|
||||
{
|
||||
testIntegers<std::uint32_t>(r);
|
||||
}
|
||||
SUBCASE("int64_t")
|
||||
{
|
||||
testIntegers<std::int64_t>(r);
|
||||
}
|
||||
SUBCASE("uint64_t")
|
||||
{
|
||||
testIntegers<std::uint64_t>(r);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pathologies")
|
||||
{
|
||||
CHECK_THROWS_AS(
|
||||
lexicalCastThrow<int>("\xef\xbc\x91\xef\xbc\x90"), BadLexicalCast);
|
||||
}
|
||||
|
||||
TEST_CASE("conversion overflows")
|
||||
{
|
||||
tryBadConvert<std::uint64_t>("99999999999999999999");
|
||||
tryBadConvert<std::uint32_t>("4294967300");
|
||||
tryBadConvert<std::uint16_t>("75821");
|
||||
}
|
||||
|
||||
TEST_CASE("conversion underflows")
|
||||
{
|
||||
tryBadConvert<std::uint32_t>("-1");
|
||||
|
||||
tryBadConvert<std::int64_t>("-99999999999999999999");
|
||||
tryBadConvert<std::int32_t>("-4294967300");
|
||||
tryBadConvert<std::int16_t>("-75821");
|
||||
}
|
||||
|
||||
TEST_CASE("conversion edge cases")
|
||||
{
|
||||
CHECK(tryEdgeCase<std::uint64_t>("18446744073709551614"));
|
||||
CHECK(tryEdgeCase<std::uint64_t>("18446744073709551615"));
|
||||
CHECK_FALSE(tryEdgeCase<std::uint64_t>("18446744073709551616"));
|
||||
|
||||
CHECK(tryEdgeCase<std::int64_t>("9223372036854775806"));
|
||||
CHECK(tryEdgeCase<std::int64_t>("9223372036854775807"));
|
||||
CHECK_FALSE(tryEdgeCase<std::int64_t>("9223372036854775808"));
|
||||
|
||||
CHECK(tryEdgeCase<std::int64_t>("-9223372036854775807"));
|
||||
CHECK(tryEdgeCase<std::int64_t>("-9223372036854775808"));
|
||||
CHECK_FALSE(tryEdgeCase<std::int64_t>("-9223372036854775809"));
|
||||
|
||||
CHECK(tryEdgeCase<std::uint32_t>("4294967294"));
|
||||
CHECK(tryEdgeCase<std::uint32_t>("4294967295"));
|
||||
CHECK_FALSE(tryEdgeCase<std::uint32_t>("4294967296"));
|
||||
|
||||
CHECK(tryEdgeCase<std::int32_t>("2147483646"));
|
||||
CHECK(tryEdgeCase<std::int32_t>("2147483647"));
|
||||
CHECK_FALSE(tryEdgeCase<std::int32_t>("2147483648"));
|
||||
|
||||
CHECK(tryEdgeCase<std::int32_t>("-2147483647"));
|
||||
CHECK(tryEdgeCase<std::int32_t>("-2147483648"));
|
||||
CHECK_FALSE(tryEdgeCase<std::int32_t>("-2147483649"));
|
||||
|
||||
CHECK(tryEdgeCase<std::uint16_t>("65534"));
|
||||
CHECK(tryEdgeCase<std::uint16_t>("65535"));
|
||||
CHECK_FALSE(tryEdgeCase<std::uint16_t>("65536"));
|
||||
|
||||
CHECK(tryEdgeCase<std::int16_t>("32766"));
|
||||
CHECK(tryEdgeCase<std::int16_t>("32767"));
|
||||
CHECK_FALSE(tryEdgeCase<std::int16_t>("32768"));
|
||||
|
||||
CHECK(tryEdgeCase<std::int16_t>("-32767"));
|
||||
CHECK(tryEdgeCase<std::int16_t>("-32768"));
|
||||
CHECK_FALSE(tryEdgeCase<std::int16_t>("-32769"));
|
||||
}
|
||||
|
||||
TEST_CASE("throwing conversion")
|
||||
{
|
||||
testThrowConvert<std::uint64_t>("99999999999999999999", false);
|
||||
testThrowConvert<std::uint64_t>("9223372036854775806", true);
|
||||
|
||||
testThrowConvert<std::uint32_t>("4294967290", true);
|
||||
testThrowConvert<std::uint32_t>("42949672900", false);
|
||||
testThrowConvert<std::uint32_t>("429496729000", false);
|
||||
testThrowConvert<std::uint32_t>("4294967290000", false);
|
||||
|
||||
testThrowConvert<std::int32_t>("5294967295", false);
|
||||
testThrowConvert<std::int32_t>("-2147483644", true);
|
||||
|
||||
testThrowConvert<std::int16_t>("66666", false);
|
||||
testThrowConvert<std::int16_t>("-5711", true);
|
||||
}
|
||||
|
||||
TEST_CASE("zero conversion")
|
||||
{
|
||||
SUBCASE("signed")
|
||||
{
|
||||
std::int32_t out;
|
||||
|
||||
CHECK(lexicalCastChecked(out, "-0"));
|
||||
CHECK(lexicalCastChecked(out, "0"));
|
||||
CHECK(lexicalCastChecked(out, "+0"));
|
||||
}
|
||||
|
||||
SUBCASE("unsigned")
|
||||
{
|
||||
std::uint32_t out;
|
||||
|
||||
CHECK_FALSE(lexicalCastChecked(out, "-0"));
|
||||
CHECK(lexicalCastChecked(out, "0"));
|
||||
CHECK(lexicalCastChecked(out, "+0"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("entire range")
|
||||
{
|
||||
std::int32_t i = std::numeric_limits<std::int16_t>::min();
|
||||
std::string const empty("");
|
||||
|
||||
while (i <= std::numeric_limits<std::int16_t>::max())
|
||||
{
|
||||
std::int16_t j = static_cast<std::int16_t>(i);
|
||||
|
||||
auto actual = std::to_string(j);
|
||||
|
||||
auto result = lexicalCast(j, empty);
|
||||
|
||||
CHECK(result == actual);
|
||||
|
||||
if (result == actual)
|
||||
{
|
||||
auto number = lexicalCast<std::int16_t>(result);
|
||||
CHECK(number == j);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
216
src/doctest/beast/PropertyStream.cpp
Normal file
216
src/doctest/beast/PropertyStream.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace beast;
|
||||
using Source = PropertyStream::Source;
|
||||
|
||||
TEST_SUITE_BEGIN("PropertyStream");
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
test_peel_name(
|
||||
std::string s,
|
||||
std::string const& expected,
|
||||
std::string const& expected_remainder)
|
||||
{
|
||||
std::string const peeled_name = Source::peel_name(&s);
|
||||
CHECK(peeled_name == expected);
|
||||
CHECK(s == expected_remainder);
|
||||
}
|
||||
|
||||
void
|
||||
test_peel_leading_slash(
|
||||
std::string s,
|
||||
std::string const& expected,
|
||||
bool should_be_found)
|
||||
{
|
||||
bool const found(Source::peel_leading_slash(&s));
|
||||
CHECK(found == should_be_found);
|
||||
CHECK(s == expected);
|
||||
}
|
||||
|
||||
void
|
||||
test_peel_trailing_slashstar(
|
||||
std::string s,
|
||||
std::string const& expected_remainder,
|
||||
bool should_be_found)
|
||||
{
|
||||
bool const found(Source::peel_trailing_slashstar(&s));
|
||||
CHECK(found == should_be_found);
|
||||
CHECK(s == expected_remainder);
|
||||
}
|
||||
|
||||
void
|
||||
test_find_one(Source& root, Source* expected, std::string const& name)
|
||||
{
|
||||
Source* source(root.find_one(name));
|
||||
CHECK(source == expected);
|
||||
}
|
||||
|
||||
void
|
||||
test_find_path(Source& root, std::string const& path, Source* expected)
|
||||
{
|
||||
Source* source(root.find_path(path));
|
||||
CHECK(source == expected);
|
||||
}
|
||||
|
||||
void
|
||||
test_find_one_deep(Source& root, std::string const& name, Source* expected)
|
||||
{
|
||||
Source* source(root.find_one_deep(name));
|
||||
CHECK(source == expected);
|
||||
}
|
||||
|
||||
void
|
||||
test_find(Source& root, std::string path, Source* expected, bool expected_star)
|
||||
{
|
||||
auto const result(root.find(path));
|
||||
CHECK(result.first == expected);
|
||||
CHECK(result.second == expected_star);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("peel_name")
|
||||
{
|
||||
test_peel_name("a", "a", "");
|
||||
test_peel_name("foo/bar", "foo", "bar");
|
||||
test_peel_name("foo/goo/bar", "foo", "goo/bar");
|
||||
test_peel_name("", "", "");
|
||||
}
|
||||
|
||||
TEST_CASE("peel_leading_slash")
|
||||
{
|
||||
test_peel_leading_slash("foo/", "foo/", false);
|
||||
test_peel_leading_slash("foo", "foo", false);
|
||||
test_peel_leading_slash("/foo/", "foo/", true);
|
||||
test_peel_leading_slash("/foo", "foo", true);
|
||||
}
|
||||
|
||||
TEST_CASE("peel_trailing_slashstar")
|
||||
{
|
||||
test_peel_trailing_slashstar("/foo/goo/*", "/foo/goo", true);
|
||||
test_peel_trailing_slashstar("foo/goo/*", "foo/goo", true);
|
||||
test_peel_trailing_slashstar("/foo/goo/", "/foo/goo", false);
|
||||
test_peel_trailing_slashstar("foo/goo", "foo/goo", false);
|
||||
test_peel_trailing_slashstar("", "", false);
|
||||
test_peel_trailing_slashstar("/", "", false);
|
||||
test_peel_trailing_slashstar("/*", "", true);
|
||||
test_peel_trailing_slashstar("//", "/", false);
|
||||
test_peel_trailing_slashstar("**", "*", true);
|
||||
test_peel_trailing_slashstar("*/", "*", false);
|
||||
}
|
||||
|
||||
TEST_CASE("find_one")
|
||||
{
|
||||
Source a("a");
|
||||
Source b("b");
|
||||
Source c("c");
|
||||
Source d("d");
|
||||
Source e("e");
|
||||
Source f("f");
|
||||
Source g("g");
|
||||
|
||||
// a { b { d { f }, e }, c { g } }
|
||||
a.add(b);
|
||||
a.add(c);
|
||||
c.add(g);
|
||||
b.add(d);
|
||||
b.add(e);
|
||||
d.add(f);
|
||||
|
||||
test_find_one(a, &b, "b");
|
||||
test_find_one(a, nullptr, "d");
|
||||
test_find_one(b, &e, "e");
|
||||
test_find_one(d, &f, "f");
|
||||
}
|
||||
|
||||
TEST_CASE("find_path")
|
||||
{
|
||||
Source a("a");
|
||||
Source b("b");
|
||||
Source c("c");
|
||||
Source d("d");
|
||||
Source e("e");
|
||||
Source f("f");
|
||||
Source g("g");
|
||||
|
||||
a.add(b);
|
||||
a.add(c);
|
||||
c.add(g);
|
||||
b.add(d);
|
||||
b.add(e);
|
||||
d.add(f);
|
||||
|
||||
test_find_path(a, "a", nullptr);
|
||||
test_find_path(a, "e", nullptr);
|
||||
test_find_path(a, "a/b", nullptr);
|
||||
test_find_path(a, "a/b/e", nullptr);
|
||||
test_find_path(a, "b/e/g", nullptr);
|
||||
test_find_path(a, "b/e/f", nullptr);
|
||||
test_find_path(a, "b", &b);
|
||||
test_find_path(a, "b/e", &e);
|
||||
test_find_path(a, "b/d/f", &f);
|
||||
}
|
||||
|
||||
TEST_CASE("find_one_deep")
|
||||
{
|
||||
Source a("a");
|
||||
Source b("b");
|
||||
Source c("c");
|
||||
Source d("d");
|
||||
Source e("e");
|
||||
Source f("f");
|
||||
Source g("g");
|
||||
|
||||
a.add(b);
|
||||
a.add(c);
|
||||
c.add(g);
|
||||
b.add(d);
|
||||
b.add(e);
|
||||
d.add(f);
|
||||
|
||||
test_find_one_deep(a, "z", nullptr);
|
||||
test_find_one_deep(a, "g", &g);
|
||||
test_find_one_deep(a, "b", &b);
|
||||
test_find_one_deep(a, "d", &d);
|
||||
test_find_one_deep(a, "f", &f);
|
||||
}
|
||||
|
||||
TEST_CASE("find")
|
||||
{
|
||||
Source a("a");
|
||||
Source b("b");
|
||||
Source c("c");
|
||||
Source d("d");
|
||||
Source e("e");
|
||||
Source f("f");
|
||||
Source g("g");
|
||||
|
||||
a.add(b);
|
||||
a.add(c);
|
||||
c.add(g);
|
||||
b.add(d);
|
||||
b.add(e);
|
||||
d.add(f);
|
||||
|
||||
test_find(a, "", &a, false);
|
||||
test_find(a, "*", &a, true);
|
||||
test_find(a, "/b", &b, false);
|
||||
test_find(a, "b", &b, false);
|
||||
test_find(a, "d", &d, false);
|
||||
test_find(a, "/b*", &b, true);
|
||||
test_find(a, "b*", &b, true);
|
||||
test_find(a, "d*", &d, true);
|
||||
test_find(a, "/b/*", &b, true);
|
||||
test_find(a, "b/*", &b, true);
|
||||
test_find(a, "d/*", &d, true);
|
||||
test_find(a, "a", nullptr, false);
|
||||
test_find(a, "/d", nullptr, false);
|
||||
test_find(a, "/d*", nullptr, true);
|
||||
test_find(a, "/d/*", nullptr, true);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
248
src/doctest/beast/SemanticVersion.cpp
Normal file
248
src/doctest/beast/SemanticVersion.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#include <xrpl/beast/core/SemanticVersion.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace beast;
|
||||
|
||||
TEST_SUITE_BEGIN("SemanticVersion");
|
||||
|
||||
using identifier_list = SemanticVersion::identifier_list;
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
checkPass(std::string const& input, bool shouldPass = true)
|
||||
{
|
||||
SemanticVersion v;
|
||||
|
||||
if (shouldPass)
|
||||
{
|
||||
CHECK(v.parse(input));
|
||||
CHECK(v.print() == input);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_FALSE(v.parse(input));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
checkFail(std::string const& input)
|
||||
{
|
||||
checkPass(input, false);
|
||||
}
|
||||
|
||||
// check input and input with appended metadata
|
||||
void
|
||||
checkMeta(std::string const& input, bool shouldPass)
|
||||
{
|
||||
checkPass(input, shouldPass);
|
||||
|
||||
checkPass(input + "+a", shouldPass);
|
||||
checkPass(input + "+1", shouldPass);
|
||||
checkPass(input + "+a.b", shouldPass);
|
||||
checkPass(input + "+ab.cd", shouldPass);
|
||||
|
||||
checkFail(input + "!");
|
||||
checkFail(input + "+");
|
||||
checkFail(input + "++");
|
||||
checkFail(input + "+!");
|
||||
checkFail(input + "+.");
|
||||
checkFail(input + "+a.!");
|
||||
}
|
||||
|
||||
void
|
||||
checkMetaFail(std::string const& input)
|
||||
{
|
||||
checkMeta(input, false);
|
||||
}
|
||||
|
||||
// check input, input with appended release data,
|
||||
// input with appended metadata, and input with both
|
||||
// appended release data and appended metadata
|
||||
void
|
||||
checkRelease(std::string const& input, bool shouldPass = true)
|
||||
{
|
||||
checkMeta(input, shouldPass);
|
||||
|
||||
checkMeta(input + "-1", shouldPass);
|
||||
checkMeta(input + "-a", shouldPass);
|
||||
checkMeta(input + "-a1", shouldPass);
|
||||
checkMeta(input + "-a1.b1", shouldPass);
|
||||
checkMeta(input + "-ab.cd", shouldPass);
|
||||
checkMeta(input + "--", shouldPass);
|
||||
|
||||
checkMetaFail(input + "+");
|
||||
checkMetaFail(input + "!");
|
||||
checkMetaFail(input + "-");
|
||||
checkMetaFail(input + "-!");
|
||||
checkMetaFail(input + "-.");
|
||||
checkMetaFail(input + "-a.!");
|
||||
checkMetaFail(input + "-0.a");
|
||||
}
|
||||
|
||||
// Checks the major.minor.version string alone and with all
|
||||
// possible combinations of release identifiers and metadata.
|
||||
void
|
||||
check(std::string const& input, bool shouldPass = true)
|
||||
{
|
||||
checkRelease(input, shouldPass);
|
||||
}
|
||||
|
||||
void
|
||||
negcheck(std::string const& input)
|
||||
{
|
||||
check(input, false);
|
||||
}
|
||||
|
||||
identifier_list
|
||||
ids()
|
||||
{
|
||||
return identifier_list();
|
||||
}
|
||||
|
||||
identifier_list
|
||||
ids(std::string const& s1)
|
||||
{
|
||||
identifier_list v;
|
||||
v.push_back(s1);
|
||||
return v;
|
||||
}
|
||||
|
||||
identifier_list
|
||||
ids(std::string const& s1, std::string const& s2)
|
||||
{
|
||||
identifier_list v;
|
||||
v.push_back(s1);
|
||||
v.push_back(s2);
|
||||
return v;
|
||||
}
|
||||
|
||||
identifier_list
|
||||
ids(std::string const& s1, std::string const& s2, std::string const& s3)
|
||||
{
|
||||
identifier_list v;
|
||||
v.push_back(s1);
|
||||
v.push_back(s2);
|
||||
v.push_back(s3);
|
||||
return v;
|
||||
}
|
||||
|
||||
// Checks the decomposition of the input into appropriate values
|
||||
void
|
||||
checkValues(
|
||||
std::string const& input,
|
||||
int majorVersion,
|
||||
int minorVersion,
|
||||
int patchVersion,
|
||||
identifier_list const& preReleaseIdentifiers = identifier_list(),
|
||||
identifier_list const& metaData = identifier_list())
|
||||
{
|
||||
SemanticVersion v;
|
||||
|
||||
CHECK(v.parse(input));
|
||||
|
||||
CHECK(v.majorVersion == majorVersion);
|
||||
CHECK(v.minorVersion == minorVersion);
|
||||
CHECK(v.patchVersion == patchVersion);
|
||||
|
||||
CHECK(v.preReleaseIdentifiers == preReleaseIdentifiers);
|
||||
CHECK(v.metaData == metaData);
|
||||
}
|
||||
|
||||
// makes sure the left version is less than the right
|
||||
void
|
||||
checkLessInternal(std::string const& lhs, std::string const& rhs)
|
||||
{
|
||||
SemanticVersion left;
|
||||
SemanticVersion right;
|
||||
|
||||
CHECK(left.parse(lhs));
|
||||
CHECK(right.parse(rhs));
|
||||
|
||||
CHECK(compare(left, left) == 0);
|
||||
CHECK(compare(right, right) == 0);
|
||||
CHECK(compare(left, right) < 0);
|
||||
CHECK(compare(right, left) > 0);
|
||||
|
||||
CHECK(left < right);
|
||||
CHECK(right > left);
|
||||
CHECK(left == left);
|
||||
CHECK(right == right);
|
||||
}
|
||||
|
||||
void
|
||||
checkLess(std::string const& lhs, std::string const& rhs)
|
||||
{
|
||||
checkLessInternal(lhs, rhs);
|
||||
checkLessInternal(lhs + "+meta", rhs);
|
||||
checkLessInternal(lhs, rhs + "+meta");
|
||||
checkLessInternal(lhs + "+meta", rhs + "+meta");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("parsing")
|
||||
{
|
||||
check("0.0.0");
|
||||
check("1.2.3");
|
||||
check("2147483647.2147483647.2147483647"); // max int
|
||||
|
||||
// negative values
|
||||
negcheck("-1.2.3");
|
||||
negcheck("1.-2.3");
|
||||
negcheck("1.2.-3");
|
||||
|
||||
// missing parts
|
||||
negcheck("");
|
||||
negcheck("1");
|
||||
negcheck("1.");
|
||||
negcheck("1.2");
|
||||
negcheck("1.2.");
|
||||
negcheck(".2.3");
|
||||
|
||||
// whitespace
|
||||
negcheck(" 1.2.3");
|
||||
negcheck("1 .2.3");
|
||||
negcheck("1.2 .3");
|
||||
negcheck("1.2.3 ");
|
||||
|
||||
// leading zeroes
|
||||
negcheck("01.2.3");
|
||||
negcheck("1.02.3");
|
||||
negcheck("1.2.03");
|
||||
}
|
||||
|
||||
TEST_CASE("values")
|
||||
{
|
||||
checkValues("0.1.2", 0, 1, 2);
|
||||
checkValues("1.2.3", 1, 2, 3);
|
||||
checkValues("1.2.3-rc1", 1, 2, 3, ids("rc1"));
|
||||
checkValues("1.2.3-rc1.debug", 1, 2, 3, ids("rc1", "debug"));
|
||||
checkValues("1.2.3-rc1.debug.asm", 1, 2, 3, ids("rc1", "debug", "asm"));
|
||||
checkValues("1.2.3+full", 1, 2, 3, ids(), ids("full"));
|
||||
checkValues("1.2.3+full.prod", 1, 2, 3, ids(), ids("full", "prod"));
|
||||
checkValues(
|
||||
"1.2.3+full.prod.x86", 1, 2, 3, ids(), ids("full", "prod", "x86"));
|
||||
checkValues(
|
||||
"1.2.3-rc1.debug.asm+full.prod.x86",
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
ids("rc1", "debug", "asm"),
|
||||
ids("full", "prod", "x86"));
|
||||
}
|
||||
|
||||
TEST_CASE("comparisons")
|
||||
{
|
||||
checkLess("1.0.0-alpha", "1.0.0-alpha.1");
|
||||
checkLess("1.0.0-alpha.1", "1.0.0-alpha.beta");
|
||||
checkLess("1.0.0-alpha.beta", "1.0.0-beta");
|
||||
checkLess("1.0.0-beta", "1.0.0-beta.2");
|
||||
checkLess("1.0.0-beta.2", "1.0.0-beta.11");
|
||||
checkLess("1.0.0-beta.11", "1.0.0-rc.1");
|
||||
checkLess("1.0.0-rc.1", "1.0.0");
|
||||
checkLess("0.9.9", "1.0.0");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
1168
src/doctest/beast/aged_associative_container.cpp
Normal file
1168
src/doctest/beast/aged_associative_container.cpp
Normal file
File diff suppressed because it is too large
Load Diff
17
src/doctest/beast/basic_seconds_clock.cpp
Normal file
17
src/doctest/beast/basic_seconds_clock.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <xrpl/beast/clock/basic_seconds_clock.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace beast;
|
||||
|
||||
TEST_SUITE_BEGIN("basic_seconds_clock");
|
||||
|
||||
TEST_CASE("basic_seconds_clock::now() works")
|
||||
{
|
||||
// Just verify that now() can be called without throwing
|
||||
auto t = basic_seconds_clock::now();
|
||||
// Verify that the time point is valid (not zero)
|
||||
CHECK(t.time_since_epoch().count() > 0);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
98
src/doctest/beast/beast_Zero.cpp
Normal file
98
src/doctest/beast/beast_Zero.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
namespace beast {
|
||||
|
||||
struct adl_tester
|
||||
{
|
||||
};
|
||||
|
||||
int
|
||||
signum(adl_tester)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace inner_adl_test {
|
||||
|
||||
struct adl_tester2
|
||||
{
|
||||
};
|
||||
|
||||
int
|
||||
signum(adl_tester2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace inner_adl_test
|
||||
|
||||
} // namespace beast
|
||||
|
||||
using namespace beast;
|
||||
|
||||
TEST_SUITE_BEGIN("Zero");
|
||||
|
||||
namespace {
|
||||
|
||||
struct IntegerWrapper
|
||||
{
|
||||
int value;
|
||||
|
||||
IntegerWrapper(int v) : value(v)
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
signum() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
test_lhs_zero(IntegerWrapper x)
|
||||
{
|
||||
CHECK((x >= zero) == (x.signum() >= 0));
|
||||
CHECK((x > zero) == (x.signum() > 0));
|
||||
CHECK((x == zero) == (x.signum() == 0));
|
||||
CHECK((x != zero) == (x.signum() != 0));
|
||||
CHECK((x < zero) == (x.signum() < 0));
|
||||
CHECK((x <= zero) == (x.signum() <= 0));
|
||||
}
|
||||
|
||||
void
|
||||
test_rhs_zero(IntegerWrapper x)
|
||||
{
|
||||
CHECK((zero >= x) == (0 >= x.signum()));
|
||||
CHECK((zero > x) == (0 > x.signum()));
|
||||
CHECK((zero == x) == (0 == x.signum()));
|
||||
CHECK((zero != x) == (0 != x.signum()));
|
||||
CHECK((zero < x) == (0 < x.signum()));
|
||||
CHECK((zero <= x) == (0 <= x.signum()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("lhs zero")
|
||||
{
|
||||
test_lhs_zero(-7);
|
||||
test_lhs_zero(0);
|
||||
test_lhs_zero(32);
|
||||
}
|
||||
|
||||
TEST_CASE("rhs zero")
|
||||
{
|
||||
test_rhs_zero(-4);
|
||||
test_rhs_zero(0);
|
||||
test_rhs_zero(64);
|
||||
}
|
||||
|
||||
TEST_CASE("ADL")
|
||||
{
|
||||
CHECK(adl_tester{} == zero);
|
||||
CHECK(inner_adl_test::adl_tester2{} == zero);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
2
src/doctest/beast/main.cpp
Normal file
2
src/doctest/beast/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
168
src/doctest/beast/xxhasher.cpp
Normal file
168
src/doctest/beast/xxhasher.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <xrpl/beast/hash/xxhasher.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace beast;
|
||||
|
||||
TEST_SUITE_BEGIN("XXHasher");
|
||||
|
||||
TEST_CASE("Without seed")
|
||||
{
|
||||
xxhasher hasher{};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 16042857369214894119ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("With seed")
|
||||
{
|
||||
xxhasher hasher{static_cast<std::uint32_t>(102)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 14440132435660934800ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("With two seeds")
|
||||
{
|
||||
xxhasher hasher{
|
||||
static_cast<std::uint32_t>(102), static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 14440132435660934800ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Big object with multiple small updates without seed")
|
||||
{
|
||||
xxhasher hasher{};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
}
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 15296278154063476002ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Big object with multiple small updates with seed")
|
||||
{
|
||||
xxhasher hasher{static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
}
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 17285302196561698791ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Big object with small and big updates without seed")
|
||||
{
|
||||
xxhasher hasher{};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
std::string bigObject;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
bigObject += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
hasher(bigObject.data(), bigObject.size());
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(static_cast<xxhasher::result_type>(hasher) == 1865045178324729219ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Big object with small and big updates with seed")
|
||||
{
|
||||
xxhasher hasher{static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
std::string bigObject;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
bigObject += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
hasher(bigObject.data(), bigObject.size());
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 16189862915636005281ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Big object with one update without seed")
|
||||
{
|
||||
xxhasher hasher{};
|
||||
|
||||
std::string objectToHash;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
objectToHash += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 15296278154063476002ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Big object with one update with seed")
|
||||
{
|
||||
xxhasher hasher{static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
objectToHash += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
CHECK(
|
||||
static_cast<xxhasher::result_type>(hasher) == 17285302196561698791ULL);
|
||||
}
|
||||
|
||||
TEST_CASE("Operator result type doesn't change the internal state")
|
||||
{
|
||||
SUBCASE("small object")
|
||||
{
|
||||
xxhasher hasher;
|
||||
|
||||
std::string object{"Hello xxhash"};
|
||||
hasher(object.data(), object.size());
|
||||
auto xxhashResult1 = static_cast<xxhasher::result_type>(hasher);
|
||||
auto xxhashResult2 = static_cast<xxhasher::result_type>(hasher);
|
||||
|
||||
CHECK(xxhashResult1 == xxhashResult2);
|
||||
}
|
||||
SUBCASE("big object")
|
||||
{
|
||||
xxhasher hasher;
|
||||
|
||||
std::string object;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
object += "Hello, xxHash!";
|
||||
}
|
||||
hasher(object.data(), object.size());
|
||||
auto xxhashResult1 = hasher.operator xxhasher::result_type();
|
||||
auto xxhashResult2 = hasher.operator xxhasher::result_type();
|
||||
|
||||
CHECK(xxhashResult1 == xxhashResult2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
168
src/doctest/core/Workers.cpp
Normal file
168
src/doctest/core/Workers.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <xrpl/core/PerfLog.h>
|
||||
#include <xrpl/core/detail/Workers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TEST_SUITE_BEGIN("Workers");
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Dummy class for unit tests.
|
||||
*/
|
||||
class PerfLogTest : public perf::PerfLog
|
||||
{
|
||||
void
|
||||
rpcStart(std::string const& method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rpcFinish(std::string const& method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rpcError(std::string const& method, std::uint64_t dur) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
jobQueue(JobType const type) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
jobStart(
|
||||
JobType const type,
|
||||
std::chrono::microseconds dur,
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime,
|
||||
int instance) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
jobFinish(JobType const type, std::chrono::microseconds dur, int instance)
|
||||
override
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value
|
||||
countersJson() const override
|
||||
{
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
Json::Value
|
||||
currentJson() const override
|
||||
{
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
void
|
||||
resizeJobs(int const resize) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rotate() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TestCallback : Workers::Callback
|
||||
{
|
||||
void
|
||||
processTask(int instance) override
|
||||
{
|
||||
std::lock_guard lk{mut};
|
||||
if (--count == 0)
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
std::condition_variable cv;
|
||||
std::mutex mut;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
void
|
||||
testThreads(int const tc1, int const tc2, int const tc3)
|
||||
{
|
||||
TestCallback cb;
|
||||
std::unique_ptr<perf::PerfLog> perfLog = std::make_unique<PerfLogTest>();
|
||||
|
||||
Workers w(cb, perfLog.get(), "Test", tc1);
|
||||
CHECK(w.getNumberOfThreads() == tc1);
|
||||
|
||||
auto testForThreadCount = [&cb, &w](int const threadCount) {
|
||||
// Prepare the callback.
|
||||
cb.count = threadCount;
|
||||
|
||||
// Execute the test.
|
||||
w.setNumberOfThreads(threadCount);
|
||||
CHECK(w.getNumberOfThreads() == threadCount);
|
||||
|
||||
for (int i = 0; i < threadCount; ++i)
|
||||
w.addTask();
|
||||
|
||||
// 10 seconds should be enough to finish on any system
|
||||
using namespace std::chrono_literals;
|
||||
std::unique_lock<std::mutex> lk{cb.mut};
|
||||
bool const signaled =
|
||||
cb.cv.wait_for(lk, 10s, [&cb] { return cb.count == 0; });
|
||||
CHECK(signaled);
|
||||
CHECK(cb.count == 0);
|
||||
};
|
||||
testForThreadCount(tc1);
|
||||
testForThreadCount(tc2);
|
||||
testForThreadCount(tc3);
|
||||
w.stop();
|
||||
|
||||
// We had better finished all our work!
|
||||
CHECK(cb.count == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("threadCounts: 0 -> 0 -> 0")
|
||||
{
|
||||
testThreads(0, 0, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("threadCounts: 1 -> 0 -> 1")
|
||||
{
|
||||
testThreads(1, 0, 1);
|
||||
}
|
||||
|
||||
TEST_CASE("threadCounts: 2 -> 1 -> 2")
|
||||
{
|
||||
testThreads(2, 1, 2);
|
||||
}
|
||||
|
||||
TEST_CASE("threadCounts: 4 -> 3 -> 5")
|
||||
{
|
||||
testThreads(4, 3, 5);
|
||||
}
|
||||
|
||||
TEST_CASE("threadCounts: 16 -> 4 -> 15")
|
||||
{
|
||||
testThreads(16, 4, 15);
|
||||
}
|
||||
|
||||
TEST_CASE("threadCounts: 64 -> 3 -> 65")
|
||||
{
|
||||
testThreads(64, 3, 65);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
} // namespace xrpl
|
||||
2
src/doctest/core/main.cpp
Normal file
2
src/doctest/core/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
122
src/doctest/csf/BasicNetwork.cpp
Normal file
122
src/doctest/csf/BasicNetwork.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
#include <test/csf/Scheduler.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
using namespace xrpl::test::csf;
|
||||
|
||||
TEST_SUITE_BEGIN("BasicNetwork");
|
||||
|
||||
namespace {
|
||||
|
||||
struct Peer
|
||||
{
|
||||
int id;
|
||||
std::set<int> set;
|
||||
|
||||
Peer(Peer const&) = default;
|
||||
Peer(Peer&&) = default;
|
||||
|
||||
explicit Peer(int id_) : id(id_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Net>
|
||||
void
|
||||
start(Scheduler& scheduler, Net& net)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto t = scheduler.in(1s, [&] { set.insert(0); });
|
||||
if (id == 0)
|
||||
{
|
||||
for (auto const link : net.links(this))
|
||||
net.send(this, link.target, [&, to = link.target] {
|
||||
to->receive(net, this, 1);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler.cancel(t);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Net>
|
||||
void
|
||||
receive(Net& net, Peer* from, int m)
|
||||
{
|
||||
set.insert(m);
|
||||
++m;
|
||||
if (m < 5)
|
||||
{
|
||||
for (auto const link : net.links(this))
|
||||
net.send(this, link.target, [&, mm = m, to = link.target] {
|
||||
to->receive(net, this, mm);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("BasicNetwork operations")
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::vector<Peer> pv;
|
||||
pv.emplace_back(0);
|
||||
pv.emplace_back(1);
|
||||
pv.emplace_back(2);
|
||||
Scheduler scheduler;
|
||||
BasicNetwork<Peer*> net(scheduler);
|
||||
CHECK(!net.connect(&pv[0], &pv[0]));
|
||||
CHECK(net.connect(&pv[0], &pv[1], 1s));
|
||||
CHECK(net.connect(&pv[1], &pv[2], 1s));
|
||||
CHECK(!net.connect(&pv[0], &pv[1]));
|
||||
for (auto& peer : pv)
|
||||
peer.start(scheduler, net);
|
||||
CHECK(scheduler.step_for(0s));
|
||||
CHECK(scheduler.step_for(1s));
|
||||
CHECK(scheduler.step());
|
||||
CHECK(!scheduler.step());
|
||||
CHECK(!scheduler.step_for(1s));
|
||||
net.send(&pv[0], &pv[1], [] {});
|
||||
net.send(&pv[1], &pv[0], [] {});
|
||||
CHECK(net.disconnect(&pv[0], &pv[1]));
|
||||
CHECK(!net.disconnect(&pv[0], &pv[1]));
|
||||
for (;;)
|
||||
{
|
||||
auto const links = net.links(&pv[1]);
|
||||
if (links.empty())
|
||||
break;
|
||||
CHECK(net.disconnect(&pv[1], links[0].target));
|
||||
}
|
||||
CHECK(pv[0].set == std::set<int>({0, 2, 4}));
|
||||
CHECK(pv[1].set == std::set<int>({1, 3}));
|
||||
CHECK(pv[2].set == std::set<int>({2, 4}));
|
||||
}
|
||||
|
||||
TEST_CASE("BasicNetwork disconnect")
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
Scheduler scheduler;
|
||||
BasicNetwork<int> net(scheduler);
|
||||
CHECK(net.connect(0, 1, 1s));
|
||||
CHECK(net.connect(0, 2, 2s));
|
||||
|
||||
std::set<int> delivered;
|
||||
net.send(0, 1, [&]() { delivered.insert(1); });
|
||||
net.send(0, 2, [&]() { delivered.insert(2); });
|
||||
|
||||
scheduler.in(1000ms, [&]() { CHECK(net.disconnect(0, 2)); });
|
||||
scheduler.in(1100ms, [&]() { CHECK(net.connect(0, 2)); });
|
||||
|
||||
scheduler.step();
|
||||
|
||||
// only the first message is delivered because the disconnect at 1 s
|
||||
// purges all pending messages from 0 to 2
|
||||
CHECK(delivered == std::set<int>({1}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
72
src/doctest/csf/Digraph.cpp
Normal file
72
src/doctest/csf/Digraph.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <test/csf/Digraph.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace xrpl::test::csf;
|
||||
|
||||
TEST_SUITE_BEGIN("Digraph");
|
||||
|
||||
TEST_CASE("Digraph basic operations")
|
||||
{
|
||||
using Graph = Digraph<char, std::string>;
|
||||
Graph graph;
|
||||
|
||||
CHECK(!graph.connected('a', 'b'));
|
||||
CHECK(!graph.edge('a', 'b'));
|
||||
CHECK(!graph.disconnect('a', 'b'));
|
||||
|
||||
CHECK(graph.connect('a', 'b', "foobar"));
|
||||
CHECK(graph.connected('a', 'b'));
|
||||
CHECK(*graph.edge('a', 'b') == "foobar");
|
||||
|
||||
CHECK(!graph.connect('a', 'b', "repeat"));
|
||||
CHECK(graph.disconnect('a', 'b'));
|
||||
CHECK(graph.connect('a', 'b', "repeat"));
|
||||
CHECK(graph.connected('a', 'b'));
|
||||
CHECK(*graph.edge('a', 'b') == "repeat");
|
||||
|
||||
CHECK(graph.connect('a', 'c', "tree"));
|
||||
|
||||
{
|
||||
std::vector<std::tuple<char, char, std::string>> edges;
|
||||
|
||||
for (auto const& edge : graph.outEdges('a'))
|
||||
{
|
||||
edges.emplace_back(edge.source, edge.target, edge.data);
|
||||
}
|
||||
|
||||
std::vector<std::tuple<char, char, std::string>> expected;
|
||||
expected.emplace_back('a', 'b', "repeat");
|
||||
expected.emplace_back('a', 'c', "tree");
|
||||
CHECK(edges == expected);
|
||||
CHECK(graph.outDegree('a') == expected.size());
|
||||
}
|
||||
|
||||
CHECK(graph.outEdges('r').size() == 0);
|
||||
CHECK(graph.outDegree('r') == 0);
|
||||
CHECK(graph.outDegree('c') == 0);
|
||||
|
||||
// only 'a' has out edges
|
||||
CHECK(graph.outVertices().size() == 1);
|
||||
std::vector<char> expected = {'b', 'c'};
|
||||
|
||||
CHECK((graph.outVertices('a') == expected));
|
||||
CHECK(graph.outVertices('b').size() == 0);
|
||||
CHECK(graph.outVertices('c').size() == 0);
|
||||
CHECK(graph.outVertices('r').size() == 0);
|
||||
|
||||
std::stringstream ss;
|
||||
graph.saveDot(ss, [](char v) { return v; });
|
||||
std::string expectedDot =
|
||||
"digraph {\n"
|
||||
"a -> b;\n"
|
||||
"a -> c;\n"
|
||||
"}\n";
|
||||
CHECK(ss.str() == expectedDot);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
75
src/doctest/csf/Histogram.cpp
Normal file
75
src/doctest/csf/Histogram.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include <test/csf/Histogram.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl::test::csf;
|
||||
|
||||
TEST_SUITE_BEGIN("Histogram");
|
||||
|
||||
TEST_CASE("Histogram empty")
|
||||
{
|
||||
Histogram<int> hist;
|
||||
|
||||
CHECK(hist.size() == 0);
|
||||
CHECK(hist.numBins() == 0);
|
||||
CHECK(hist.minValue() == 0);
|
||||
CHECK(hist.maxValue() == 0);
|
||||
CHECK(hist.avg() == 0);
|
||||
CHECK(hist.percentile(0.0f) == hist.minValue());
|
||||
CHECK(hist.percentile(0.5f) == 0);
|
||||
CHECK(hist.percentile(0.9f) == 0);
|
||||
CHECK(hist.percentile(1.0f) == hist.maxValue());
|
||||
}
|
||||
|
||||
TEST_CASE("Histogram single element")
|
||||
{
|
||||
Histogram<int> hist;
|
||||
hist.insert(1);
|
||||
|
||||
CHECK(hist.size() == 1);
|
||||
CHECK(hist.numBins() == 1);
|
||||
CHECK(hist.minValue() == 1);
|
||||
CHECK(hist.maxValue() == 1);
|
||||
CHECK(hist.avg() == 1);
|
||||
CHECK(hist.percentile(0.0f) == hist.minValue());
|
||||
CHECK(hist.percentile(0.5f) == 1);
|
||||
CHECK(hist.percentile(0.9f) == 1);
|
||||
CHECK(hist.percentile(1.0f) == hist.maxValue());
|
||||
}
|
||||
|
||||
TEST_CASE("Histogram two elements")
|
||||
{
|
||||
Histogram<int> hist;
|
||||
hist.insert(1);
|
||||
hist.insert(9);
|
||||
|
||||
CHECK(hist.size() == 2);
|
||||
CHECK(hist.numBins() == 2);
|
||||
CHECK(hist.minValue() == 1);
|
||||
CHECK(hist.maxValue() == 9);
|
||||
CHECK(hist.avg() == 5);
|
||||
CHECK(hist.percentile(0.0f) == hist.minValue());
|
||||
CHECK(hist.percentile(0.5f) == 1);
|
||||
CHECK(hist.percentile(0.9f) == 9);
|
||||
CHECK(hist.percentile(1.0f) == hist.maxValue());
|
||||
}
|
||||
|
||||
TEST_CASE("Histogram duplicate elements")
|
||||
{
|
||||
Histogram<int> hist;
|
||||
hist.insert(1);
|
||||
hist.insert(9);
|
||||
hist.insert(1);
|
||||
|
||||
CHECK(hist.size() == 3);
|
||||
CHECK(hist.numBins() == 2);
|
||||
CHECK(hist.minValue() == 1);
|
||||
CHECK(hist.maxValue() == 9);
|
||||
CHECK(hist.avg() == 11 / 3);
|
||||
CHECK(hist.percentile(0.0f) == hist.minValue());
|
||||
CHECK(hist.percentile(0.5f) == 1);
|
||||
CHECK(hist.percentile(0.9f) == 9);
|
||||
CHECK(hist.percentile(1.0f) == hist.maxValue());
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
63
src/doctest/csf/Scheduler.cpp
Normal file
63
src/doctest/csf/Scheduler.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <test/csf/Scheduler.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
using namespace xrpl::test::csf;
|
||||
|
||||
TEST_SUITE_BEGIN("Scheduler");
|
||||
|
||||
TEST_CASE("Scheduler basic operations")
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
Scheduler scheduler;
|
||||
std::set<int> seen;
|
||||
|
||||
scheduler.in(1s, [&] { seen.insert(1); });
|
||||
scheduler.in(2s, [&] { seen.insert(2); });
|
||||
auto token = scheduler.in(3s, [&] { seen.insert(3); });
|
||||
scheduler.at(scheduler.now() + 4s, [&] { seen.insert(4); });
|
||||
scheduler.at(scheduler.now() + 8s, [&] { seen.insert(8); });
|
||||
|
||||
auto start = scheduler.now();
|
||||
|
||||
// Process first event
|
||||
CHECK(seen.empty());
|
||||
CHECK(scheduler.step_one());
|
||||
CHECK(seen == std::set<int>({1}));
|
||||
CHECK(scheduler.now() == (start + 1s));
|
||||
|
||||
// No processing if stepping until current time
|
||||
CHECK(scheduler.step_until(scheduler.now()));
|
||||
CHECK(seen == std::set<int>({1}));
|
||||
CHECK(scheduler.now() == (start + 1s));
|
||||
|
||||
// Process next event
|
||||
CHECK(scheduler.step_for(1s));
|
||||
CHECK(seen == std::set<int>({1, 2}));
|
||||
CHECK(scheduler.now() == (start + 2s));
|
||||
|
||||
// Don't process cancelled event, but advance clock
|
||||
scheduler.cancel(token);
|
||||
CHECK(scheduler.step_for(1s));
|
||||
CHECK(seen == std::set<int>({1, 2}));
|
||||
CHECK(scheduler.now() == (start + 3s));
|
||||
|
||||
// Process until 3 seen ints
|
||||
CHECK(scheduler.step_while([&]() { return seen.size() < 3; }));
|
||||
CHECK(seen == std::set<int>({1, 2, 4}));
|
||||
CHECK(scheduler.now() == (start + 4s));
|
||||
|
||||
// Process the rest
|
||||
CHECK(scheduler.step());
|
||||
CHECK(seen == std::set<int>({1, 2, 4, 8}));
|
||||
CHECK(scheduler.now() == (start + 8s));
|
||||
|
||||
// Process the rest again doesn't advance
|
||||
CHECK(!scheduler.step());
|
||||
CHECK(seen == std::set<int>({1, 2, 4, 8}));
|
||||
CHECK(scheduler.now() == (start + 8s));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
2
src/doctest/csf/main.cpp
Normal file
2
src/doctest/csf/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
2
src/doctest/nodestore/main.cpp
Normal file
2
src/doctest/nodestore/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
46
src/doctest/nodestore/varint.cpp
Normal file
46
src/doctest/nodestore/varint.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <xrpl/nodestore/detail/varint.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
using namespace xrpl::NodeStore;
|
||||
|
||||
TEST_SUITE_BEGIN("varint");
|
||||
|
||||
TEST_CASE("encode, decode")
|
||||
{
|
||||
std::vector<std::size_t> vv = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
126,
|
||||
127,
|
||||
128,
|
||||
253,
|
||||
254,
|
||||
255,
|
||||
16127,
|
||||
16128,
|
||||
16129,
|
||||
0xff,
|
||||
0xffff,
|
||||
0xffffffff,
|
||||
0xffffffffffffUL,
|
||||
0xffffffffffffffffUL};
|
||||
|
||||
for (auto const v : vv)
|
||||
{
|
||||
std::array<std::uint8_t, varint_traits<std::size_t>::max> vi;
|
||||
auto const n0 = write_varint(vi.data(), v);
|
||||
CHECK(n0 > 0);
|
||||
CHECK(n0 == size_varint(v));
|
||||
std::size_t v1;
|
||||
auto const n1 = read_varint(vi.data(), n0, v1);
|
||||
CHECK(n1 == n0);
|
||||
CHECK(v == v1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
37
src/doctest/protocol/ApiVersion.cpp
Normal file
37
src/doctest/protocol/ApiVersion.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("ApiVersion");
|
||||
|
||||
TEST_CASE("API versions invariants")
|
||||
{
|
||||
static_assert(
|
||||
RPC::apiMinimumSupportedVersion <= RPC::apiMaximumSupportedVersion);
|
||||
static_assert(
|
||||
RPC::apiMinimumSupportedVersion <= RPC::apiMaximumValidVersion);
|
||||
static_assert(
|
||||
RPC::apiMaximumSupportedVersion <= RPC::apiMaximumValidVersion);
|
||||
static_assert(RPC::apiBetaVersion <= RPC::apiMaximumValidVersion);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE("API versions")
|
||||
{
|
||||
// Update when we change versions
|
||||
static_assert(RPC::apiMinimumSupportedVersion >= 1);
|
||||
static_assert(RPC::apiMinimumSupportedVersion < 2);
|
||||
static_assert(RPC::apiMaximumSupportedVersion >= 2);
|
||||
static_assert(RPC::apiMaximumSupportedVersion < 3);
|
||||
static_assert(RPC::apiMaximumValidVersion >= 3);
|
||||
static_assert(RPC::apiMaximumValidVersion < 4);
|
||||
static_assert(RPC::apiBetaVersion >= 3);
|
||||
static_assert(RPC::apiBetaVersion < 4);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
98
src/doctest/protocol/BuildInfo.cpp
Normal file
98
src/doctest/protocol/BuildInfo.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <xrpl/protocol/BuildInfo.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("BuildInfo");
|
||||
|
||||
TEST_CASE("EncodeSoftwareVersion")
|
||||
{
|
||||
auto encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b7");
|
||||
|
||||
SUBCASE("first two bytes identify the particular implementation, 0x183B")
|
||||
{
|
||||
CHECK(
|
||||
(encodedVersion & 0xFFFF'0000'0000'0000LLU) ==
|
||||
0x183B'0000'0000'0000LLU);
|
||||
}
|
||||
|
||||
SUBCASE("next three bytes: major, minor, patch version")
|
||||
{
|
||||
CHECK(
|
||||
(encodedVersion & 0x0000'FFFF'FF00'0000LLU) ==
|
||||
0x0000'0102'0300'0000LLU);
|
||||
}
|
||||
|
||||
SUBCASE("next two bits indicate release type")
|
||||
{
|
||||
// 01 if a beta
|
||||
auto betaBits = (encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22;
|
||||
CHECK(betaBits == 0b01);
|
||||
|
||||
// 10 if an RC
|
||||
auto rcVersion = BuildInfo::encodeSoftwareVersion("1.2.4-rc7");
|
||||
auto rcBits = (rcVersion & 0x0000'0000'00C0'0000LLU) >> 22;
|
||||
CHECK(rcBits == 0b10);
|
||||
|
||||
// 11 if neither an RC nor a beta
|
||||
auto releaseVersion = BuildInfo::encodeSoftwareVersion("1.2.5");
|
||||
auto releaseBits = (releaseVersion & 0x0000'0000'00C0'0000LLU) >> 22;
|
||||
CHECK(releaseBits == 0b11);
|
||||
}
|
||||
|
||||
SUBCASE("next six bits: rc/beta number (1-63)")
|
||||
{
|
||||
auto v = BuildInfo::encodeSoftwareVersion("1.2.6-b63");
|
||||
auto betaNum = (v & 0x0000'0000'003F'0000LLU) >> 16;
|
||||
CHECK(betaNum == 63);
|
||||
}
|
||||
|
||||
SUBCASE("last two bytes are zeros")
|
||||
{
|
||||
CHECK((encodedVersion & 0x0000'0000'0000'FFFFLLU) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("wrong format version strings")
|
||||
{
|
||||
// no rc/beta number
|
||||
auto v1 = BuildInfo::encodeSoftwareVersion("1.2.3-b");
|
||||
CHECK((v1 & 0x0000'0000'00FF'0000LLU) == 0);
|
||||
|
||||
// rc/beta number out of range
|
||||
auto v2 = BuildInfo::encodeSoftwareVersion("1.2.3-b64");
|
||||
CHECK((v2 & 0x0000'0000'00FF'0000LLU) == 0);
|
||||
}
|
||||
|
||||
SUBCASE("rc/beta number of a release is 0")
|
||||
{
|
||||
auto v = BuildInfo::encodeSoftwareVersion("1.2.6");
|
||||
CHECK((v & 0x0000'0000'003F'0000LLU) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("IsRippledVersion")
|
||||
{
|
||||
auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU;
|
||||
CHECK_FALSE(BuildInfo::isRippledVersion(vFF));
|
||||
|
||||
auto vRippled = 0x183B'0000'0000'0000LLU;
|
||||
CHECK(BuildInfo::isRippledVersion(vRippled));
|
||||
}
|
||||
|
||||
TEST_CASE("IsNewerVersion")
|
||||
{
|
||||
auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU;
|
||||
CHECK_FALSE(BuildInfo::isNewerVersion(vFF));
|
||||
|
||||
auto v159 = BuildInfo::encodeSoftwareVersion("1.5.9");
|
||||
CHECK_FALSE(BuildInfo::isNewerVersion(v159));
|
||||
|
||||
auto vCurrent = BuildInfo::getEncodedVersion();
|
||||
CHECK_FALSE(BuildInfo::isNewerVersion(vCurrent));
|
||||
|
||||
auto vMax = BuildInfo::encodeSoftwareVersion("255.255.255");
|
||||
CHECK(BuildInfo::isNewerVersion(vMax));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
194
src/doctest/protocol/Issue.cpp
Normal file
194
src/doctest/protocol/Issue.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
// Issue doctest - converted from src/test/protocol/Issue_test.cpp
|
||||
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
#if BEAST_MSVC
|
||||
#define STL_SET_HAS_EMPLACE 1
|
||||
#else
|
||||
#define STL_SET_HAS_EMPLACE 0
|
||||
#endif
|
||||
|
||||
#ifndef XRPL_ASSETS_ENABLE_STD_HASH
|
||||
#if BEAST_MAC || BEAST_IOS
|
||||
#define XRPL_ASSETS_ENABLE_STD_HASH 0
|
||||
#else
|
||||
#define XRPL_ASSETS_ENABLE_STD_HASH 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
TEST_SUITE_BEGIN("Issue");
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace xrpl;
|
||||
using Domain = uint256;
|
||||
|
||||
// Comparison, hash tests for uint60 (via base_uint)
|
||||
template <typename Unsigned>
|
||||
void
|
||||
testUnsigned()
|
||||
{
|
||||
Unsigned const u1(1);
|
||||
Unsigned const u2(2);
|
||||
Unsigned const u3(3);
|
||||
|
||||
CHECK(u1 != u2);
|
||||
CHECK(u1 < u2);
|
||||
CHECK(u1 <= u2);
|
||||
CHECK(u2 <= u2);
|
||||
CHECK(u2 == u2);
|
||||
CHECK(u2 >= u2);
|
||||
CHECK(u3 >= u2);
|
||||
CHECK(u3 > u2);
|
||||
|
||||
std::hash<Unsigned> hash;
|
||||
|
||||
CHECK(hash(u1) == hash(u1));
|
||||
CHECK(hash(u2) == hash(u2));
|
||||
CHECK(hash(u3) == hash(u3));
|
||||
CHECK(hash(u1) != hash(u2));
|
||||
CHECK(hash(u1) != hash(u3));
|
||||
CHECK(hash(u2) != hash(u3));
|
||||
}
|
||||
|
||||
// Comparison, hash tests for Issue
|
||||
template <class IssueType>
|
||||
void
|
||||
testIssue()
|
||||
{
|
||||
Currency const c1(1);
|
||||
AccountID const i1(1);
|
||||
Currency const c2(2);
|
||||
AccountID const i2(2);
|
||||
Currency const c3(3);
|
||||
AccountID const i3(3);
|
||||
|
||||
CHECK(IssueType(c1, i1) != IssueType(c2, i1));
|
||||
CHECK(IssueType(c1, i1) < IssueType(c2, i1));
|
||||
CHECK(IssueType(c1, i1) <= IssueType(c2, i1));
|
||||
CHECK(IssueType(c2, i1) <= IssueType(c2, i1));
|
||||
CHECK(IssueType(c2, i1) == IssueType(c2, i1));
|
||||
CHECK(IssueType(c2, i1) >= IssueType(c2, i1));
|
||||
CHECK(IssueType(c3, i1) >= IssueType(c2, i1));
|
||||
CHECK(IssueType(c3, i1) > IssueType(c2, i1));
|
||||
CHECK(IssueType(c1, i1) != IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i1) < IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i1) <= IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i2) <= IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i2) == IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i2) >= IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i3) >= IssueType(c1, i2));
|
||||
CHECK(IssueType(c1, i3) > IssueType(c1, i2));
|
||||
|
||||
std::hash<IssueType> hash;
|
||||
|
||||
CHECK(hash(IssueType(c1, i1)) == hash(IssueType(c1, i1)));
|
||||
CHECK(hash(IssueType(c1, i2)) == hash(IssueType(c1, i2)));
|
||||
CHECK(hash(IssueType(c1, i3)) == hash(IssueType(c1, i3)));
|
||||
CHECK(hash(IssueType(c2, i1)) == hash(IssueType(c2, i1)));
|
||||
CHECK(hash(IssueType(c2, i2)) == hash(IssueType(c2, i2)));
|
||||
CHECK(hash(IssueType(c2, i3)) == hash(IssueType(c2, i3)));
|
||||
CHECK(hash(IssueType(c3, i1)) == hash(IssueType(c3, i1)));
|
||||
CHECK(hash(IssueType(c3, i2)) == hash(IssueType(c3, i2)));
|
||||
CHECK(hash(IssueType(c3, i3)) == hash(IssueType(c3, i3)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c1, i2)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c1, i3)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c2, i1)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c2, i2)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c2, i3)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c3, i1)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c3, i2)));
|
||||
CHECK(hash(IssueType(c1, i1)) != hash(IssueType(c3, i3)));
|
||||
}
|
||||
|
||||
template <class Set>
|
||||
void
|
||||
testIssueSet()
|
||||
{
|
||||
Currency const c1(1);
|
||||
AccountID const i1(1);
|
||||
Currency const c2(2);
|
||||
AccountID const i2(2);
|
||||
Issue const a1(c1, i1);
|
||||
Issue const a2(c2, i2);
|
||||
|
||||
{
|
||||
Set c;
|
||||
|
||||
c.insert(a1);
|
||||
REQUIRE(c.size() == 1);
|
||||
c.insert(a2);
|
||||
REQUIRE(c.size() == 2);
|
||||
|
||||
REQUIRE(c.erase(Issue(c1, i2)) == 0);
|
||||
REQUIRE(c.erase(Issue(c1, i1)) == 1);
|
||||
REQUIRE(c.erase(Issue(c2, i2)) == 1);
|
||||
REQUIRE(c.empty());
|
||||
}
|
||||
|
||||
{
|
||||
Set c;
|
||||
|
||||
c.insert(a1);
|
||||
REQUIRE(c.size() == 1);
|
||||
c.insert(a2);
|
||||
REQUIRE(c.size() == 2);
|
||||
|
||||
REQUIRE(c.erase(Issue(c1, i2)) == 0);
|
||||
REQUIRE(c.erase(Issue(c1, i1)) == 1);
|
||||
REQUIRE(c.erase(Issue(c2, i2)) == 1);
|
||||
REQUIRE(c.empty());
|
||||
|
||||
#if STL_SET_HAS_EMPLACE
|
||||
c.emplace(c1, i1);
|
||||
REQUIRE(c.size() == 1);
|
||||
c.emplace(c2, i2);
|
||||
REQUIRE(c.size() == 2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Currency")
|
||||
{
|
||||
testUnsigned<Currency>();
|
||||
}
|
||||
|
||||
TEST_CASE("AccountID")
|
||||
{
|
||||
testUnsigned<AccountID>();
|
||||
}
|
||||
|
||||
TEST_CASE("Issue")
|
||||
{
|
||||
testIssue<Issue>();
|
||||
}
|
||||
|
||||
TEST_CASE("std::set<Issue>")
|
||||
{
|
||||
testIssueSet<std::set<Issue>>();
|
||||
}
|
||||
|
||||
#if XRPL_ASSETS_ENABLE_STD_HASH
|
||||
TEST_CASE("std::unordered_set<Issue>")
|
||||
{
|
||||
testIssueSet<std::unordered_set<Issue>>();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("hash_set<Issue>")
|
||||
{
|
||||
testIssueSet<hash_set<Issue>>();
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
989
src/doctest/protocol/MultiApiJson.cpp
Normal file
989
src/doctest/protocol/MultiApiJson.cpp
Normal 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();
|
||||
201
src/doctest/protocol/PublicKey.cpp
Normal file
201
src/doctest/protocol/PublicKey.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("PublicKey");
|
||||
|
||||
namespace {
|
||||
|
||||
using blob = std::vector<std::uint8_t>;
|
||||
|
||||
template <class FwdIter, class Container>
|
||||
void
|
||||
hex_to_binary(FwdIter first, FwdIter last, Container& out)
|
||||
{
|
||||
struct Table
|
||||
{
|
||||
int val[256];
|
||||
Table()
|
||||
{
|
||||
std::fill(val, val + 256, 0);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
val['0' + i] = i;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
val['A' + i] = 10 + i;
|
||||
val['a' + i] = 10 + i;
|
||||
}
|
||||
}
|
||||
int
|
||||
operator[](int i)
|
||||
{
|
||||
return val[i];
|
||||
}
|
||||
};
|
||||
|
||||
static Table lut;
|
||||
out.reserve(std::distance(first, last) / 2);
|
||||
while (first != last)
|
||||
{
|
||||
auto const hi(lut[(*first++)]);
|
||||
auto const lo(lut[(*first++)]);
|
||||
out.push_back((hi * 16) + lo);
|
||||
}
|
||||
}
|
||||
|
||||
blob
|
||||
sig(std::string const& hex)
|
||||
{
|
||||
blob b;
|
||||
hex_to_binary(hex.begin(), hex.end(), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
bool
|
||||
check(std::optional<ECDSACanonicality> answer, std::string const& s)
|
||||
{
|
||||
return ecdsaCanonicality(makeSlice(sig(s))) == answer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Canonical")
|
||||
{
|
||||
SUBCASE("Fully canonical")
|
||||
{
|
||||
CHECK(check(
|
||||
ECDSACanonicality::fullyCanonical,
|
||||
"3045"
|
||||
"022100FF478110D1D4294471EC76E0157540C2181F47DEBD25D7F9E7DDCCCD47EE"
|
||||
"E905"
|
||||
"0220078F07CDAE6C240855D084AD91D1479609533C147C93B0AEF19BC9724D003F"
|
||||
"28"));
|
||||
CHECK(check(
|
||||
ECDSACanonicality::fullyCanonical,
|
||||
"3045"
|
||||
"0221009218248292F1762D8A51BE80F8A7F2CD288D810CE781D5955700DA1684DF"
|
||||
"1D2D"
|
||||
"022041A1EE1746BFD72C9760CC93A7AAA8047D52C8833A03A20EAAE92EA19717B4"
|
||||
"54"));
|
||||
CHECK(check(
|
||||
ECDSACanonicality::fullyCanonical,
|
||||
"3044"
|
||||
"02206A9E43775F73B6D1EC420E4DDD222A80D4C6DF5D1BEECC431A91B63C928B75"
|
||||
"81"
|
||||
"022023E9CC2D61DDA6F73EAA6BCB12688BEB0F434769276B3127E4044ED895C9D9"
|
||||
"6B"));
|
||||
}
|
||||
|
||||
SUBCASE("Canonical but not fully canonical")
|
||||
{
|
||||
CHECK(check(
|
||||
ECDSACanonicality::canonical,
|
||||
"3046"
|
||||
"022100F477B3FA6F31C7CB3A0D1AD94A231FDD24B8D78862EE334CEA7CD08F6CBC"
|
||||
"0A1B"
|
||||
"022100928E6BCF1ED2684679730C5414AEC48FD62282B090041C41453C1D064AF5"
|
||||
"97A1"));
|
||||
CHECK(check(
|
||||
ECDSACanonicality::canonical,
|
||||
"3045"
|
||||
"022063E7C7CA93CB2400E413A342C027D00665F8BAB9C22EF0A7B8AE3AAF092230"
|
||||
"B6"
|
||||
"0221008F2E8BB7D09521ABBC277717B14B93170AE6465C5A1B36561099319C4BEB"
|
||||
"254C"));
|
||||
}
|
||||
|
||||
SUBCASE("Valid")
|
||||
{
|
||||
CHECK(check(
|
||||
ECDSACanonicality::fullyCanonical,
|
||||
"3006"
|
||||
"020101"
|
||||
"020102"));
|
||||
CHECK(check(
|
||||
ECDSACanonicality::fullyCanonical,
|
||||
"3044"
|
||||
"02203932c892e2e550f3af8ee4ce9c215a87f9bb831dcac87b2838e2c2eaa891df"
|
||||
"0c"
|
||||
"022030b61dd36543125d56b9f9f3a1f53189e5af33cdda8d77a5209aec03978fa0"
|
||||
"01"));
|
||||
}
|
||||
|
||||
SUBCASE("Invalid")
|
||||
{
|
||||
CHECK(check(
|
||||
std::nullopt,
|
||||
"3005"
|
||||
"0201FF"
|
||||
"0200"));
|
||||
CHECK(check(
|
||||
std::nullopt,
|
||||
"3006"
|
||||
"020101"
|
||||
"020202"));
|
||||
CHECK(check(
|
||||
std::nullopt,
|
||||
"3006"
|
||||
"020701"
|
||||
"020102"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Base58: secp256k1")
|
||||
{
|
||||
auto const pk1 = derivePublicKey(
|
||||
KeyType::secp256k1,
|
||||
generateSecretKey(
|
||||
KeyType::secp256k1, generateSeed("masterpassphrase")));
|
||||
|
||||
auto const pk2 = parseBase58<PublicKey>(
|
||||
TokenType::NodePublic,
|
||||
"n94a1u4jAz288pZLtw6yFWVbi89YamiC6JBXPVUj5zmExe5fTVg9");
|
||||
CHECK(pk2);
|
||||
CHECK(pk1 == *pk2);
|
||||
|
||||
// Try converting short, long and malformed data
|
||||
CHECK_FALSE(parseBase58<PublicKey>(TokenType::NodePublic, ""));
|
||||
CHECK_FALSE(parseBase58<PublicKey>(TokenType::NodePublic, " "));
|
||||
CHECK_FALSE(parseBase58<PublicKey>(TokenType::NodePublic, "!ty89234gh45"));
|
||||
}
|
||||
|
||||
TEST_CASE("Base58: ed25519")
|
||||
{
|
||||
auto const pk1 = derivePublicKey(
|
||||
KeyType::ed25519,
|
||||
generateSecretKey(KeyType::ed25519, generateSeed("masterpassphrase")));
|
||||
|
||||
auto const pk2 = parseBase58<PublicKey>(
|
||||
TokenType::NodePublic,
|
||||
"nHUeeJCSY2dM71oxM8Cgjouf5ekTuev2mwDpc374aLMxzDLXNmjf");
|
||||
CHECK(pk2);
|
||||
CHECK(pk1 == *pk2);
|
||||
}
|
||||
|
||||
TEST_CASE("Miscellaneous operations")
|
||||
{
|
||||
auto const pk1 = derivePublicKey(
|
||||
KeyType::secp256k1,
|
||||
generateSecretKey(
|
||||
KeyType::secp256k1, generateSeed("masterpassphrase")));
|
||||
|
||||
PublicKey pk2(pk1);
|
||||
CHECK(pk1 == pk2);
|
||||
CHECK(pk2 == pk1);
|
||||
|
||||
PublicKey pk3 = derivePublicKey(
|
||||
KeyType::secp256k1,
|
||||
generateSecretKey(
|
||||
KeyType::secp256k1, generateSeed("arbitraryPassPhrase")));
|
||||
// Testing the copy assignment operation of PublicKey class
|
||||
pk3 = pk2;
|
||||
CHECK(pk3 == pk2);
|
||||
CHECK(pk1 == pk3);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
262
src/doctest/protocol/Quality.cpp
Normal file
262
src/doctest/protocol/Quality.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("Quality");
|
||||
|
||||
namespace {
|
||||
|
||||
// Create a raw, non-integral amount from mantissa and exponent
|
||||
STAmount
|
||||
raw(std::uint64_t mantissa, int exponent)
|
||||
{
|
||||
return STAmount(Issue{Currency(3), AccountID(3)}, mantissa, exponent);
|
||||
}
|
||||
|
||||
template <class Integer>
|
||||
STAmount
|
||||
amount(Integer integer, std::enable_if_t<std::is_signed<Integer>::value>* = 0)
|
||||
{
|
||||
static_assert(std::is_integral<Integer>::value, "");
|
||||
return STAmount(integer, false);
|
||||
}
|
||||
|
||||
template <class Integer>
|
||||
STAmount
|
||||
amount(Integer integer, std::enable_if_t<!std::is_signed<Integer>::value>* = 0)
|
||||
{
|
||||
static_assert(std::is_integral<Integer>::value, "");
|
||||
if (integer < 0)
|
||||
return STAmount(-integer, true);
|
||||
return STAmount(integer, false);
|
||||
}
|
||||
|
||||
template <class In, class Out>
|
||||
Amounts
|
||||
amounts(In in, Out out)
|
||||
{
|
||||
return Amounts(amount(in), amount(out));
|
||||
}
|
||||
|
||||
template <class In1, class Out1, class Int, class In2, class Out2>
|
||||
void
|
||||
ceil_in(
|
||||
Quality const& q,
|
||||
In1 in,
|
||||
Out1 out,
|
||||
Int limit,
|
||||
In2 in_expected,
|
||||
Out2 out_expected)
|
||||
{
|
||||
auto expect_result(amounts(in_expected, out_expected));
|
||||
auto actual_result(q.ceil_in(amounts(in, out), amount(limit)));
|
||||
|
||||
CHECK(actual_result == expect_result);
|
||||
}
|
||||
|
||||
template <class In1, class Out1, class Int, class In2, class Out2>
|
||||
void
|
||||
ceil_out(
|
||||
Quality const& q,
|
||||
In1 in,
|
||||
Out1 out,
|
||||
Int limit,
|
||||
In2 in_expected,
|
||||
Out2 out_expected)
|
||||
{
|
||||
auto const expect_result(amounts(in_expected, out_expected));
|
||||
auto const actual_result(q.ceil_out(amounts(in, out), amount(limit)));
|
||||
|
||||
CHECK(actual_result == expect_result);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("comparisons")
|
||||
{
|
||||
STAmount const amount1(noIssue(), 231);
|
||||
STAmount const amount2(noIssue(), 462);
|
||||
STAmount const amount3(noIssue(), 924);
|
||||
|
||||
Quality const q11(Amounts(amount1, amount1));
|
||||
Quality const q12(Amounts(amount1, amount2));
|
||||
Quality const q13(Amounts(amount1, amount3));
|
||||
Quality const q21(Amounts(amount2, amount1));
|
||||
Quality const q31(Amounts(amount3, amount1));
|
||||
|
||||
CHECK(q11 == q11);
|
||||
CHECK(q11 < q12);
|
||||
CHECK(q12 < q13);
|
||||
CHECK(q31 < q21);
|
||||
CHECK(q21 < q11);
|
||||
CHECK(q11 >= q11);
|
||||
CHECK(q12 >= q11);
|
||||
CHECK(q13 >= q12);
|
||||
CHECK(q21 >= q31);
|
||||
CHECK(q11 >= q21);
|
||||
CHECK(q12 > q11);
|
||||
CHECK(q13 > q12);
|
||||
CHECK(q21 > q31);
|
||||
CHECK(q11 > q21);
|
||||
CHECK(q11 <= q11);
|
||||
CHECK(q11 <= q12);
|
||||
CHECK(q12 <= q13);
|
||||
CHECK(q31 <= q21);
|
||||
CHECK(q21 <= q11);
|
||||
CHECK(q31 != q21);
|
||||
}
|
||||
|
||||
TEST_CASE("composition")
|
||||
{
|
||||
STAmount const amount1(noIssue(), 231);
|
||||
STAmount const amount2(noIssue(), 462);
|
||||
STAmount const amount3(noIssue(), 924);
|
||||
|
||||
Quality const q11(Amounts(amount1, amount1));
|
||||
Quality const q12(Amounts(amount1, amount2));
|
||||
Quality const q13(Amounts(amount1, amount3));
|
||||
Quality const q21(Amounts(amount2, amount1));
|
||||
Quality const q31(Amounts(amount3, amount1));
|
||||
|
||||
CHECK(composed_quality(q12, q21) == q11);
|
||||
|
||||
Quality const q13_31(composed_quality(q13, q31));
|
||||
Quality const q31_13(composed_quality(q31, q13));
|
||||
|
||||
CHECK(q13_31 == q31_13);
|
||||
CHECK(q13_31 == q11);
|
||||
}
|
||||
|
||||
TEST_CASE("operations")
|
||||
{
|
||||
Quality const q11(
|
||||
Amounts(STAmount(noIssue(), 731), STAmount(noIssue(), 731)));
|
||||
|
||||
Quality qa(q11);
|
||||
Quality qb(q11);
|
||||
|
||||
CHECK(qa == qb);
|
||||
CHECK(++qa != q11);
|
||||
CHECK(qa != qb);
|
||||
CHECK(--qb != q11);
|
||||
CHECK(qa != qb);
|
||||
CHECK(qb < qa);
|
||||
CHECK(qb++ < qa);
|
||||
CHECK(qb++ < qa);
|
||||
CHECK(qb++ == qa);
|
||||
CHECK(qa < qb);
|
||||
}
|
||||
|
||||
TEST_CASE("ceil_in")
|
||||
{
|
||||
SUBCASE("1 in, 1 out")
|
||||
{
|
||||
Quality q(Amounts(amount(1), amount(1)));
|
||||
|
||||
ceil_in(q, 1, 1, 1, 1, 1); // 1 in, 1 out, limit 1 -> 1 in, 1 out
|
||||
ceil_in(q, 10, 10, 5, 5, 5); // 10 in, 10 out, limit 5 -> 5 in, 5 out
|
||||
ceil_in(q, 5, 5, 10, 5, 5); // 5 in, 5 out, limit 10 -> 5 in, 5 out
|
||||
}
|
||||
|
||||
SUBCASE("1 in, 2 out")
|
||||
{
|
||||
Quality q(Amounts(amount(1), amount(2)));
|
||||
|
||||
ceil_in(
|
||||
q, 40, 80, 40, 40, 80); // 40 in, 80 out, limit 40 -> 40 in, 80 out
|
||||
ceil_in(
|
||||
q, 40, 80, 20, 20, 40); // 40 in, 80 out, limit 20 -> 20 in, 40 out
|
||||
ceil_in(
|
||||
q, 40, 80, 60, 40, 80); // 40 in, 80 out, limit 60 -> 40 in, 80 out
|
||||
}
|
||||
|
||||
SUBCASE("2 in, 1 out")
|
||||
{
|
||||
Quality q(Amounts(amount(2), amount(1)));
|
||||
|
||||
ceil_in(
|
||||
q, 40, 20, 20, 20, 10); // 40 in, 20 out, limit 20 -> 20 in, 10 out
|
||||
ceil_in(
|
||||
q, 40, 20, 40, 40, 20); // 40 in, 20 out, limit 40 -> 40 in, 20 out
|
||||
ceil_in(
|
||||
q, 40, 20, 50, 40, 20); // 40 in, 20 out, limit 50 -> 40 in, 20 out
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ceil_out")
|
||||
{
|
||||
SUBCASE("1 in, 1 out")
|
||||
{
|
||||
Quality q(Amounts(amount(1), amount(1)));
|
||||
|
||||
ceil_out(q, 1, 1, 1, 1, 1); // 1 in, 1 out, limit 1 -> 1 in, 1 out
|
||||
ceil_out(q, 10, 10, 5, 5, 5); // 10 in, 10 out, limit 5 -> 5 in, 5 out
|
||||
ceil_out(
|
||||
q, 10, 10, 20, 10, 10); // 10 in, 10 out, limit 20 -> 10 in, 10 out
|
||||
}
|
||||
|
||||
SUBCASE("1 in, 2 out")
|
||||
{
|
||||
Quality q(Amounts(amount(1), amount(2)));
|
||||
|
||||
ceil_out(
|
||||
q, 40, 80, 40, 20, 40); // 40 in, 80 out, limit 40 -> 20 in, 40 out
|
||||
ceil_out(
|
||||
q, 40, 80, 80, 40, 80); // 40 in, 80 out, limit 80 -> 40 in, 80 out
|
||||
ceil_out(
|
||||
q,
|
||||
40,
|
||||
80,
|
||||
100,
|
||||
40,
|
||||
80); // 40 in, 80 out, limit 100 -> 40 in, 80 out
|
||||
}
|
||||
|
||||
SUBCASE("2 in, 1 out")
|
||||
{
|
||||
Quality q(Amounts(amount(2), amount(1)));
|
||||
|
||||
ceil_out(
|
||||
q, 40, 20, 20, 40, 20); // 40 in, 20 out, limit 20 -> 40 in, 20 out
|
||||
ceil_out(
|
||||
q, 40, 20, 40, 40, 20); // 40 in, 20 out, limit 40 -> 40 in, 20 out
|
||||
ceil_out(
|
||||
q, 40, 20, 10, 20, 10); // 40 in, 20 out, limit 10 -> 20 in, 10 out
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("raw")
|
||||
{
|
||||
Quality q(0x5d048191fb9130daull); // 126836389.7680090
|
||||
Amounts const value(
|
||||
amount(349469768), // 349.469768 XRP
|
||||
raw(2755280000000000ull, -15)); // 2.75528
|
||||
STAmount const limit(raw(4131113916555555, -16)); // .4131113916555555
|
||||
Amounts const result(q.ceil_out(value, limit));
|
||||
CHECK(result.in != beast::zero);
|
||||
}
|
||||
|
||||
TEST_CASE("round")
|
||||
{
|
||||
Quality q(0x59148191fb913522ull); // 57719.63525051682
|
||||
CHECK(q.round(3).rate().getText() == "57800");
|
||||
CHECK(q.round(4).rate().getText() == "57720");
|
||||
CHECK(q.round(5).rate().getText() == "57720");
|
||||
CHECK(q.round(6).rate().getText() == "57719.7");
|
||||
CHECK(q.round(7).rate().getText() == "57719.64");
|
||||
CHECK(q.round(8).rate().getText() == "57719.636");
|
||||
CHECK(q.round(9).rate().getText() == "57719.6353");
|
||||
CHECK(q.round(10).rate().getText() == "57719.63526");
|
||||
CHECK(q.round(11).rate().getText() == "57719.635251");
|
||||
CHECK(q.round(12).rate().getText() == "57719.6352506");
|
||||
CHECK(q.round(13).rate().getText() == "57719.63525052");
|
||||
CHECK(q.round(14).rate().getText() == "57719.635250517");
|
||||
CHECK(q.round(15).rate().getText() == "57719.6352505169");
|
||||
CHECK(q.round(16).rate().getText() == "57719.63525051682");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
116
src/doctest/protocol/STAccount.cpp
Normal file
116
src/doctest/protocol/STAccount.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("STAccount");
|
||||
|
||||
TEST_CASE("STAccount default constructor")
|
||||
{
|
||||
STAccount const defaultAcct;
|
||||
CHECK(defaultAcct.getSType() == STI_ACCOUNT);
|
||||
CHECK(defaultAcct.getText() == "");
|
||||
CHECK(defaultAcct.isDefault() == true);
|
||||
CHECK(defaultAcct.value() == AccountID{});
|
||||
}
|
||||
|
||||
TEST_CASE("STAccount deserialized default")
|
||||
{
|
||||
STAccount const defaultAcct;
|
||||
// Construct a deserialized default STAccount.
|
||||
Serializer s;
|
||||
s.addVL(nullptr, 0);
|
||||
SerialIter sit(s.slice());
|
||||
STAccount const deserializedDefault(sit, sfAccount);
|
||||
CHECK(deserializedDefault.isEquivalent(defaultAcct));
|
||||
}
|
||||
|
||||
TEST_CASE("STAccount constructor from SField")
|
||||
{
|
||||
STAccount const defaultAcct;
|
||||
STAccount const sfAcct{sfAccount};
|
||||
CHECK(sfAcct.getSType() == STI_ACCOUNT);
|
||||
CHECK(sfAcct.getText() == "");
|
||||
CHECK(sfAcct.isDefault());
|
||||
CHECK(sfAcct.value() == AccountID{});
|
||||
CHECK(sfAcct.isEquivalent(defaultAcct));
|
||||
|
||||
Serializer s;
|
||||
sfAcct.add(s);
|
||||
CHECK(s.size() == 1);
|
||||
CHECK(strHex(s) == "00");
|
||||
SerialIter sit(s.slice());
|
||||
STAccount const deserializedSf(sit, sfAccount);
|
||||
CHECK(deserializedSf.isEquivalent(sfAcct));
|
||||
}
|
||||
|
||||
TEST_CASE("STAccount constructor from SField and AccountID")
|
||||
{
|
||||
STAccount const defaultAcct;
|
||||
STAccount const sfAcct{sfAccount};
|
||||
STAccount const zeroAcct{sfAccount, AccountID{}};
|
||||
CHECK(zeroAcct.getText() == "rrrrrrrrrrrrrrrrrrrrrhoLvTp");
|
||||
CHECK(!zeroAcct.isDefault());
|
||||
CHECK(zeroAcct.value() == AccountID{0});
|
||||
CHECK(!zeroAcct.isEquivalent(defaultAcct));
|
||||
CHECK(!zeroAcct.isEquivalent(sfAcct));
|
||||
|
||||
Serializer s;
|
||||
zeroAcct.add(s);
|
||||
CHECK(s.size() == 21);
|
||||
CHECK(strHex(s) == "140000000000000000000000000000000000000000");
|
||||
SerialIter sit(s.slice());
|
||||
STAccount const deserializedZero(sit, sfAccount);
|
||||
CHECK(deserializedZero.isEquivalent(zeroAcct));
|
||||
}
|
||||
|
||||
TEST_CASE("STAccount bad size throws")
|
||||
{
|
||||
// Construct from a VL that is not exactly 160 bits.
|
||||
Serializer s;
|
||||
std::uint8_t const bits128[]{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
s.addVL(bits128, sizeof(bits128));
|
||||
SerialIter sit(s.slice());
|
||||
CHECK_THROWS_AS(STAccount(sit, sfAccount), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_CASE("STAccount equivalent types")
|
||||
{
|
||||
STAccount const zeroAcct{sfAccount, AccountID{}};
|
||||
// Interestingly, equal values but different types are equivalent!
|
||||
STAccount const regKey{sfRegularKey, AccountID{}};
|
||||
CHECK(regKey.isEquivalent(zeroAcct));
|
||||
}
|
||||
|
||||
TEST_CASE("STAccount assignment")
|
||||
{
|
||||
STAccount const defaultAcct;
|
||||
STAccount const zeroAcct{sfAccount, AccountID{}};
|
||||
|
||||
STAccount assignAcct;
|
||||
CHECK(assignAcct.isEquivalent(defaultAcct));
|
||||
CHECK(assignAcct.isDefault());
|
||||
assignAcct = AccountID{};
|
||||
CHECK(!assignAcct.isEquivalent(defaultAcct));
|
||||
CHECK(assignAcct.isEquivalent(zeroAcct));
|
||||
CHECK(!assignAcct.isDefault());
|
||||
}
|
||||
|
||||
TEST_CASE("AccountID parsing")
|
||||
{
|
||||
auto const s = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
auto const parsed = parseBase58<AccountID>(s);
|
||||
REQUIRE(parsed);
|
||||
CHECK(toBase58(*parsed) == s);
|
||||
}
|
||||
|
||||
TEST_CASE("AccountID invalid parsing")
|
||||
{
|
||||
auto const s =
|
||||
"âabcd1rNxp4h8apvRis6mJf9Sh8C6iRxfrDWNâabcdAVâ\xc2\x80\xc2\x8f";
|
||||
CHECK(!parseBase58<AccountID>(s));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
123
src/doctest/protocol/STInteger.cpp
Normal file
123
src/doctest/protocol/STInteger.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/STInteger.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("STInteger");
|
||||
|
||||
TEST_CASE("UInt8")
|
||||
{
|
||||
STUInt8 u8(255);
|
||||
CHECK(u8.value() == 255);
|
||||
CHECK(u8.getText() == "255");
|
||||
CHECK(u8.getSType() == STI_UINT8);
|
||||
CHECK(u8.getJson(JsonOptions::none) == 255);
|
||||
|
||||
// there is some special handling for sfTransactionResult
|
||||
STUInt8 tr(sfTransactionResult, 0);
|
||||
CHECK(tr.value() == 0);
|
||||
CHECK(
|
||||
tr.getText() ==
|
||||
"The transaction was applied. Only final in a validated ledger.");
|
||||
CHECK(tr.getSType() == STI_UINT8);
|
||||
CHECK(tr.getJson(JsonOptions::none) == "tesSUCCESS");
|
||||
|
||||
// invalid transaction result
|
||||
STUInt8 tr2(sfTransactionResult, 255);
|
||||
CHECK(tr2.value() == 255);
|
||||
CHECK(tr2.getText() == "255");
|
||||
CHECK(tr2.getSType() == STI_UINT8);
|
||||
CHECK(tr2.getJson(JsonOptions::none) == 255);
|
||||
}
|
||||
|
||||
TEST_CASE("UInt16")
|
||||
{
|
||||
STUInt16 u16(65535);
|
||||
CHECK(u16.value() == 65535);
|
||||
CHECK(u16.getText() == "65535");
|
||||
CHECK(u16.getSType() == STI_UINT16);
|
||||
CHECK(u16.getJson(JsonOptions::none) == 65535);
|
||||
|
||||
// there is some special handling for sfLedgerEntryType
|
||||
STUInt16 let(sfLedgerEntryType, ltACCOUNT_ROOT);
|
||||
CHECK(let.value() == ltACCOUNT_ROOT);
|
||||
CHECK(let.getText() == "AccountRoot");
|
||||
CHECK(let.getSType() == STI_UINT16);
|
||||
CHECK(let.getJson(JsonOptions::none) == "AccountRoot");
|
||||
|
||||
// there is some special handling for sfTransactionType
|
||||
STUInt16 tlt(sfTransactionType, ttPAYMENT);
|
||||
CHECK(tlt.value() == ttPAYMENT);
|
||||
CHECK(tlt.getText() == "Payment");
|
||||
CHECK(tlt.getSType() == STI_UINT16);
|
||||
CHECK(tlt.getJson(JsonOptions::none) == "Payment");
|
||||
}
|
||||
|
||||
TEST_CASE("UInt32")
|
||||
{
|
||||
STUInt32 u32(4'294'967'295u);
|
||||
CHECK(u32.value() == 4'294'967'295u);
|
||||
CHECK(u32.getText() == "4294967295");
|
||||
CHECK(u32.getSType() == STI_UINT32);
|
||||
CHECK(u32.getJson(JsonOptions::none) == 4'294'967'295u);
|
||||
|
||||
// there is some special handling for sfPermissionValue
|
||||
STUInt32 pv(sfPermissionValue, ttPAYMENT + 1);
|
||||
CHECK(pv.value() == ttPAYMENT + 1);
|
||||
CHECK(pv.getText() == "Payment");
|
||||
CHECK(pv.getSType() == STI_UINT32);
|
||||
CHECK(pv.getJson(JsonOptions::none) == "Payment");
|
||||
STUInt32 pv2(sfPermissionValue, PaymentMint);
|
||||
CHECK(pv2.value() == PaymentMint);
|
||||
CHECK(pv2.getText() == "PaymentMint");
|
||||
CHECK(pv2.getSType() == STI_UINT32);
|
||||
CHECK(pv2.getJson(JsonOptions::none) == "PaymentMint");
|
||||
}
|
||||
|
||||
TEST_CASE("UInt64")
|
||||
{
|
||||
STUInt64 u64(0xFFFFFFFFFFFFFFFFull);
|
||||
CHECK(u64.value() == 0xFFFFFFFFFFFFFFFFull);
|
||||
CHECK(u64.getText() == "18446744073709551615");
|
||||
CHECK(u64.getSType() == STI_UINT64);
|
||||
|
||||
// By default, getJson returns hex string
|
||||
auto jsonVal = u64.getJson(JsonOptions::none);
|
||||
CHECK(jsonVal.isString());
|
||||
CHECK(jsonVal.asString() == "ffffffffffffffff");
|
||||
|
||||
STUInt64 u64_2(sfMaximumAmount, 0xFFFFFFFFFFFFFFFFull);
|
||||
CHECK(u64_2.value() == 0xFFFFFFFFFFFFFFFFull);
|
||||
CHECK(u64_2.getText() == "18446744073709551615");
|
||||
CHECK(u64_2.getSType() == STI_UINT64);
|
||||
CHECK(u64_2.getJson(JsonOptions::none) == "18446744073709551615");
|
||||
}
|
||||
|
||||
TEST_CASE("Int32")
|
||||
{
|
||||
SUBCASE("min value")
|
||||
{
|
||||
int const minInt32 = -2147483648;
|
||||
STInt32 i32(minInt32);
|
||||
CHECK(i32.value() == minInt32);
|
||||
CHECK(i32.getText() == "-2147483648");
|
||||
CHECK(i32.getSType() == STI_INT32);
|
||||
CHECK(i32.getJson(JsonOptions::none) == minInt32);
|
||||
}
|
||||
|
||||
SUBCASE("max value")
|
||||
{
|
||||
int const maxInt32 = 2147483647;
|
||||
STInt32 i32(maxInt32);
|
||||
CHECK(i32.value() == maxInt32);
|
||||
CHECK(i32.getText() == "2147483647");
|
||||
CHECK(i32.getSType() == STI_INT32);
|
||||
CHECK(i32.getJson(JsonOptions::none) == maxInt32);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
194
src/doctest/protocol/STNumber.cpp
Normal file
194
src/doctest/protocol/STNumber.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include <xrpl/json/json_forwards.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("STNumber");
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
testCombo(Number number)
|
||||
{
|
||||
STNumber const before{sfNumber, number};
|
||||
CHECK(number == before);
|
||||
Serializer s;
|
||||
before.add(s);
|
||||
CHECK(s.size() == 12);
|
||||
SerialIter sit(s.slice());
|
||||
STNumber const after{sit, sfNumber};
|
||||
CHECK(after.isEquivalent(before));
|
||||
CHECK(number == after);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("STNumber default constructor")
|
||||
{
|
||||
static_assert(!std::is_convertible_v<STNumber*, Number*>);
|
||||
|
||||
STNumber const stnum{sfNumber};
|
||||
CHECK(stnum.getSType() == STI_NUMBER);
|
||||
CHECK(stnum.getText() == "0");
|
||||
CHECK(stnum.isDefault() == true);
|
||||
CHECK(stnum.value() == Number{0});
|
||||
}
|
||||
|
||||
TEST_CASE("STNumber mantissa serialization")
|
||||
{
|
||||
std::initializer_list<std::int64_t> const mantissas = {
|
||||
std::numeric_limits<std::int64_t>::min(),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
std::numeric_limits<std::int64_t>::max()};
|
||||
for (std::int64_t mantissa : mantissas)
|
||||
testCombo(Number{mantissa});
|
||||
}
|
||||
|
||||
TEST_CASE("STNumber exponent serialization")
|
||||
{
|
||||
std::initializer_list<std::int32_t> const exponents = {
|
||||
Number::minExponent, -1, 0, 1, Number::maxExponent - 1};
|
||||
for (std::int32_t exponent : exponents)
|
||||
testCombo(Number{123, exponent});
|
||||
}
|
||||
|
||||
TEST_CASE("STNumber multiplication with STAmount")
|
||||
{
|
||||
STAmount const strikePrice{noIssue(), 100};
|
||||
STNumber const factor{sfNumber, 100};
|
||||
auto const iouValue = strikePrice.iou();
|
||||
IOUAmount totalValue{iouValue * factor};
|
||||
STAmount const totalAmount{totalValue, strikePrice.issue()};
|
||||
CHECK(totalAmount == Number{10'000});
|
||||
}
|
||||
|
||||
TEST_CASE("numberFromJson integer values")
|
||||
{
|
||||
CHECK(numberFromJson(sfNumber, Json::Value(42)) == STNumber(sfNumber, 42));
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, Json::Value(-42)) == STNumber(sfNumber, -42));
|
||||
CHECK(numberFromJson(sfNumber, Json::UInt(42)) == STNumber(sfNumber, 42));
|
||||
}
|
||||
|
||||
TEST_CASE("numberFromJson string values")
|
||||
{
|
||||
CHECK(numberFromJson(sfNumber, "-123") == STNumber(sfNumber, -123));
|
||||
CHECK(numberFromJson(sfNumber, "123") == STNumber(sfNumber, 123));
|
||||
CHECK(numberFromJson(sfNumber, "-123") == STNumber(sfNumber, -123));
|
||||
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, "3.14") ==
|
||||
STNumber(sfNumber, Number(314, -2)));
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, "-3.14") ==
|
||||
STNumber(sfNumber, -Number(314, -2)));
|
||||
CHECK(numberFromJson(sfNumber, "3.14e2") == STNumber(sfNumber, 314));
|
||||
CHECK(numberFromJson(sfNumber, "-3.14e2") == STNumber(sfNumber, -314));
|
||||
|
||||
CHECK(numberFromJson(sfNumber, "1000e-2") == STNumber(sfNumber, 10));
|
||||
CHECK(numberFromJson(sfNumber, "-1000e-2") == STNumber(sfNumber, -10));
|
||||
}
|
||||
|
||||
TEST_CASE("numberFromJson zero values")
|
||||
{
|
||||
CHECK(numberFromJson(sfNumber, "0") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "0.0") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "0.000") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "-0") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "-0.0") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "-0.000") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "0e6") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "0.0e6") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "0.000e6") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "-0e6") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "-0.0e6") == STNumber(sfNumber, 0));
|
||||
CHECK(numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0));
|
||||
}
|
||||
|
||||
TEST_CASE("numberFromJson int limits")
|
||||
{
|
||||
constexpr auto imin = std::numeric_limits<int>::min();
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, imin) == STNumber(sfNumber, Number(imin, 0)));
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, std::to_string(imin)) ==
|
||||
STNumber(sfNumber, Number(imin, 0)));
|
||||
|
||||
constexpr auto imax = std::numeric_limits<int>::max();
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, imax) == STNumber(sfNumber, Number(imax, 0)));
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, std::to_string(imax)) ==
|
||||
STNumber(sfNumber, Number(imax, 0)));
|
||||
|
||||
constexpr auto umax = std::numeric_limits<unsigned int>::max();
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, umax) == STNumber(sfNumber, Number(umax, 0)));
|
||||
CHECK(
|
||||
numberFromJson(sfNumber, std::to_string(umax)) ==
|
||||
STNumber(sfNumber, Number(umax, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("numberFromJson invalid values")
|
||||
{
|
||||
SUBCASE("empty string")
|
||||
{
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, ""), std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("just e")
|
||||
{
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "e"), std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("trailing e")
|
||||
{
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "1e"), std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("leading e")
|
||||
{
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "e2"), std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("null json")
|
||||
{
|
||||
CHECK_THROWS_AS(
|
||||
numberFromJson(sfNumber, Json::Value()), std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("very large number")
|
||||
{
|
||||
CHECK_THROWS_AS(
|
||||
numberFromJson(
|
||||
sfNumber,
|
||||
"1234567890123456789012345678901234567890123456789012345678"
|
||||
"9012345678901234567890123456789012345678901234567890123456"
|
||||
"78901234567890123456789012345678901234567890"),
|
||||
std::bad_cast);
|
||||
}
|
||||
|
||||
SUBCASE("leading zeros")
|
||||
{
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "001"), std::runtime_error);
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "000.0"), std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("dangling dot")
|
||||
{
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, ".1"), std::runtime_error);
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "1."), std::runtime_error);
|
||||
CHECK_THROWS_AS(numberFromJson(sfNumber, "1.e3"), std::runtime_error);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
257
src/doctest/protocol/SecretKey.cpp
Normal file
257
src/doctest/protocol/SecretKey.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include <xrpl/beast/utility/rngfill.h>
|
||||
#include <xrpl/crypto/csprng.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
struct TestKeyData
|
||||
{
|
||||
std::array<std::uint8_t, 16> seed;
|
||||
std::array<std::uint8_t, 33> pubkey;
|
||||
std::array<std::uint8_t, 32> seckey;
|
||||
char const* addr;
|
||||
};
|
||||
|
||||
using blob = std::vector<std::uint8_t>;
|
||||
|
||||
TEST_SUITE_BEGIN("SecretKey");
|
||||
|
||||
TEST_CASE("secp256k1: canonicality")
|
||||
{
|
||||
std::array<std::uint8_t, 32> const digestData{
|
||||
0x34, 0xC1, 0x90, 0x28, 0xC8, 0x0D, 0x21, 0xF3, 0xF4, 0x8C, 0x93,
|
||||
0x54, 0x89, 0x5F, 0x8D, 0x5B, 0xF0, 0xD5, 0xEE, 0x7F, 0xF4, 0x57,
|
||||
0x64, 0x7C, 0xF6, 0x55, 0xF5, 0x53, 0x0A, 0x30, 0x22, 0xA7};
|
||||
|
||||
std::array<std::uint8_t, 33> const pkData{
|
||||
0x02, 0x50, 0x96, 0xEB, 0x12, 0xD3, 0xE9, 0x24, 0x23, 0x4E, 0x71,
|
||||
0x62, 0x36, 0x9C, 0x11, 0xD8, 0xBF, 0x87, 0x7E, 0xDA, 0x23, 0x87,
|
||||
0x78, 0xE7, 0xA3, 0x1F, 0xF0, 0xAA, 0xC5, 0xD0, 0xDB, 0xCF, 0x37};
|
||||
|
||||
std::array<std::uint8_t, 32> const skData{
|
||||
0xAA, 0x92, 0x14, 0x17, 0xE7, 0xE5, 0xC2, 0x99, 0xDA, 0x4E, 0xEC,
|
||||
0x16, 0xD1, 0xCA, 0xA9, 0x2F, 0x19, 0xB1, 0x9F, 0x2A, 0x68, 0x51,
|
||||
0x1F, 0x68, 0xEC, 0x73, 0xBB, 0xB2, 0xF5, 0x23, 0x6F, 0x3D};
|
||||
|
||||
std::array<std::uint8_t, 71> const sig{
|
||||
0x30, 0x45, 0x02, 0x21, 0x00, 0xB4, 0x9D, 0x07, 0xF0, 0xE9, 0x34, 0xBA,
|
||||
0x46, 0x8C, 0x0E, 0xFC, 0x78, 0x11, 0x77, 0x91, 0x40, 0x8D, 0x1F, 0xB8,
|
||||
0xB6, 0x3A, 0x64, 0x92, 0xAD, 0x39, 0x5A, 0xC2, 0xF3, 0x60, 0xF2, 0x46,
|
||||
0x60, 0x02, 0x20, 0x50, 0x87, 0x39, 0xDB, 0x0A, 0x2E, 0xF8, 0x16, 0x76,
|
||||
0xE3, 0x9F, 0x45, 0x9C, 0x8B, 0xBB, 0x07, 0xA0, 0x9C, 0x3E, 0x9F, 0x9B,
|
||||
0xEB, 0x69, 0x62, 0x94, 0xD5, 0x24, 0xD4, 0x79, 0xD6, 0x27, 0x40};
|
||||
|
||||
std::array<std::uint8_t, 72> const non{
|
||||
0x30, 0x46, 0x02, 0x21, 0x00, 0xB4, 0x9D, 0x07, 0xF0, 0xE9, 0x34, 0xBA,
|
||||
0x46, 0x8C, 0x0E, 0xFC, 0x78, 0x11, 0x77, 0x91, 0x40, 0x8D, 0x1F, 0xB8,
|
||||
0xB6, 0x3A, 0x64, 0x92, 0xAD, 0x39, 0x5A, 0xC2, 0xF3, 0x60, 0xF2, 0x46,
|
||||
0x60, 0x02, 0x21, 0x00, 0xAF, 0x78, 0xC6, 0x24, 0xF5, 0xD1, 0x07, 0xE9,
|
||||
0x89, 0x1C, 0x60, 0xBA, 0x63, 0x74, 0x44, 0xF7, 0x1A, 0x12, 0x9E, 0x47,
|
||||
0x13, 0x5D, 0x36, 0xD9, 0x2A, 0xFD, 0x39, 0xB8, 0x56, 0x60, 0x1A, 0x01};
|
||||
|
||||
auto const digest = uint256::fromVoid(digestData.data());
|
||||
|
||||
PublicKey const pk{makeSlice(pkData)};
|
||||
SecretKey const sk{makeSlice(skData)};
|
||||
|
||||
{
|
||||
auto const canonicality = ecdsaCanonicality(makeSlice(sig));
|
||||
CHECK(canonicality);
|
||||
CHECK(*canonicality == ECDSACanonicality::fullyCanonical);
|
||||
}
|
||||
|
||||
{
|
||||
auto const canonicality = ecdsaCanonicality(makeSlice(non));
|
||||
CHECK(canonicality);
|
||||
CHECK(*canonicality != ECDSACanonicality::fullyCanonical);
|
||||
}
|
||||
|
||||
CHECK(verifyDigest(pk, digest, makeSlice(sig), false));
|
||||
CHECK(verifyDigest(pk, digest, makeSlice(sig), true));
|
||||
CHECK(verifyDigest(pk, digest, makeSlice(non), false));
|
||||
CHECK(!verifyDigest(pk, digest, makeSlice(non), true));
|
||||
}
|
||||
|
||||
TEST_CASE("secp256k1: digest signing & verification")
|
||||
{
|
||||
for (std::size_t i = 0; i < 32; i++)
|
||||
{
|
||||
auto const [pk, sk] = randomKeyPair(KeyType::secp256k1);
|
||||
|
||||
CHECK(pk == derivePublicKey(KeyType::secp256k1, sk));
|
||||
CHECK(*publicKeyType(pk) == KeyType::secp256k1);
|
||||
|
||||
for (std::size_t j = 0; j < 32; j++)
|
||||
{
|
||||
uint256 digest;
|
||||
beast::rngfill(digest.data(), digest.size(), crypto_prng());
|
||||
|
||||
auto sig = signDigest(pk, sk, digest);
|
||||
|
||||
CHECK(sig.size() != 0);
|
||||
CHECK(verifyDigest(pk, digest, sig, true));
|
||||
|
||||
// Wrong digest:
|
||||
CHECK(!verifyDigest(pk, ~digest, sig, true));
|
||||
|
||||
// Slightly change the signature:
|
||||
if (auto ptr = sig.data())
|
||||
ptr[j % sig.size()]++;
|
||||
|
||||
// Wrong signature:
|
||||
CHECK(!verifyDigest(pk, digest, sig, true));
|
||||
|
||||
// Wrong digest and signature:
|
||||
CHECK(!verifyDigest(pk, ~digest, sig, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSigning(KeyType type)
|
||||
{
|
||||
for (std::size_t i = 0; i < 32; i++)
|
||||
{
|
||||
auto const [pk, sk] = randomKeyPair(type);
|
||||
|
||||
CHECK(pk == derivePublicKey(type, sk));
|
||||
CHECK(*publicKeyType(pk) == type);
|
||||
|
||||
for (std::size_t j = 0; j < 32; j++)
|
||||
{
|
||||
std::vector<std::uint8_t> data(64 + (8 * i) + j);
|
||||
beast::rngfill(data.data(), data.size(), crypto_prng());
|
||||
|
||||
auto sig = sign(pk, sk, makeSlice(data));
|
||||
|
||||
CHECK(sig.size() != 0);
|
||||
CHECK(verify(pk, makeSlice(data), sig));
|
||||
|
||||
// Construct wrong data:
|
||||
auto badData = data;
|
||||
|
||||
// swaps the smallest and largest elements in buffer
|
||||
std::iter_swap(
|
||||
std::min_element(badData.begin(), badData.end()),
|
||||
std::max_element(badData.begin(), badData.end()));
|
||||
|
||||
// Wrong data: should fail
|
||||
CHECK(!verify(pk, makeSlice(badData), sig));
|
||||
|
||||
// Slightly change the signature:
|
||||
if (auto ptr = sig.data())
|
||||
ptr[j % sig.size()]++;
|
||||
|
||||
// Wrong signature: should fail
|
||||
CHECK(!verify(pk, makeSlice(data), sig));
|
||||
|
||||
// Wrong data and signature: should fail
|
||||
CHECK(!verify(pk, makeSlice(badData), sig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
314
src/doctest/protocol/Seed.cpp
Normal file
314
src/doctest/protocol/Seed.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/utility/rngfill.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("Seed");
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
equal(Seed const& lhs, Seed const& rhs)
|
||||
{
|
||||
return std::equal(
|
||||
lhs.data(),
|
||||
lhs.data() + lhs.size(),
|
||||
rhs.data(),
|
||||
rhs.data() + rhs.size());
|
||||
}
|
||||
|
||||
std::string
|
||||
testPassphrase(std::string passphrase)
|
||||
{
|
||||
auto const seed1 = generateSeed(passphrase);
|
||||
auto const seed2 = parseBase58<Seed>(toBase58(seed1));
|
||||
|
||||
CHECK(static_cast<bool>(seed2));
|
||||
CHECK(equal(seed1, *seed2));
|
||||
return toBase58(seed1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("construction")
|
||||
{
|
||||
SUBCASE("from raw bytes")
|
||||
{
|
||||
std::uint8_t src[16];
|
||||
|
||||
for (std::uint8_t i = 0; i < 64; i++)
|
||||
{
|
||||
beast::rngfill(src, sizeof(src), default_prng());
|
||||
Seed const seed({src, sizeof(src)});
|
||||
CHECK(memcmp(seed.data(), src, sizeof(src)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("from uint128")
|
||||
{
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
uint128 src;
|
||||
beast::rngfill(src.data(), src.size(), default_prng());
|
||||
Seed const seed(src);
|
||||
CHECK(memcmp(seed.data(), src.data(), src.size()) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("generation from passphrase")
|
||||
{
|
||||
CHECK(
|
||||
testPassphrase("masterpassphrase") == "snoPBrXtMeMyMHUVTgbuqAfg1SUTb");
|
||||
CHECK(
|
||||
testPassphrase("Non-Random Passphrase") ==
|
||||
"snMKnVku798EnBwUfxeSD8953sLYA");
|
||||
CHECK(
|
||||
testPassphrase("cookies excitement hand public") ==
|
||||
"sspUXGrmjQhq6mgc24jiRuevZiwKT");
|
||||
}
|
||||
|
||||
TEST_CASE("base58 operations")
|
||||
{
|
||||
SUBCASE("success")
|
||||
{
|
||||
CHECK(parseBase58<Seed>("snoPBrXtMeMyMHUVTgbuqAfg1SUTb"));
|
||||
CHECK(parseBase58<Seed>("snMKnVku798EnBwUfxeSD8953sLYA"));
|
||||
CHECK(parseBase58<Seed>("sspUXGrmjQhq6mgc24jiRuevZiwKT"));
|
||||
}
|
||||
|
||||
SUBCASE("failure")
|
||||
{
|
||||
CHECK_FALSE(parseBase58<Seed>(""));
|
||||
CHECK_FALSE(parseBase58<Seed>("sspUXGrmjQhq6mgc24jiRuevZiwK"));
|
||||
CHECK_FALSE(parseBase58<Seed>("sspUXGrmjQhq6mgc24jiRuevZiwKTT"));
|
||||
CHECK_FALSE(parseBase58<Seed>("sspOXGrmjQhq6mgc24jiRuevZiwKT"));
|
||||
CHECK_FALSE(parseBase58<Seed>("ssp/XGrmjQhq6mgc24jiRuevZiwKT"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("random generation")
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
auto const seed1 = randomSeed();
|
||||
auto const seed2 = parseBase58<Seed>(toBase58(seed1));
|
||||
|
||||
CHECK(static_cast<bool>(seed2));
|
||||
CHECK(equal(seed1, *seed2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Node keypair generation & signing (secp256k1)")
|
||||
{
|
||||
std::string const message1 = "http://www.ripple.com";
|
||||
std::string const message2 = "https://www.ripple.com";
|
||||
|
||||
auto const secretKey =
|
||||
generateSecretKey(KeyType::secp256k1, generateSeed("masterpassphrase"));
|
||||
auto const publicKey = derivePublicKey(KeyType::secp256k1, secretKey);
|
||||
|
||||
CHECK(
|
||||
toBase58(TokenType::NodePublic, publicKey) ==
|
||||
"n94a1u4jAz288pZLtw6yFWVbi89YamiC6JBXPVUj5zmExe5fTVg9");
|
||||
CHECK(
|
||||
toBase58(TokenType::NodePrivate, secretKey) ==
|
||||
"pnen77YEeUd4fFKG7iycBWcwKpTaeFRkW2WFostaATy1DSupwXe");
|
||||
CHECK(
|
||||
to_string(calcNodeID(publicKey)) ==
|
||||
"7E59C17D50F5959C7B158FEC95C8F815BF653DC8");
|
||||
|
||||
auto sig = sign(publicKey, secretKey, makeSlice(message1));
|
||||
CHECK(sig.size() != 0);
|
||||
CHECK(verify(publicKey, makeSlice(message1), sig));
|
||||
|
||||
// Correct public key but wrong message
|
||||
CHECK_FALSE(verify(publicKey, makeSlice(message2), sig));
|
||||
|
||||
// Verify with incorrect public key
|
||||
{
|
||||
auto const otherPublicKey = derivePublicKey(
|
||||
KeyType::secp256k1,
|
||||
generateSecretKey(
|
||||
KeyType::secp256k1, generateSeed("otherpassphrase")));
|
||||
|
||||
CHECK_FALSE(verify(otherPublicKey, makeSlice(message1), sig));
|
||||
}
|
||||
|
||||
// Correct public key but wrong signature
|
||||
{
|
||||
// Slightly change the signature:
|
||||
if (auto ptr = sig.data())
|
||||
ptr[sig.size() / 2]++;
|
||||
|
||||
CHECK_FALSE(verify(publicKey, makeSlice(message1), sig));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Node keypair generation & signing (ed25519)")
|
||||
{
|
||||
std::string const message1 = "http://www.ripple.com";
|
||||
std::string const message2 = "https://www.ripple.com";
|
||||
|
||||
auto const secretKey =
|
||||
generateSecretKey(KeyType::ed25519, generateSeed("masterpassphrase"));
|
||||
auto const publicKey = derivePublicKey(KeyType::ed25519, secretKey);
|
||||
|
||||
CHECK(
|
||||
toBase58(TokenType::NodePublic, publicKey) ==
|
||||
"nHUeeJCSY2dM71oxM8Cgjouf5ekTuev2mwDpc374aLMxzDLXNmjf");
|
||||
CHECK(
|
||||
toBase58(TokenType::NodePrivate, secretKey) ==
|
||||
"paKv46LztLqK3GaKz1rG2nQGN6M4JLyRtxFBYFTw4wAVHtGys36");
|
||||
CHECK(
|
||||
to_string(calcNodeID(publicKey)) ==
|
||||
"AA066C988C712815CC37AF71472B7CBBBD4E2A0A");
|
||||
|
||||
auto sig = sign(publicKey, secretKey, makeSlice(message1));
|
||||
CHECK(sig.size() != 0);
|
||||
CHECK(verify(publicKey, makeSlice(message1), sig));
|
||||
|
||||
// Correct public key but wrong message
|
||||
CHECK_FALSE(verify(publicKey, makeSlice(message2), sig));
|
||||
|
||||
// Verify with incorrect public key
|
||||
{
|
||||
auto const otherPublicKey = derivePublicKey(
|
||||
KeyType::ed25519,
|
||||
generateSecretKey(
|
||||
KeyType::ed25519, generateSeed("otherpassphrase")));
|
||||
|
||||
CHECK_FALSE(verify(otherPublicKey, makeSlice(message1), sig));
|
||||
}
|
||||
|
||||
// Correct public key but wrong signature
|
||||
{
|
||||
if (auto ptr = sig.data())
|
||||
ptr[sig.size() / 2]++;
|
||||
|
||||
CHECK_FALSE(verify(publicKey, makeSlice(message1), sig));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Account keypair generation & signing (secp256k1)")
|
||||
{
|
||||
std::string const message1 = "http://www.ripple.com";
|
||||
std::string const message2 = "https://www.ripple.com";
|
||||
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase"));
|
||||
|
||||
CHECK(toBase58(calcAccountID(pk)) == "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
|
||||
CHECK(
|
||||
toBase58(TokenType::AccountPublic, pk) ==
|
||||
"aBQG8RQAzjs1eTKFEAQXr2gS4utcDiEC9wmi7pfUPTi27VCahwgw");
|
||||
CHECK(
|
||||
toBase58(TokenType::AccountSecret, sk) ==
|
||||
"p9JfM6HHi64m6mvB6v5k7G2b1cXzGmYiCNJf6GHPKvFTWdeRVjh");
|
||||
|
||||
auto sig = sign(pk, sk, makeSlice(message1));
|
||||
CHECK(sig.size() != 0);
|
||||
CHECK(verify(pk, makeSlice(message1), sig));
|
||||
|
||||
// Correct public key but wrong message
|
||||
CHECK_FALSE(verify(pk, makeSlice(message2), sig));
|
||||
|
||||
// Verify with incorrect public key
|
||||
{
|
||||
auto const otherKeyPair = generateKeyPair(
|
||||
KeyType::secp256k1, generateSeed("otherpassphrase"));
|
||||
|
||||
CHECK_FALSE(verify(otherKeyPair.first, makeSlice(message1), sig));
|
||||
}
|
||||
|
||||
// Correct public key but wrong signature
|
||||
{
|
||||
if (auto ptr = sig.data())
|
||||
ptr[sig.size() / 2]++;
|
||||
|
||||
CHECK_FALSE(verify(pk, makeSlice(message1), sig));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Account keypair generation & signing (ed25519)")
|
||||
{
|
||||
std::string const message1 = "http://www.ripple.com";
|
||||
std::string const message2 = "https://www.ripple.com";
|
||||
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::ed25519, generateSeed("masterpassphrase"));
|
||||
|
||||
CHECK(to_string(calcAccountID(pk)) == "rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf");
|
||||
CHECK(
|
||||
toBase58(TokenType::AccountPublic, pk) ==
|
||||
"aKGheSBjmCsKJVuLNKRAKpZXT6wpk2FCuEZAXJupXgdAxX5THCqR");
|
||||
CHECK(
|
||||
toBase58(TokenType::AccountSecret, sk) ==
|
||||
"pwDQjwEhbUBmPuEjFpEG75bFhv2obkCB7NxQsfFxM7xGHBMVPu9");
|
||||
|
||||
auto sig = sign(pk, sk, makeSlice(message1));
|
||||
CHECK(sig.size() != 0);
|
||||
CHECK(verify(pk, makeSlice(message1), sig));
|
||||
|
||||
// Correct public key but wrong message
|
||||
CHECK_FALSE(verify(pk, makeSlice(message2), sig));
|
||||
|
||||
// Verify with incorrect public key
|
||||
{
|
||||
auto const otherKeyPair =
|
||||
generateKeyPair(KeyType::ed25519, generateSeed("otherpassphrase"));
|
||||
|
||||
CHECK_FALSE(verify(otherKeyPair.first, makeSlice(message1), sig));
|
||||
}
|
||||
|
||||
// Correct public key but wrong signature
|
||||
{
|
||||
if (auto ptr = sig.data())
|
||||
ptr[sig.size() / 2]++;
|
||||
|
||||
CHECK_FALSE(verify(pk, makeSlice(message1), sig));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Parsing")
|
||||
{
|
||||
// account IDs and node and account public and private
|
||||
// keys should not be parseable as seeds.
|
||||
|
||||
auto const node1 = randomKeyPair(KeyType::secp256k1);
|
||||
|
||||
CHECK_FALSE(parseGenericSeed(toBase58(TokenType::NodePublic, node1.first)));
|
||||
CHECK_FALSE(
|
||||
parseGenericSeed(toBase58(TokenType::NodePrivate, node1.second)));
|
||||
|
||||
auto const node2 = randomKeyPair(KeyType::ed25519);
|
||||
|
||||
CHECK_FALSE(parseGenericSeed(toBase58(TokenType::NodePublic, node2.first)));
|
||||
CHECK_FALSE(
|
||||
parseGenericSeed(toBase58(TokenType::NodePrivate, node2.second)));
|
||||
|
||||
auto const account1 = generateKeyPair(KeyType::secp256k1, randomSeed());
|
||||
|
||||
CHECK_FALSE(parseGenericSeed(toBase58(calcAccountID(account1.first))));
|
||||
CHECK_FALSE(
|
||||
parseGenericSeed(toBase58(TokenType::AccountPublic, account1.first)));
|
||||
CHECK_FALSE(
|
||||
parseGenericSeed(toBase58(TokenType::AccountSecret, account1.second)));
|
||||
|
||||
auto const account2 = generateKeyPair(KeyType::ed25519, randomSeed());
|
||||
|
||||
CHECK_FALSE(parseGenericSeed(toBase58(calcAccountID(account2.first))));
|
||||
CHECK_FALSE(
|
||||
parseGenericSeed(toBase58(TokenType::AccountPublic, account2.first)));
|
||||
CHECK_FALSE(
|
||||
parseGenericSeed(toBase58(TokenType::AccountSecret, account2.second)));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
155
src/doctest/protocol/SeqProxy.cpp
Normal file
155
src/doctest/protocol/SeqProxy.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("SeqProxy");
|
||||
|
||||
namespace {
|
||||
|
||||
// Exercise value(), isSeq(), and isTicket().
|
||||
constexpr bool
|
||||
expectValues(SeqProxy seqProx, std::uint32_t value, SeqProxy::Type type)
|
||||
{
|
||||
bool const expectSeq{type == SeqProxy::seq};
|
||||
return (seqProx.value() == value) && (seqProx.isSeq() == expectSeq) &&
|
||||
(seqProx.isTicket() == !expectSeq);
|
||||
}
|
||||
|
||||
// Exercise all SeqProxy comparison operators expecting lhs < rhs.
|
||||
constexpr bool
|
||||
expectLt(SeqProxy lhs, SeqProxy rhs)
|
||||
{
|
||||
return (lhs < rhs) && (lhs <= rhs) && (!(lhs == rhs)) && (lhs != rhs) &&
|
||||
(!(lhs >= rhs)) && (!(lhs > rhs));
|
||||
}
|
||||
|
||||
// Exercise all SeqProxy comparison operators expecting lhs == rhs.
|
||||
constexpr bool
|
||||
expectEq(SeqProxy lhs, SeqProxy rhs)
|
||||
{
|
||||
return (!(lhs < rhs)) && (lhs <= rhs) && (lhs == rhs) && (!(lhs != rhs)) &&
|
||||
(lhs >= rhs) && (!(lhs > rhs));
|
||||
}
|
||||
|
||||
// Exercise all SeqProxy comparison operators expecting lhs > rhs.
|
||||
constexpr bool
|
||||
expectGt(SeqProxy lhs, SeqProxy rhs)
|
||||
{
|
||||
return (!(lhs < rhs)) && (!(lhs <= rhs)) && (!(lhs == rhs)) &&
|
||||
(lhs != rhs) && (lhs >= rhs) && (lhs > rhs);
|
||||
}
|
||||
|
||||
// Verify streaming.
|
||||
bool
|
||||
streamTest(SeqProxy seqProx)
|
||||
{
|
||||
std::string const type{seqProx.isSeq() ? "sequence" : "ticket"};
|
||||
std::string const value{std::to_string(seqProx.value())};
|
||||
|
||||
std::stringstream ss;
|
||||
ss << seqProx;
|
||||
std::string str{ss.str()};
|
||||
|
||||
return str.find(type) == 0 && str[type.size()] == ' ' &&
|
||||
str.find(value) == (type.size() + 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("SeqProxy operations")
|
||||
{
|
||||
// While SeqProxy supports values of zero, they are not
|
||||
// expected in the wild. Nevertheless they are tested here.
|
||||
// But so are values of 1, which are expected to occur in the wild.
|
||||
static constexpr std::uint32_t uintMax{
|
||||
std::numeric_limits<std::uint32_t>::max()};
|
||||
static constexpr SeqProxy::Type seq{SeqProxy::seq};
|
||||
static constexpr SeqProxy::Type ticket{SeqProxy::ticket};
|
||||
|
||||
static constexpr SeqProxy seqZero{seq, 0};
|
||||
static constexpr SeqProxy seqSmall{seq, 1};
|
||||
static constexpr SeqProxy seqMid0{seq, 2};
|
||||
static constexpr SeqProxy seqMid1{seqMid0};
|
||||
static constexpr SeqProxy seqBig{seq, uintMax};
|
||||
|
||||
static constexpr SeqProxy ticZero{ticket, 0};
|
||||
static constexpr SeqProxy ticSmall{ticket, 1};
|
||||
static constexpr SeqProxy ticMid0{ticket, 2};
|
||||
static constexpr SeqProxy ticMid1{ticMid0};
|
||||
static constexpr SeqProxy ticBig{ticket, uintMax};
|
||||
|
||||
SUBCASE("value(), isSeq() and isTicket()")
|
||||
{
|
||||
static_assert(expectValues(seqZero, 0, seq), "");
|
||||
static_assert(expectValues(seqSmall, 1, seq), "");
|
||||
static_assert(expectValues(seqMid0, 2, seq), "");
|
||||
static_assert(expectValues(seqMid1, 2, seq), "");
|
||||
static_assert(expectValues(seqBig, uintMax, seq), "");
|
||||
|
||||
static_assert(expectValues(ticZero, 0, ticket), "");
|
||||
static_assert(expectValues(ticSmall, 1, ticket), "");
|
||||
static_assert(expectValues(ticMid0, 2, ticket), "");
|
||||
static_assert(expectValues(ticMid1, 2, ticket), "");
|
||||
static_assert(expectValues(ticBig, uintMax, ticket), "");
|
||||
}
|
||||
|
||||
SUBCASE("comparison operators - seqZero")
|
||||
{
|
||||
static_assert(expectEq(seqZero, seqZero), "");
|
||||
static_assert(expectLt(seqZero, seqSmall), "");
|
||||
static_assert(expectLt(seqZero, seqMid0), "");
|
||||
static_assert(expectLt(seqZero, seqMid1), "");
|
||||
static_assert(expectLt(seqZero, seqBig), "");
|
||||
static_assert(expectLt(seqZero, ticZero), "");
|
||||
static_assert(expectLt(seqZero, ticSmall), "");
|
||||
static_assert(expectLt(seqZero, ticMid0), "");
|
||||
static_assert(expectLt(seqZero, ticMid1), "");
|
||||
static_assert(expectLt(seqZero, ticBig), "");
|
||||
}
|
||||
|
||||
SUBCASE("comparison operators - seqSmall")
|
||||
{
|
||||
static_assert(expectGt(seqSmall, seqZero), "");
|
||||
static_assert(expectEq(seqSmall, seqSmall), "");
|
||||
static_assert(expectLt(seqSmall, seqMid0), "");
|
||||
static_assert(expectLt(seqSmall, seqBig), "");
|
||||
static_assert(expectLt(seqSmall, ticZero), "");
|
||||
}
|
||||
|
||||
SUBCASE("comparison operators - seqBig")
|
||||
{
|
||||
static_assert(expectGt(seqBig, seqZero), "");
|
||||
static_assert(expectGt(seqBig, seqSmall), "");
|
||||
static_assert(expectEq(seqBig, seqBig), "");
|
||||
static_assert(expectLt(seqBig, ticZero), "");
|
||||
}
|
||||
|
||||
SUBCASE("comparison operators - ticBig")
|
||||
{
|
||||
static_assert(expectGt(ticBig, seqZero), "");
|
||||
static_assert(expectGt(ticBig, seqBig), "");
|
||||
static_assert(expectGt(ticBig, ticZero), "");
|
||||
static_assert(expectEq(ticBig, ticBig), "");
|
||||
}
|
||||
|
||||
SUBCASE("streaming")
|
||||
{
|
||||
CHECK(streamTest(seqZero));
|
||||
CHECK(streamTest(seqSmall));
|
||||
CHECK(streamTest(seqMid0));
|
||||
CHECK(streamTest(seqMid1));
|
||||
CHECK(streamTest(seqBig));
|
||||
CHECK(streamTest(ticZero));
|
||||
CHECK(streamTest(ticSmall));
|
||||
CHECK(streamTest(ticMid0));
|
||||
CHECK(streamTest(ticMid1));
|
||||
CHECK(streamTest(ticBig));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
47
src/doctest/protocol/Serializer.cpp
Normal file
47
src/doctest/protocol/Serializer.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("Serializer");
|
||||
|
||||
TEST_CASE("Serializer add32/geti32")
|
||||
{
|
||||
std::initializer_list<std::int32_t> const values = {
|
||||
std::numeric_limits<std::int32_t>::min(),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
std::numeric_limits<std::int32_t>::max()};
|
||||
for (std::int32_t value : values)
|
||||
{
|
||||
Serializer s;
|
||||
s.add32(value);
|
||||
CHECK(s.size() == 4);
|
||||
SerialIter sit(s.slice());
|
||||
CHECK(sit.geti32() == value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Serializer add64/geti64")
|
||||
{
|
||||
std::initializer_list<std::int64_t> const values = {
|
||||
std::numeric_limits<std::int64_t>::min(),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
std::numeric_limits<std::int64_t>::max()};
|
||||
for (std::int64_t value : values)
|
||||
{
|
||||
Serializer s;
|
||||
s.add64(value);
|
||||
CHECK(s.size() == 8);
|
||||
SerialIter sit(s.slice());
|
||||
CHECK(sit.geti64() == value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
106
src/doctest/protocol/TER.cpp
Normal file
106
src/doctest/protocol/TER.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST_SUITE_BEGIN("TER");
|
||||
|
||||
TEST_CASE("transResultInfo")
|
||||
{
|
||||
for (auto i = -400; i < 400; ++i)
|
||||
{
|
||||
TER t = TER::fromInt(i);
|
||||
auto inRange = isTelLocal(t) || isTemMalformed(t) || isTefFailure(t) ||
|
||||
isTerRetry(t) || isTesSuccess(t) || isTecClaim(t);
|
||||
|
||||
std::string token, text;
|
||||
auto good = transResultInfo(t, token, text);
|
||||
CHECK((inRange || !good));
|
||||
CHECK(transToken(t) == (good ? token : "-"));
|
||||
CHECK(transHuman(t) == (good ? text : "-"));
|
||||
|
||||
auto code = transCode(token);
|
||||
CHECK(good == !!code);
|
||||
CHECK((!code || *code == t));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("conversion")
|
||||
{
|
||||
// Lambda that verifies assignability and convertibility.
|
||||
auto isConvertable = [](auto from, auto to) {
|
||||
using From_t = std::decay_t<decltype(from)>;
|
||||
using To_t = std::decay_t<decltype(to)>;
|
||||
static_assert(std::is_convertible<From_t, To_t>::value, "Convert err");
|
||||
static_assert(
|
||||
std::is_constructible<To_t, From_t>::value, "Construct err");
|
||||
static_assert(
|
||||
std::is_assignable<To_t&, From_t const&>::value, "Assign err");
|
||||
};
|
||||
|
||||
// Verify the right types convert to NotTEC.
|
||||
NotTEC const notTec;
|
||||
isConvertable(telLOCAL_ERROR, notTec);
|
||||
isConvertable(temMALFORMED, notTec);
|
||||
isConvertable(tefFAILURE, notTec);
|
||||
isConvertable(terRETRY, notTec);
|
||||
isConvertable(tesSUCCESS, notTec);
|
||||
isConvertable(notTec, notTec);
|
||||
|
||||
// Lambda that verifies types and not assignable or convertible.
|
||||
auto notConvertible = [](auto from, auto to) {
|
||||
using To_t = std::decay_t<decltype(to)>;
|
||||
using From_t = std::decay_t<decltype(from)>;
|
||||
static_assert(!std::is_convertible<From_t, To_t>::value, "Convert err");
|
||||
static_assert(
|
||||
!std::is_constructible<To_t, From_t>::value, "Construct err");
|
||||
static_assert(
|
||||
!std::is_assignable<To_t&, From_t const&>::value, "Assign err");
|
||||
};
|
||||
|
||||
// Verify types that shouldn't convert to NotTEC.
|
||||
TER const ter;
|
||||
notConvertible(tecCLAIM, notTec);
|
||||
notConvertible(ter, notTec);
|
||||
notConvertible(4, notTec);
|
||||
|
||||
// Verify the right types convert to TER.
|
||||
isConvertable(telLOCAL_ERROR, ter);
|
||||
isConvertable(temMALFORMED, ter);
|
||||
isConvertable(tefFAILURE, ter);
|
||||
isConvertable(terRETRY, ter);
|
||||
isConvertable(tesSUCCESS, ter);
|
||||
isConvertable(tecCLAIM, ter);
|
||||
isConvertable(notTec, ter);
|
||||
isConvertable(ter, ter);
|
||||
|
||||
// Verify that you can't convert from int to ter.
|
||||
notConvertible(4, ter);
|
||||
}
|
||||
|
||||
TEST_CASE("comparison")
|
||||
{
|
||||
// Test comparison operators on TER types
|
||||
auto checkComparable = [](auto lhs, auto rhs) {
|
||||
CHECK((lhs == rhs) == (TERtoInt(lhs) == TERtoInt(rhs)));
|
||||
CHECK((lhs != rhs) == (TERtoInt(lhs) != TERtoInt(rhs)));
|
||||
CHECK((lhs < rhs) == (TERtoInt(lhs) < TERtoInt(rhs)));
|
||||
CHECK((lhs <= rhs) == (TERtoInt(lhs) <= TERtoInt(rhs)));
|
||||
CHECK((lhs > rhs) == (TERtoInt(lhs) > TERtoInt(rhs)));
|
||||
CHECK((lhs >= rhs) == (TERtoInt(lhs) >= TERtoInt(rhs)));
|
||||
};
|
||||
|
||||
// Test various TER type comparisons
|
||||
checkComparable(telLOCAL_ERROR, temMALFORMED);
|
||||
checkComparable(tefFAILURE, terRETRY);
|
||||
checkComparable(tesSUCCESS, tecCLAIM);
|
||||
checkComparable(NotTEC{telLOCAL_ERROR}, TER{tecCLAIM});
|
||||
checkComparable(tesSUCCESS, tesSUCCESS);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
2
src/doctest/protocol/main.cpp
Normal file
2
src/doctest/protocol/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
Reference in New Issue
Block a user