Compare commits

..

3 Commits

Author SHA1 Message Date
Pratik Mankawde
61be075ff8 added few more tests and doc 2025-12-12 12:18:19 +00:00
Pratik Mankawde
db73390eff test commit 2025-12-11 19:04:11 +00:00
Pratik Mankawde
adc6ca6d11 first set of changes 2025-12-11 18:38:52 +00:00
313 changed files with 11987 additions and 2508 deletions

View File

@@ -1,272 +0,0 @@
ignorePaths:
- build/**
- src/libxrpl/crypto
- src/test/** # Will be removed in the future
- CMakeUserPresets.json
- Doxyfile
- docs/**/*.puml
- cmake/**
- LICENSE.md
language: en
allowCompoundWords: true
ignoreRandomStrings: true
minWordLength: 5
dictionaries:
- cpp
- en_US
- en_GB
ignoreRegExpList:
- /[rs][1-9A-HJ-NP-Za-km-z]{25,34}/g # addresses and seeds
- /(XRPL|BEAST)_[A-Z_0-9]+_H_INCLUDED+/g # include guards
- /(XRPL|BEAST)_[A-Z_0-9]+_H+/g # include guards
- /::[a-z:_]+/g # things from other namespaces
- /lib[a-z]+/g # libraries
- /[0-9]{4}-[0-9]{2}-[0-9]{2}[,:][A-Za-zÀ-ÖØ-öø-ÿ.\s]+/g # copyright dates
- /[0-9]{4}[,:]?\s*[A-Za-zÀ-ÖØ-öø-ÿ.\s]+/g # copyright years
- /\[[A-Za-z0-9-]+\]\(https:\/\/github.com\/[A-Za-z0-9-]+\)/g # Github usernames
- /-[DWw][a-zA-Z0-9_-]+=/g # compile flags
- /[\['"`]-[DWw][a-zA-Z0-9_-]+['"`\]]/g # compile flags
suggestWords:
- xprl->xrpl
- unsynched->unsynced
- synched->synced
- synch->sync
words:
- abempty
- AMMID
- amt
- amts
- asnode
- asynchrony
- attestation
- authorises
- autobridge
- autobridged
- autobridging
- bimap
- bindir
- bookdir
- Bougalis
- Britto
- Btrfs
- canonicality
- checkme
- chrono
- citardauq
- clawback
- clawbacks
- coeffs
- coldwallet
- compr
- conanfile
- conanrun
- connectability
- coro
- coros
- cowid
- cryptocondition
- cryptoconditional
- cryptoconditions
- csprng
- ctid
- currenttxhash
- daria
- dcmake
- dearmor
- deleteme
- demultiplexer
- deserializaton
- desync
- desynced
- determ
- distro
- doxyfile
- dxrpl
- endmacro
- endpointv
- exceptioned
- Falco
- finalizers
- firewalled
- fmtdur
- funclets
- gcov
- gcovr
- Gnutella
- gpgcheck
- gpgkey
- hotwallet
- ifndef
- inequation
- insuf
- insuff
- iou
- ious
- isrdc
- jemalloc
- jlog
- keylet
- keylets
- keyvadb
- ledgerentry
- ledgerhash
- ledgerindex
- leftw
- legleux
- levelization
- levelized
- libpb
- libxrpl
- llection
- LOCALGOOD
- logwstream
- lseq
- lsmf
- ltype
- MEMORYSTATUSEX
- Merkle
- Metafuncton
- misprediction
- mptbalance
- mptflags
- mptid
- mptissuance
- mptissuanceid
- mptoken
- mptokenid
- mptokenissuance
- mptokens
- mpts
- multisig
- multisign
- multisigned
- Nakamoto
- nftid
- nftoffer
- nftoken
- nftokenid
- nftokenpages
- nftokens
- nftpage
- nikb
- nonxrp
- noripple
- nudb
- nullptr
- nunl
- Nyffenegger
- ostr
- partitioner
- paychan
- paychans
- permdex
- perminute
- permissioned
- pointee
- preauth
- preauthorization
- preauthorize
- preauthorizes
- preclaim
- protobuf
- protos
- ptrs
- pyenv
- qalloc
- queuable
- Raphson
- replayer
- rerere
- retriable
- RIPD
- ripdtop
- rippleci
- rippled
- ripplerpc
- rippletest
- RLUSD
- rngfill
- rocksdb
- Rohrs
- roundings
- sahyadri
- Satoshi
- secp
- sendq
- seqit
- sf
- shamap
- shamapitem
- sidechain
- SIGGOOD
- sle
- sles
- soci
- socidb
- sslws
- statsd
- STATSDCOLLECTOR
- stissue
- stnum
- stobj
- stobject
- stpath
- stpathset
- sttx
- stvar
- stvector
- stxchainattestations
- superpeer
- superpeers
- takergets
- takerpays
- ters
- tx
- txid
- txids
- txjson
- txn
- txns
- txs
- umant
- unacquired
- unambiguity
- unauthorizes
- unauthorizing
- unergonomic
- unfetched
- unflatten
- unfund
- unimpair
- unroutable
- unscalable
- unserviced
- unshareable
- unshares
- unsquelch
- unsquelched
- unsquelching
- unvalidated
- unveto
- unvetoed
- upvotes
- USDB
- variadics
- venv
- vfalco
- vinnie
- wextra
- wptr
- writeme
- wsrch
- wthread
- xbridge
- xchain
- ximinez
- XMACRO
- xrpkuwait
- xrpl
- xrpld
- xrplf
- xxhash
- xxhasher

View File

@@ -4,6 +4,9 @@ description: "Install Conan dependencies, optionally forcing a rebuild of all de
# Note that actions do not support 'type' and all inputs are strings, see
# https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#inputs.
inputs:
build_dir:
description: "The directory where to build."
required: true
build_type:
description: 'The build type to use ("Debug", "Release").'
required: true
@@ -25,13 +28,17 @@ runs:
- name: Install Conan dependencies
shell: bash
env:
BUILD_DIR: ${{ inputs.build_dir }}
BUILD_NPROC: ${{ inputs.build_nproc }}
BUILD_OPTION: ${{ inputs.force_build == 'true' && '*' || 'missing' }}
BUILD_TYPE: ${{ inputs.build_type }}
LOG_VERBOSITY: ${{ inputs.log_verbosity }}
run: |
echo 'Installing dependencies.'
mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"
conan install \
--output-folder . \
--build="${BUILD_OPTION}" \
--options:host='&:tests=True' \
--options:host='&:xrpld=True' \
@@ -39,4 +46,4 @@ runs:
--conf:all tools.build:jobs=${BUILD_NPROC} \
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
.
..

View File

@@ -11,6 +11,12 @@ runs:
echo 'Checking environment variables.'
set
echo 'Checking CMake version.'
cmake --version
echo 'Checking Conan version.'
conan --version
- name: Check configuration (Linux and macOS)
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
shell: bash
@@ -21,23 +27,17 @@ runs:
echo 'Checking environment variables.'
env | sort
echo 'Checking CMake version.'
cmake --version
echo 'Checking compiler version.'
${{ runner.os == 'Linux' && '${CC}' || 'clang' }} --version
echo 'Checking Conan version.'
conan --version
echo 'Checking Ninja version.'
ninja --version
echo 'Checking nproc version.'
nproc --version
- name: Check configuration (all)
shell: bash
run: |
echo 'Checking Ccache version.'
ccache --version
echo 'Checking CMake version.'
cmake --version
echo 'Checking Conan version.'
conan --version

View File

@@ -81,7 +81,7 @@ It generates many files of [results](results):
- `rawincludes.txt`: The raw dump of the `#includes`
- `paths.txt`: A second dump grouping the source module
to the destination module, de-duped, and with frequency counts.
to the destination module, deduped, and with frequency counts.
- `includes/`: A directory where each file represents a module and
contains a list of modules and counts that the module _includes_.
- `includedby/`: Similar to `includes/`, but the other way around. Each

View File

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

View File

@@ -31,9 +31,6 @@ run from the repository root.
the `xrpld` binary.
5. `.github/scripts/rename/namespace.sh`: This script will rename the C++
namespaces from `ripple` to `xrpl`.
6. `.github/scripts/rename/config.sh`: This script will rename the config from
`rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old
filename will still be accepted.
You can run all these scripts from the repository root as follows:
@@ -43,5 +40,4 @@ You can run all these scripts from the repository root as follows:
./.github/scripts/rename/cmake.sh .
./.github/scripts/rename/binary.sh .
./.github/scripts/rename/namespace.sh .
./.github/scripts/rename/config.sh .
```

View File

@@ -1,72 +0,0 @@
#!/bin/bash
# Exit the script as soon as an error occurs.
set -e
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
SED_COMMAND=sed
if [[ "${OSTYPE}" == 'darwin'* ]]; then
if ! command -v gsed &> /dev/null; then
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
exit 1
fi
SED_COMMAND=gsed
fi
# This script renames the config from `rippled.cfg` to `xrpld.cfg`, and updates
# the code accordingly. The old filename will still be accepted.
# Usage: .github/scripts/rename/config.sh <repository directory>
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <repository directory>"
exit 1
fi
DIRECTORY=$1
echo "Processing directory: ${DIRECTORY}"
if [ ! -d "${DIRECTORY}" ]; then
echo "Error: Directory '${DIRECTORY}' does not exist."
exit 1
fi
pushd ${DIRECTORY}
# Add the xrpld.cfg to the .gitignore.
if ! grep -q 'xrpld.cfg' .gitignore; then
${SED_COMMAND} -i '/rippled.cfg/a\
/xrpld.cfg' .gitignore
fi
# Rename the files.
if [ -e rippled.cfg ]; then
mv rippled.cfg xrpld.cfg
fi
if [ -e cfg/rippled-example.cfg ]; then
mv cfg/rippled-example.cfg cfg/xrpld-example.cfg
fi
# Rename inside the files.
DIRECTORIES=("cfg" "cmake" "include" "src")
for DIRECTORY in "${DIRECTORIES[@]}"; do
echo "Processing directory: ${DIRECTORY}"
find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.cmake" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" \) | while read -r FILE; do
echo "Processing file: ${FILE}"
${SED_COMMAND} -i -E 's/rippled(-example)?[ .]cfg/xrpld\1.cfg/g' "${FILE}"
done
done
${SED_COMMAND} -i 's/rippled/xrpld/g' cfg/xrpld-example.cfg
${SED_COMMAND} -i 's/rippled/xrpld/g' src/test/core/Config_test.cpp
${SED_COMMAND} -i 's/ripplevalidators/xrplvalidators/g' src/test/core/Config_test.cpp
${SED_COMMAND} -i 's/rippleConfig/xrpldConfig/g' src/test/core/Config_test.cpp
${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp
${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp
# Restore the old config file name in the code that maintains support for now.
${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp
# Restore an URL.
${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg
popd
echo "Renaming complete."

View File

@@ -232,8 +232,6 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
f"-{architecture['platform'][architecture['platform'].find('/') + 1 :]}"
)
config_name += f"-{build_type.lower()}"
if "-Dcoverage=ON" in cmake_args:
config_name += "-coverage"
if "-Dunity=ON" in cmake_args:
config_name += "-unity"

View File

@@ -15,196 +15,196 @@
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "15",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "16",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "17",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "18",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "19",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "20",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "gcc",
"compiler_version": "15",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "20",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "21",
"image_sha": "cc09fd3"
"image_sha": "0525eae"
},
{
"distro_name": "rhel",
"distro_version": "8",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "8",
"compiler_name": "clang",
"compiler_version": "any",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "clang",
"compiler_version": "any",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "10",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "rhel",
"distro_version": "10",
"compiler_name": "clang",
"compiler_version": "any",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "jammy",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "16",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "17",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "18",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "19",
"image_sha": "cc09fd3"
"image_sha": "e1782cd"
}
],
"build_type": ["Debug", "Release"],

View File

@@ -114,9 +114,6 @@ jobs:
matrix:
os: [linux, macos, windows]
with:
# Enable ccache only for events targeting the XRPLF repository, since
# other accounts will not have access to our remote cache storage.
ccache_enabled: ${{ github.repository_owner == 'XRPLF' }}
os: ${{ matrix.os }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -68,12 +68,6 @@ jobs:
matrix:
os: [linux, macos, windows]
with:
# Enable ccache only for events targeting the XRPLF repository, since
# other accounts will not have access to our remote cache storage.
# However, we do not enable ccache for events targeting the master or a
# release branch, to protect against the rare case that the output
# produced by ccache is not identical to a regular compilation.
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !(github.base_ref == 'master' || startsWith(github.base_ref, 'release')) }}
os: ${{ matrix.os }}
strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }}
secrets:

View File

@@ -22,7 +22,7 @@ defaults:
shell: bash
env:
BUILD_DIR: build
BUILD_DIR: .build
NPROC_SUBTRACT: 2
jobs:
@@ -36,7 +36,7 @@ jobs:
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Get number of processors
uses: XRPLF/actions/get-nproc@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/get-nproc@046b1620f6bfd6cd0985dc82c3df02786801fe0a
id: nproc
with:
subtract: ${{ env.NPROC_SUBTRACT }}

View File

@@ -3,6 +3,11 @@ name: Build and test configuration
on:
workflow_call:
inputs:
build_dir:
description: "The directory where to build."
required: true
type: string
build_only:
description: 'Whether to only build or to build and test the code ("true", "false").'
required: true
@@ -10,14 +15,8 @@ on:
build_type:
description: 'The build type to use ("Debug", "Release").'
required: true
type: string
ccache_enabled:
description: "Whether to enable ccache."
required: false
type: boolean
default: false
required: true
cmake_args:
description: "Additional arguments to pass to CMake."
@@ -27,8 +26,8 @@ on:
cmake_target:
description: "The CMake target to build."
required: true
type: string
required: true
runs_on:
description: Runner to run the job on as a JSON string
@@ -60,11 +59,6 @@ defaults:
run:
shell: bash
env:
# Conan installs the generators in the build/generators directory, see the
# layout() method in conanfile.py. We then run CMake from the build directory.
BUILD_DIR: build
jobs:
build-and-test:
name: ${{ inputs.config_name }}
@@ -72,47 +66,26 @@ jobs:
container: ${{ inputs.image != '' && inputs.image || null }}
timeout-minutes: 60
env:
# Use a namespace to keep the objects separate for each configuration.
CCACHE_NAMESPACE: ${{ inputs.config_name }}
# Ccache supports both Redis and HTTP endpoints.
# * For Redis, use the following format: redis://ip:port, see
# https://github.com/ccache/ccache/wiki/Redis-storage. Note that TLS is
# not directly supported by ccache, and requires use of a proxy.
# * For HTTP use the following format: http://ip:port/cache when using
# nginx as backend or http://ip:port|layout=bazel when using Bazel
# Remote Cache, see https://github.com/ccache/ccache/wiki/HTTP-storage.
# Note that HTTPS is not directly supported by ccache.
CCACHE_REMOTE_ONLY: true
CCACHE_REMOTE_STORAGE: http://cache.dev.ripplex.io:8080|layout=bazel
# Ignore the creation and modification timestamps on files, since the
# header files are copied into separate directories by CMake, which will
# otherwise result in cache misses.
CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime
# Determine if coverage and voidstar should be enabled.
COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
ENABLED_VOIDSTAR: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
ENABLED_COVERAGE: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
steps:
- name: Cleanup workspace (macOS and Windows)
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
uses: XRPLF/actions/cleanup-workspace@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/cleanup-workspace@01b244d2718865d427b499822fbd3f15e7197fcc
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/prepare-runner@99685816bb60a95a66852f212f382580e180df3a
with:
disable_ccache: ${{ !inputs.ccache_enabled }}
- name: Set ccache log file
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}"
disable_ccache: false
- name: Print build environment
uses: ./.github/actions/print-env
- name: Get number of processors
uses: XRPLF/actions/get-nproc@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/get-nproc@046b1620f6bfd6cd0985dc82c3df02786801fe0a
id: nproc
with:
subtract: ${{ inputs.nproc_subtract }}
@@ -123,6 +96,7 @@ jobs:
- name: Build dependencies
uses: ./.github/actions/build-deps
with:
build_dir: ${{ inputs.build_dir }}
build_nproc: ${{ steps.nproc.outputs.nproc }}
build_type: ${{ inputs.build_type }}
# Set the verbosity to "quiet" for Windows to avoid an excessive
@@ -130,7 +104,7 @@ jobs:
log_verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }}
- name: Configure CMake
working-directory: ${{ env.BUILD_DIR }}
working-directory: ${{ inputs.build_dir }}
env:
BUILD_TYPE: ${{ inputs.build_type }}
CMAKE_ARGS: ${{ inputs.cmake_args }}
@@ -143,7 +117,7 @@ jobs:
..
- name: Build the binary
working-directory: ${{ env.BUILD_DIR }}
working-directory: ${{ inputs.build_dir }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
BUILD_TYPE: ${{ inputs.build_type }}
@@ -155,18 +129,11 @@ jobs:
--parallel "${BUILD_NPROC}" \
--target "${CMAKE_TARGET}"
- name: Show ccache statistics
if: ${{ inputs.ccache_enabled }}
run: |
ccache --show-stats -vv
if [ '${{ runner.debug }}' = '1' ]; then
cat "${CCACHE_LOGFILE}"
curl ${CCACHE_REMOTE_STORAGE%|*}/status || true
fi
- name: Upload the binary (Linux)
if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
env:
BUILD_DIR: ${{ inputs.build_dir }}
with:
name: xrpld-${{ inputs.config_name }}
path: ${{ env.BUILD_DIR }}/xrpld
@@ -175,7 +142,7 @@ jobs:
- name: Check linking (Linux)
if: ${{ runner.os == 'Linux' }}
working-directory: ${{ env.BUILD_DIR }}
working-directory: ${{ inputs.build_dir }}
run: |
ldd ./xrpld
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then
@@ -186,14 +153,14 @@ jobs:
fi
- name: Verify presence of instrumentation (Linux)
if: ${{ runner.os == 'Linux' && env.VOIDSTAR_ENABLED == 'true' }}
working-directory: ${{ env.BUILD_DIR }}
if: ${{ runner.os == 'Linux' && env.ENABLED_VOIDSTAR == 'true' }}
working-directory: ${{ inputs.build_dir }}
run: |
./xrpld --version | grep libvoidstar
- name: Run the separate tests
if: ${{ !inputs.build_only }}
working-directory: ${{ env.BUILD_DIR }}
working-directory: ${{ inputs.build_dir }}
# Windows locks some of the build files while running tests, and parallel jobs can collide
env:
BUILD_TYPE: ${{ inputs.build_type }}
@@ -206,7 +173,7 @@ jobs:
- name: Run the embedded tests
if: ${{ !inputs.build_only }}
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', inputs.build_dir, inputs.build_type) || inputs.build_dir }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
run: |
@@ -221,8 +188,8 @@ jobs:
netstat -an
- name: Prepare coverage report
if: ${{ !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
working-directory: ${{ env.BUILD_DIR }}
if: ${{ !inputs.build_only && env.ENABLED_COVERAGE == 'true' }}
working-directory: ${{ inputs.build_dir }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
BUILD_TYPE: ${{ inputs.build_type }}
@@ -234,13 +201,13 @@ jobs:
--target coverage
- name: Upload coverage report
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.ENABLED_COVERAGE == 'true' }}
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
with:
disable_search: true
disable_telem: true
fail_ci_if_error: true
files: ${{ env.BUILD_DIR }}/coverage.xml
files: ${{ inputs.build_dir }}/coverage.xml
plugins: noop
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true

View File

@@ -8,24 +8,21 @@ name: Build and test
on:
workflow_call:
inputs:
ccache_enabled:
description: "Whether to enable ccache."
build_dir:
description: "The directory where to build."
required: false
type: boolean
default: false
type: string
default: ".build"
os:
description: 'The operating system to use for the build ("linux", "macos", "windows").'
required: true
type: string
strategy_matrix:
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
required: false
type: string
default: "minimal"
secrets:
CODECOV_TOKEN:
description: "The Codecov token to use for uploading coverage reports."
@@ -49,9 +46,9 @@ jobs:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
max-parallel: 10
with:
build_dir: ${{ inputs.build_dir }}
build_only: ${{ matrix.build_only }}
build_type: ${{ matrix.build_type }}
ccache_enabled: ${{ inputs.ccache_enabled }}
cmake_args: ${{ matrix.cmake_args }}
cmake_target: ${{ matrix.cmake_target }}
runs_on: ${{ toJSON(matrix.architecture.runner) }}

View File

@@ -29,8 +29,6 @@ jobs:
run: .github/scripts/rename/binary.sh .
- name: Check namespaces
run: .github/scripts/rename/namespace.sh .
- name: Check config name
run: .github/scripts/rename/config.sh .
- name: Check for differences
env:
MESSAGE: |

View File

@@ -64,21 +64,21 @@ jobs:
steps:
- name: Cleanup workspace (macOS and Windows)
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
uses: XRPLF/actions/cleanup-workspace@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/cleanup-workspace@01b244d2718865d427b499822fbd3f15e7197fcc
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/prepare-runner@99685816bb60a95a66852f212f382580e180df3a
with:
disable_ccache: true
disable_ccache: false
- name: Print build environment
uses: ./.github/actions/print-env
- name: Get number of processors
uses: XRPLF/actions/get-nproc@2ece4ec6ab7de266859a6f053571425b2bd684b6
uses: XRPLF/actions/.github/actions/get-nproc@046b1620f6bfd6cd0985dc82c3df02786801fe0a
id: nproc
with:
subtract: ${{ env.NPROC_SUBTRACT }}
@@ -92,6 +92,7 @@ jobs:
- name: Build dependencies
uses: ./.github/actions/build-deps
with:
build_dir: .build
build_nproc: ${{ steps.nproc.outputs.nproc }}
build_type: ${{ matrix.build_type }}
force_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }}

1
.gitignore vendored
View File

@@ -35,7 +35,6 @@ gmon.out
# Customized configs.
/rippled.cfg
/xrpld.cfg
/validators.txt
# Locally patched Conan recipes

View File

@@ -36,21 +36,6 @@ repos:
hooks:
- id: black
# - repo: https://github.com/streetsidesoftware/cspell-cli
# rev: v9.2.0
# hooks:
# - id: cspell # Spell check changed files
# - id: cspell # Spell check the commit message
# name: check commit message spelling
# args:
# - --no-must-find-files
# - --no-progress
# - --no-summary
# - --files
# - .git/COMMIT_EDITMSG
# stages: [commit-msg]
# always_run: true # This might not be necessary.
exclude: |
(?x)^(
external/.*|

View File

@@ -28,9 +28,6 @@ elseif(MSVC)
add_compile_options(/wd4068) # Ignore unknown pragmas
endif()
# Enable ccache to speed up builds.
include(Ccache)
# make GIT_COMMIT_HASH define available to all sources
find_package(Git)
if(Git_FOUND)
@@ -150,4 +147,5 @@ include(XrplValidatorKeys)
if(tests)
include(CTest)
add_subdirectory(src/tests/libxrpl)
add_subdirectory(src/doctest)
endif()

View File

@@ -555,16 +555,16 @@ Rippled uses a linear workflow model that can be summarized as:
git fetch --multiple upstreams user1 user2 user3 [...]
git checkout -B release-next --no-track upstream/develop
# Only do an ff-only merge if pr-branch1 is either already
# Only do an ff-only merge if prbranch1 is either already
# squashed, or needs to be merged with separate commits,
# and has no merge commits.
# Use -S on the ff-only merge if pr-branch1 isn't signed.
git merge [-S] --ff-only user1/pr-branch1
# Use -S on the ff-only merge if prbranch1 isn't signed.
git merge [-S] --ff-only user1/prbranch1
git merge --squash user2/pr-branch2
git merge --squash user2/prbranch2
git commit -S # Use the commit message provided on the PR
git merge --squash user3/pr-branch3
git merge --squash user3/prbranch3
git commit -S # Use the commit message provided on the PR
[...]
@@ -876,7 +876,7 @@ git push --delete upstream-push master-next
#### Special cases: point releases, hotfixes, etc.
On occasion, a bug or issue is discovered in a version that already
On occassion, a bug or issue is discovered in a version that already
had a final release. Most of the time, development will have started
on the next version, and will usually have changes in `develop`
and often in `release`.

View File

@@ -42,7 +42,7 @@ For more information on responsible disclosure, please read this [Wikipedia arti
## Report Handling Process
Please report the bug directly to us and limit further disclosure. If you want to prove that you knew the bug as of a given time, consider using a cryptographic pre-commitment: hash the content of your report and publish the hash on a medium of your choice (e.g. on Twitter or as a memo in a transaction) as "proof" that you had written the text at a given point in time.
Please report the bug directly to us and limit further disclosure. If you want to prove that you knew the bug as of a given time, consider using a cryptographic precommitment: hash the content of your report and publish the hash on a medium of your choice (e.g. on Twitter or as a memo in a transaction) as "proof" that you had written the text at a given point in time.
Once we receive a report, we:

View File

@@ -29,18 +29,18 @@
#
# Purpose
#
# This file documents and provides examples of all xrpld server process
# configuration options. When the xrpld server instance is launched, it
# This file documents and provides examples of all rippled server process
# configuration options. When the rippled server instance is launched, it
# looks for a file with the following name:
#
# xrpld.cfg
# rippled.cfg
#
# For more information on where the xrpld server instance searches for the
# For more information on where the rippled server instance searches for the
# file, visit:
#
# https://xrpl.org/commandline-usage.html#generic-options
#
# This file should be named xrpld.cfg. This file is UTF-8 with DOS, UNIX,
# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX,
# or Mac style end of lines. Blank lines and lines beginning with '#' are
# ignored. Undefined sections are reserved. No escapes are currently defined.
#
@@ -89,8 +89,8 @@
#
#
#
# xrpld offers various server protocols to clients making inbound
# connections. The listening ports xrpld uses are "universal" ports
# rippled offers various server protocols to clients making inbound
# connections. The listening ports rippled uses are "universal" ports
# which may be configured to handshake in one or more of the available
# supported protocols. These universal ports simplify administration:
# A single open port can be used for multiple protocols.
@@ -103,7 +103,7 @@
#
# A list of port names and key/value pairs. A port name must start with a
# letter and contain only letters and numbers. The name is not case-sensitive.
# For each name in this list, xrpld will look for a configuration file
# For each name in this list, rippled will look for a configuration file
# section with the same name and use it to create a listening port. The
# name is informational only; the choice of name does not affect the function
# of the listening port.
@@ -134,7 +134,7 @@
# ip = 127.0.0.1
# protocol = http
#
# When xrpld is used as a command line client (for example, issuing a
# When rippled is used as a command line client (for example, issuing a
# server stop command), the first port advertising the http or https
# protocol will be used to make the connection.
#
@@ -175,7 +175,7 @@
# same time. It is possible have both Websockets and Secure Websockets
# together in one port.
#
# NOTE If no ports support the peer protocol, xrpld cannot
# NOTE If no ports support the peer protocol, rippled cannot
# receive incoming peer connections or become a superpeer.
#
# limit = <number>
@@ -194,7 +194,7 @@
# required. IP address restrictions, if any, will be checked in addition
# to the credentials specified here.
#
# When acting in the client role, xrpld will supply these credentials
# When acting in the client role, rippled will supply these credentials
# using HTTP's Basic Authentication headers when making outbound HTTP/S
# requests.
#
@@ -218,7 +218,7 @@
# administrative commands.
#
# NOTE A common configuration value for the admin field is "localhost".
# If you are listening on all IPv4/IPv6 addresses by specifying
# If you are listening on all IPv4/IPv6 addresses by specifing
# ip = :: then you can use admin = ::ffff:127.0.0.1,::1 to allow
# administrative access from both IPv4 and IPv6 localhost
# connections.
@@ -237,7 +237,7 @@
# WS, or WSS protocol interfaces. If administrative commands are
# disabled for a port, these credentials have no effect.
#
# When acting in the client role, xrpld will supply these credentials
# When acting in the client role, rippled will supply these credentials
# in the submitted JSON for any administrative command requests when
# invoking JSON-RPC commands on remote servers.
#
@@ -258,7 +258,7 @@
# resource controls will default to those for non-administrative users.
#
# The secure_gateway IP addresses are intended to represent
# proxies. Since xrpld trusts these hosts, they must be
# proxies. Since rippled trusts these hosts, they must be
# responsible for properly authenticating the remote user.
#
# If some IP addresses are included for both "admin" and
@@ -272,7 +272,7 @@
# Use the specified files when configuring SSL on the port.
#
# NOTE If no files are specified and secure protocols are selected,
# xrpld will generate an internal self-signed certificate.
# rippled will generate an internal self-signed certificate.
#
# The files have these meanings:
#
@@ -297,12 +297,12 @@
# Control the ciphers which the server will support over SSL on the port,
# specified using the OpenSSL "cipher list format".
#
# NOTE If unspecified, xrpld will automatically configure a modern
# NOTE If unspecified, rippled will automatically configure a modern
# cipher suite. This default suite should be widely supported.
#
# You should not modify this string unless you have a specific
# reason and cryptographic expertise. Incorrect modification may
# keep xrpld from connecting to other instances of xrpld or
# keep rippled from connecting to other instances of rippled or
# prevent RPC and WebSocket clients from connecting.
#
# send_queue_limit = [1..65535]
@@ -382,7 +382,7 @@
#-----------------
#
# These settings control security and access attributes of the Peer to Peer
# server section of the xrpld process. Peer Protocol implements the
# server section of the rippled process. Peer Protocol implements the
# Ripple Payment protocol. It is over peer connections that transactions
# and validations are passed from to machine to machine, to determine the
# contents of validated ledgers.
@@ -396,7 +396,7 @@
# true - enables compression
# false - disables compression [default].
#
# The xrpld server can save bandwidth by compressing its peer-to-peer communications,
# The rippled server can save bandwidth by compressing its peer-to-peer communications,
# at a cost of greater CPU usage. If you enable link compression,
# the server automatically compresses communications with peer servers
# that also have link compression enabled.
@@ -432,7 +432,7 @@
#
# [ips_fixed]
#
# List of IP addresses or hostnames to which xrpld should always attempt to
# List of IP addresses or hostnames to which rippled should always attempt to
# maintain peer connections with. This is useful for manually forming private
# networks, for example to configure a validation server that connects to the
# Ripple network through a public-facing server, or for building a set
@@ -573,7 +573,7 @@
#
# minimum_txn_in_ledger_standalone = <number>
#
# Like minimum_txn_in_ledger when xrpld is running in standalone
# Like minimum_txn_in_ledger when rippled is running in standalone
# mode. Default: 1000.
#
# target_txn_in_ledger = <number>
@@ -710,7 +710,7 @@
#
# [validator_token]
#
# This is an alternative to [validation_seed] that allows xrpld to perform
# This is an alternative to [validation_seed] that allows rippled to perform
# validation without having to store the validator keys on the network
# connected server. The field should contain a single token in the form of a
# base64-encoded blob.
@@ -745,7 +745,7 @@
#
# Specify the file by its name or path.
# Unless an absolute path is specified, it will be considered relative to
# the folder in which the xrpld.cfg file is located.
# the folder in which the rippled.cfg file is located.
#
# Examples:
# /home/ripple/validators.txt
@@ -840,7 +840,7 @@
#
# 0: Disable the ledger replay feature [default]
# 1: Enable the ledger replay feature. With this feature enabled, when
# acquiring a ledger from the network, a xrpld node only downloads
# acquiring a ledger from the network, a rippled node only downloads
# the ledger header and the transactions instead of the whole ledger.
# And the ledger is built by applying the transactions to the parent
# ledger.
@@ -851,7 +851,7 @@
#
#----------------
#
# The xrpld server instance uses HTTPS GET requests in a variety of
# The rippled server instance uses HTTPS GET requests in a variety of
# circumstances, including but not limited to contacting trusted domains to
# fetch information such as mapping an email address to a Ripple Payment
# Network address.
@@ -891,7 +891,7 @@
#
#------------
#
# xrpld creates 4 SQLite database to hold bookkeeping information
# rippled creates 4 SQLite database to hold bookkeeping information
# about transactions, local credentials, and various other things.
# It also creates the NodeDB, which holds all the objects that
# make up the current and historical ledgers.
@@ -902,7 +902,7 @@
# the performance of the server.
#
# Partial pathnames will be considered relative to the location of
# the xrpld.cfg file.
# the rippled.cfg file.
#
# [node_db] Settings for the Node Database (required)
#
@@ -920,11 +920,11 @@
# type = NuDB
#
# NuDB is a high-performance database written by Ripple Labs and optimized
# for xrpld and solid-state drives.
# for rippled and solid-state drives.
#
# NuDB maintains its high speed regardless of the amount of history
# stored. Online delete may be selected, but is not required. NuDB is
# available on all platforms that xrpld runs on.
# available on all platforms that rippled runs on.
#
# type = RocksDB
#
@@ -1049,7 +1049,7 @@
#
# recovery_wait_seconds
# The online delete process checks periodically
# that xrpld is still in sync with the network,
# that rippled is still in sync with the network,
# and that the validated ledger is less than
# 'age_threshold_seconds' old. If not, then continue
# sleeping for this number of seconds and
@@ -1069,8 +1069,8 @@
# The server creates and maintains 4 to 5 bookkeeping SQLite databases in
# the 'database_path' location. If you omit this configuration setting,
# the server creates a directory called "db" located in the same place as
# your xrpld.cfg file.
# Partial pathnames are relative to the location of the xrpld executable.
# your rippled.cfg file.
# Partial pathnames are relative to the location of the rippled executable.
#
# [sqlite] Tuning settings for the SQLite databases (optional)
#
@@ -1120,7 +1120,7 @@
# The default is "wal", which uses a write-ahead
# log to implement database transactions.
# Alternately, "memory" saves disk I/O, but if
# xrpld crashes during a transaction, the
# rippled crashes during a transaction, the
# database is likely to be corrupted.
# See https://www.sqlite.org/pragma.html#pragma_journal_mode
# for more details about the available options.
@@ -1130,7 +1130,7 @@
# synchronous Valid values: off, normal, full, extra
# The default is "normal", which works well with
# the "wal" journal mode. Alternatively, "off"
# allows xrpld to continue as soon as data is
# allows rippled to continue as soon as data is
# passed to the OS, which can significantly
# increase speed, but risks data corruption if
# the host computer crashes before writing that
@@ -1144,7 +1144,7 @@
# The default is "file", which will use files
# for temporary database tables and indices.
# Alternatively, "memory" may save I/O, but
# xrpld does not currently use many, if any,
# rippled does not currently use many, if any,
# of these temporary objects.
# See https://www.sqlite.org/pragma.html#pragma_temp_store
# for more details about the available options.
@@ -1173,7 +1173,7 @@
#
# These settings are designed to help server administrators diagnose
# problems, and obtain detailed information about the activities being
# performed by the xrpld process.
# performed by the rippled process.
#
#
#
@@ -1190,7 +1190,7 @@
#
# Configuration parameters for the Beast. Insight stats collection module.
#
# Insight is a module that collects information from the areas of xrpld
# Insight is a module that collects information from the areas of rippled
# that have instrumentation. The configuration parameters control where the
# collection metrics are sent. The parameters are expressed as key = value
# pairs with no white space. The main parameter is the choice of server:
@@ -1199,7 +1199,7 @@
#
# Choice of server to send metrics to. Currently the only choice is
# "statsd" which sends UDP packets to a StatsD daemon, which must be
# running while xrpld is running. More information on StatsD is
# running while rippled is running. More information on StatsD is
# available here:
# https://github.com/b/statsd_spec
#
@@ -1209,7 +1209,7 @@
# in the format, n.n.n.n:port.
#
# "prefix" A string prepended to each collected metric. This is used
# to distinguish between different running instances of xrpld.
# to distinguish between different running instances of rippled.
#
# If this section is missing, or the server type is unspecified or unknown,
# statistics are not collected or reported.
@@ -1236,7 +1236,7 @@
#
# Example:
# [perf]
# perf_log=/var/log/xrpld/perf.log
# perf_log=/var/log/rippled/perf.log
# log_interval=2
#
#-------------------------------------------------------------------------------
@@ -1246,7 +1246,7 @@
#----------
#
# The vote settings configure settings for the entire Ripple network.
# While a single instance of xrpld cannot unilaterally enforce network-wide
# While a single instance of rippled cannot unilaterally enforce network-wide
# settings, these choices become part of the instance's vote during the
# consensus process for each voting ledger.
#
@@ -1260,7 +1260,7 @@
# The reference transaction is the simplest form of transaction.
# It represents an XRP payment between two parties.
#
# If this parameter is unspecified, xrpld will use an internal
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
@@ -1272,7 +1272,7 @@
# account's XRP balance that is at or below the reserve may only be
# spent on transaction fees, and not transferred out of the account.
#
# If this parameter is unspecified, xrpld will use an internal
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
@@ -1284,7 +1284,7 @@
# each ledger item owned by the account. Ledger items an account may
# own include trust lines, open orders, and tickets.
#
# If this parameter is unspecified, xrpld will use an internal
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
@@ -1326,7 +1326,7 @@
# tool instead.
#
# This flag has no effect on the "sign" and "sign_for" command line options
# that xrpld makes available.
# that rippled makes available.
#
# The default value of this field is "false"
#
@@ -1405,7 +1405,7 @@
#--------------------
#
# Administrators can use these values as a starting point for configuring
# their instance of xrpld, but each value should be checked to make sure
# their instance of rippled, but each value should be checked to make sure
# it meets the business requirements for the organization.
#
# Server
@@ -1415,7 +1415,7 @@
# "peer"
#
# Peer protocol open to everyone. This is required to accept
# incoming xrpld connections. This does not affect automatic
# incoming rippled connections. This does not affect automatic
# or manual outgoing Peer protocol connections.
#
# "rpc"
@@ -1432,7 +1432,7 @@
#
# ETL commands for Clio. We recommend setting secure_gateway
# in this section to a comma-separated list of the addresses
# of your Clio servers, in order to bypass xrpld's rate limiting.
# of your Clio servers, in order to bypass rippled's rate limiting.
#
# This port is commented out but can be enabled by removing
# the '#' from each corresponding line including the entry under [server]
@@ -1449,8 +1449,8 @@
# NOTE
#
# To accept connections on well known ports such as 80 (HTTP) or
# 443 (HTTPS), most operating systems will require xrpld to
# run with administrator privileges, or else xrpld will not start.
# 443 (HTTPS), most operating systems will require rippled to
# run with administrator privileges, or else rippled will not start.
[server]
port_rpc_admin_local
@@ -1496,7 +1496,7 @@ secure_gateway = 127.0.0.1
#-------------------------------------------------------------------------------
# This is primary persistent datastore for xrpld. This includes transaction
# This is primary persistent datastore for rippled. This includes transaction
# metadata, account states, and ledger headers. Helpful information can be
# found at https://xrpl.org/capacity-planning.html#node-db-type
# type=NuDB is recommended for non-validators with fast SSDs. Validators or
@@ -1511,19 +1511,19 @@ secure_gateway = 127.0.0.1
# deletion.
[node_db]
type=NuDB
path=/var/lib/xrpld/db/nudb
path=/var/lib/rippled/db/nudb
nudb_block_size=4096
online_delete=512
advisory_delete=0
[database_path]
/var/lib/xrpld/db
/var/lib/rippled/db
# This needs to be an absolute directory reference, not a relative one.
# Modify this value as required.
[debug_logfile]
/var/log/xrpld/debug.log
/var/log/rippled/debug.log
# To use the XRP test network
# (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html),
@@ -1533,7 +1533,7 @@ advisory_delete=0
# File containing trusted validator keys or validator list publishers.
# Unless an absolute path is specified, it will be considered relative to the
# folder in which the xrpld.cfg file is located.
# folder in which the rippled.cfg file is located.
[validators_file]
validators.txt

View File

@@ -1,7 +1,7 @@
#
# Default validators.txt
#
# This file is located in the same folder as your xrpld.cfg file
# This file is located in the same folder as your rippled.cfg file
# and defines which validators your server trusts not to collude.
#
# This file is UTF-8 with DOS, UNIX, or Mac style line endings.

View File

@@ -1,51 +0,0 @@
find_program(CCACHE_PATH "ccache")
if (NOT CCACHE_PATH)
return()
endif ()
# For Linux and macOS we can use the ccache binary directly.
if (NOT MSVC)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}")
message(STATUS "Found ccache: ${CCACHE_PATH}")
return()
endif ()
# For Windows more effort is required. The code below is a modified version of
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio#usage-with-cmake.
if ("${CCACHE_PATH}" MATCHES "chocolatey")
message(DEBUG "Ccache path: ${CCACHE_PATH}")
# Chocolatey uses a shim executable that we cannot use directly, in which
# case we have to find the executable it points to. If we cannot find the
# target executable then we cannot use ccache.
find_program(BASH_PATH "bash")
if (NOT BASH_PATH)
message(WARNING "Could not find bash.")
return()
endif ()
execute_process(
COMMAND bash -c "export LC_ALL='en_US.UTF-8'; ${CCACHE_PATH} --shimgen-noop | grep -oP 'path to executable: \\K.+' | head -c -1"
OUTPUT_VARIABLE CCACHE_PATH)
if (NOT CCACHE_PATH)
message(WARNING "Could not find ccache target.")
return()
endif ()
file(TO_CMAKE_PATH "${CCACHE_PATH}" CCACHE_PATH)
endif ()
message(STATUS "Found ccache: ${CCACHE_PATH}")
# Tell cmake to use ccache for compiling with Visual Studio.
file(COPY_FILE
${CCACHE_PATH} ${CMAKE_BINARY_DIR}/cl.exe
ONLY_IF_DIFFERENT)
set(CMAKE_VS_GLOBALS
"CLToolExe=cl.exe"
"CLToolPath=${CMAKE_BINARY_DIR}"
"TrackFileAccess=false"
"UseMultiToolTask=true")
# By default Visual Studio generators will use /Zi, which is not compatible with
# ccache, so tell it to use /Z7 instead.
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")

View File

@@ -149,7 +149,7 @@ elseif (use_gold AND is_gcc)
ERROR_QUIET OUTPUT_VARIABLE LD_VERSION)
#[=========================================================[
NOTE: THE gold linker inserts -rpath as DT_RUNPATH by
default instead of DT_RPATH, so you might have slightly
default intead of DT_RPATH, so you might have slightly
unexpected runtime ld behavior if you were expecting
DT_RPATH. Specify --disable-new-dtags to gold if you do
not want the default DT_RUNPATH behavior. This rpath

View File

@@ -206,7 +206,7 @@ if(xrpld)
)
exclude_if_included(xrpld)
# define a macro for tests that might need to
# be excluded or run differently in CI environment
# be exluded or run differently in CI environment
if(is_ci)
target_compile_definitions(xrpld PRIVATE XRPL_RUNNING_IN_CI)
endif ()

View File

@@ -62,7 +62,7 @@ if (is_root_project AND TARGET xrpld)
message (\"-- Skipping : \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\")
endif ()
endmacro()
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg\" etc xrpld.cfg)
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/rippled-example.cfg\" etc rippled.cfg)
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt)
")
install(CODE "

View File

@@ -182,10 +182,12 @@ class Xrpl(ConanFile):
libxrpl.libs = [
"xrpl",
"xrpl.libpb",
"ed25519",
"secp256k1",
]
# TODO: Fix the protobufs to include each other relative to
# `include/`, not `include/xrpl/proto/`.
libxrpl.includedirs = ["include", "include/xrpl/proto"]
# `include/`, not `include/ripple/proto/`.
libxrpl.includedirs = ["include", "include/ripple/proto"]
libxrpl.requires = [
"boost::headers",
"boost::chrono",

View File

@@ -134,7 +134,7 @@ validation messages (_PAV_) received from each validator on the node's UNL. Note
that the node will only count the validation messages that agree with its own
validations.
We define the **PAV** as the Percentage of Agreed Validation
We define the **PAV** as the **P**ercentage of **A**greed **V**alidation
messages received for the last N ledgers, where N = 256 by default.
When the PAV drops below the **_low-water mark_**, the validator is considered

View File

@@ -43,14 +43,14 @@ alt phase == OPEN
alt sqn%256==0
CA -[#green]> RM: <font color=green>getValidations
CA -[#green]> CA: <font color=green>create UNLModify Tx
hnote over CA#lightgreen: use validations of the last 256 ledgers\nto figure out UNLModify Tx candidates.\nIf any, create UNLModify Tx, and add to TxSet.
hnote over CA#lightgreen: use validatations of the last 256 ledgers\nto figure out UNLModify Tx candidates.\nIf any, create UNLModify Tx, and add to TxSet.
end
CA -> GC
GC -> CA: propose
deactivate CA
end
else phase == ESTABLISH
hnote over GC: receive peer positions
hnote over GC: receive peer postions
GC -> GC : update our position
GC -> CA : propose \n(if position changed)
GC -> GC : check if have consensus

View File

@@ -189,7 +189,7 @@ validations. It checks this on every call to `timerEntry`.
- _Wrong Ledger_ indicates the node is not working on the correct prior ledger
and does not have it available. It requests that ledger from the network, but
continues to work towards consensus this round while waiting. If it had been
_proposing_, it will send a special "bow-out" proposal to its peers to indicate
_proposing_, it will send a special "bowout" proposal to its peers to indicate
its change in mode for the rest of this round. For the duration of the round,
it defers to peer positions for determining the consensus outcome as if it
were just _observing_.
@@ -515,7 +515,7 @@ are excerpts of the generic consensus implementation and of helper types that wi
interact with the concrete implementing class.
```{.cpp}
// Represents a transaction under dispute this round
// Represents a transction under dispute this round
template <class Tx_t, class NodeID_t> class DisputedTx;
// Represents how the node participates in Consensus this round

View File

@@ -58,7 +58,7 @@ concept CAdoptTag = std::is_same_v<T, SharedIntrusiveAdoptIncrementStrongTag> ||
When the strong pointer count goes to zero, the "partialDestructor" is
called. This can be used to destroy as much of the object as possible while
still retaining the reference counts. For example, for SHAMapInnerNodes the
children may be reset in that function. Note that std::shared_pointer WILL
children may be reset in that function. Note that std::shared_poiner WILL
run the destructor when the strong count reaches zero, but may not free the
memory used by the object until the weak count reaches zero. In rippled, we
typically allocate shared pointers with the `make_shared` function. When

View File

@@ -301,7 +301,7 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const
// change the counts and flags (the count could be atomically changed, but
// the flags depend on the current value of the counts).
//
// Note: If this becomes a perf bottleneck, the `partialDestroyStartedMask`
// Note: If this becomes a perf bottleneck, the `partialDestoryStartedMask`
// may be able to be set non-atomically. But it is easier to reason about
// the code if the flag is set atomically.
while (1)

View File

@@ -221,8 +221,7 @@ public:
private:
enum {
// Maximum line length for log messages.
// If the message exceeds this length it will be truncated with
// ellipses.
// If the message exceeds this length it will be truncated with elipses.
maximumMessageCharacters = 12 * 1024
};

View File

@@ -1,70 +0,0 @@
#ifndef XRPL_BASICS_MALLOCTRIM_H_INCLUDED
#define XRPL_BASICS_MALLOCTRIM_H_INCLUDED
#include <xrpl/beast/utility/Journal.h>
#include <optional>
#include <string>
namespace ripple {
// -----------------------------------------------------------------------------
// Allocator interaction note:
// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that
// ptmalloc return free heap pages to the OS.
// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or
// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect
// on the *active* heap. The call is harmless but may not reclaim memory
// because those allocators manage their own arenas.
// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed
// allocations are usually returned to the OS on free regardless of trimming.
// - Call at known reclamation points (e.g., after cache sweeps / online delete)
// and consider rate limiting to avoid churn.
// -----------------------------------------------------------------------------
struct MallocTrimReport
{
bool supported{false};
int trimResult{-1};
long rssBeforeKB{-1};
long rssAfterKB{-1};
[[nodiscard]] long
deltaKB() const noexcept
{
if (rssBeforeKB < 0 || rssAfterKB < 0)
return 0;
return rssAfterKB - rssBeforeKB;
}
};
/**
* @brief Attempt to return freed memory to the operating system.
*
* On Linux with glibc malloc, this issues ::malloc_trim(0), which may release
* free space from ptmalloc arenas back to the kernel. On other platforms, or if
* a different allocator is in use, this function is a no-op and the report will
* indicate that trimming is unsupported or had no effect.
*
* @param tag Optional identifier for logging/debugging purposes.
* @param journal Journal for diagnostic logging.
* @return Report containing before/after metrics and the trim result.
*
* @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded,
* calling glibc's malloc_trim may have no effect on the active heap. The
* call is harmless but typically does not reclaim memory under those
* allocators.
*
* @note Only memory served from glibc's sbrk/arena heaps is eligible for trim.
* Large allocations satisfied via mmap are usually returned on free
* independently of trimming.
*
* @note Intended for use after operations that free significant memory (e.g.,
* cache sweeps, ledger cleanup, online delete). Consider rate limiting.
*/
MallocTrimReport
mallocTrim(std::optional<std::string> const& tag, beast::Journal journal);
} // namespace ripple
#endif

View File

@@ -152,8 +152,8 @@ public:
/** Return a "sub slice" of given length starting at the given position
Note that the subslice encompasses the range [pos, pos + rCount),
where rCount is the smaller of count and size() - pos.
Note that the subslice encompasses the range [pos, pos + rcount),
where rcount is the smaller of count and size() - pos.
@param pos position of the first character
@count requested length

View File

@@ -31,7 +31,7 @@ template <class Iterator>
std::optional<Blob>
strUnHex(std::size_t strSize, Iterator begin, Iterator end)
{
static constexpr std::array<int, 256> const digitLookupTable = []() {
static constexpr std::array<int, 256> const unxtab = []() {
std::array<int, 256> t{};
for (auto& x : t)
@@ -57,7 +57,7 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
if (strSize & 1)
{
int c = digitLookupTable[*iter++];
int c = unxtab[*iter++];
if (c < 0)
return {};
@@ -67,12 +67,12 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
while (iter != end)
{
int cHigh = digitLookupTable[*iter++];
int cHigh = unxtab[*iter++];
if (cHigh < 0)
return {};
int cLow = digitLookupTable[*iter++];
int cLow = unxtab[*iter++];
if (cLow < 0)
return {};

View File

@@ -3189,12 +3189,11 @@ operator==(aged_unordered_container<
{
if (size() != other.size())
return false;
for (auto iter(cbegin()), last(cend()), otherLast(other.cend());
iter != last;
for (auto iter(cbegin()), last(cend()), olast(other.cend()); iter != last;
++iter)
{
auto otherIter(other.find(extract(*iter)));
if (otherIter == otherLast)
auto oiter(other.find(extract(*iter)));
if (oiter == olast)
return false;
}
return true;

View File

@@ -18,7 +18,7 @@ namespace beast {
namespace detail {
// These specializations get called by the non-member functions to do the work
// These specializatons get called by the non-member functions to do the work
template <class Out, class In>
struct LexicalCast;

View File

@@ -203,7 +203,7 @@ struct is_contiguously_hashable<T[N], HashAlgorithm>
Throws:
Never
Effect:
Returns the resulting hash of all the input data.
Returns the reslting hash of all the input data.
*/
/** @{ */

View File

@@ -376,7 +376,7 @@ public:
print statement examples
"parent.child" prints child and all of its children
"parent.child." start at the parent and print down to child
"parent.grandchild" prints nothing- grandchild not direct descendent
"parent.grandchild" prints nothing- grandchild not direct discendent
"parent.grandchild." starts at the parent and prints down to grandchild
"parent.grandchild.*" starts at parent, print through grandchild
children

View File

@@ -40,7 +40,7 @@ public:
using microseconds = std::chrono::microseconds;
/**
* Configuration from [perf] section of xrpld.cfg.
* Configuration from [perf] section of rippled.cfg.
*/
struct Setup
{

445
include/xrpl/json/Object.h Normal file
View File

@@ -0,0 +1,445 @@
#ifndef XRPL_JSON_OBJECT_H_INCLUDED
#define XRPL_JSON_OBJECT_H_INCLUDED
#include <xrpl/json/Writer.h>
#include <memory>
namespace Json {
/**
Collection is a base class for Array and Object, classes which provide the
facade of JSON collections for the O(1) JSON writer, while still using no
heap memory and only a very small amount of stack.
From http://json.org, JSON has two types of collection: array, and object.
Everything else is a *scalar* - a number, a string, a boolean, the special
value null, or a legacy Json::Value.
Collections must write JSON "as-it-goes" in order to get the strong
performance guarantees. This puts restrictions upon API users:
1. Only one collection can be open for change at any one time.
This condition is enforced automatically and a std::logic_error thrown if
it is violated.
2. A tag may only be used once in an Object.
Some objects have many tags, so this condition might be a little
expensive. Enforcement of this condition is turned on in debug builds and
a std::logic_error is thrown when the tag is added for a second time.
Code samples:
Writer writer;
// An empty object.
{
Object::Root (writer);
}
// Outputs {}
// An object with one scalar value.
{
Object::Root root (writer);
write["hello"] = "world";
}
// Outputs {"hello":"world"}
// Same, using chaining.
{
Object::Root (writer)["hello"] = "world";
}
// Output is the same.
// Add several scalars, with chaining.
{
Object::Root (writer)
.set ("hello", "world")
.set ("flag", false)
.set ("x", 42);
}
// Outputs {"hello":"world","flag":false,"x":42}
// Add an array.
{
Object::Root root (writer);
{
auto array = root.setArray ("hands");
array.append ("left");
array.append ("right");
}
}
// Outputs {"hands":["left", "right"]}
// Same, using chaining.
{
Object::Root (writer)
.setArray ("hands")
.append ("left")
.append ("right");
}
// Output is the same.
// Add an object.
{
Object::Root root (writer);
{
auto object = root.setObject ("hands");
object["left"] = false;
object["right"] = true;
}
}
// Outputs {"hands":{"left":false,"right":true}}
// Same, using chaining.
{
Object::Root (writer)
.setObject ("hands")
.set ("left", false)
.set ("right", true);
}
}
// Outputs {"hands":{"left":false,"right":true}}
Typical ways to make mistakes and get a std::logic_error:
Writer writer;
Object::Root root (writer);
// Repeat a tag.
{
root ["hello"] = "world";
root ["hello"] = "there"; // THROWS! in a debug build.
}
// Open a subcollection, then set something else.
{
auto object = root.setObject ("foo");
root ["hello"] = "world"; // THROWS!
}
// Open two subcollections at a time.
{
auto object = root.setObject ("foo");
auto array = root.setArray ("bar"); // THROWS!!
}
For more examples, check the unit tests.
*/
class Collection
{
public:
Collection(Collection&& c) noexcept;
Collection&
operator=(Collection&& c) noexcept;
Collection() = delete;
~Collection();
protected:
// A null parent means "no parent at all".
// Writers cannot be null.
Collection(Collection* parent, Writer*);
void
checkWritable(std::string const& label);
Collection* parent_;
Writer* writer_;
bool enabled_;
};
class Array;
//------------------------------------------------------------------------------
/** Represents a JSON object being written to a Writer. */
class Object : protected Collection
{
public:
/** Object::Root is the only Collection that has a public constructor. */
class Root;
/** Set a scalar value in the Object for a key.
A JSON scalar is a single value - a number, string, boolean, nullptr or
a Json::Value.
`set()` throws an exception if this object is disabled (which means that
one of its children is enabled).
In a debug build, `set()` also throws an exception if the key has
already been set() before.
An operator[] is provided to allow writing `object["key"] = scalar;`.
*/
template <typename Scalar>
void
set(std::string const& key, Scalar const&);
void
set(std::string const& key, Json::Value const&);
// Detail class and method used to implement operator[].
class Proxy;
Proxy
operator[](std::string const& key);
Proxy
operator[](Json::StaticString const& key);
/** Make a new Object at a key and return it.
This Object is disabled until that sub-object is destroyed.
Throws an exception if this Object was already disabled.
*/
Object
setObject(std::string const& key);
/** Make a new Array at a key and return it.
This Object is disabled until that sub-array is destroyed.
Throws an exception if this Object was already disabled.
*/
Array
setArray(std::string const& key);
protected:
friend class Array;
Object(Collection* parent, Writer* w) : Collection(parent, w)
{
}
};
class Object::Root : public Object
{
public:
/** Each Object::Root must be constructed with its own unique Writer. */
Root(Writer&);
};
//------------------------------------------------------------------------------
/** Represents a JSON array being written to a Writer. */
class Array : private Collection
{
public:
/** Append a scalar to the Arrary.
Throws an exception if this array is disabled (which means that one of
its sub-collections is enabled).
*/
template <typename Scalar>
void
append(Scalar const&);
/**
Appends a Json::Value to an array.
Throws an exception if this Array was disabled.
*/
void
append(Json::Value const&);
/** Append a new Object and return it.
This Array is disabled until that sub-object is destroyed.
Throws an exception if this Array was disabled.
*/
Object
appendObject();
/** Append a new Array and return it.
This Array is disabled until that sub-array is destroyed.
Throws an exception if this Array was already disabled.
*/
Array
appendArray();
protected:
friend class Object;
Array(Collection* parent, Writer* w) : Collection(parent, w)
{
}
};
//------------------------------------------------------------------------------
// Generic accessor functions to allow Json::Value and Collection to
// interoperate.
/** Add a new subarray at a named key in a Json object. */
Json::Value&
setArray(Json::Value&, Json::StaticString const& key);
/** Add a new subarray at a named key in a Json object. */
Array
setArray(Object&, Json::StaticString const& key);
/** Add a new subobject at a named key in a Json object. */
Json::Value&
addObject(Json::Value&, Json::StaticString const& key);
/** Add a new subobject at a named key in a Json object. */
Object
addObject(Object&, Json::StaticString const& key);
/** Append a new subarray to a Json array. */
Json::Value&
appendArray(Json::Value&);
/** Append a new subarray to a Json array. */
Array
appendArray(Array&);
/** Append a new subobject to a Json object. */
Json::Value&
appendObject(Json::Value&);
/** Append a new subobject to a Json object. */
Object
appendObject(Array&);
/** Copy all the keys and values from one object into another. */
void
copyFrom(Json::Value& to, Json::Value const& from);
/** Copy all the keys and values from one object into another. */
void
copyFrom(Object& to, Json::Value const& from);
/** An Object that contains its own Writer. */
class WriterObject
{
public:
WriterObject(Output const& output)
: writer_(std::make_unique<Writer>(output))
, object_(std::make_unique<Object::Root>(*writer_))
{
}
WriterObject(WriterObject&& other) = default;
Object*
operator->()
{
return object_.get();
}
Object&
operator*()
{
return *object_;
}
private:
std::unique_ptr<Writer> writer_;
std::unique_ptr<Object::Root> object_;
};
WriterObject
stringWriterObject(std::string&);
//------------------------------------------------------------------------------
// Implementation details.
// Detail class for Object::operator[].
class Object::Proxy
{
private:
Object& object_;
std::string const key_;
public:
Proxy(Object& object, std::string const& key);
template <class T>
void
operator=(T const& t)
{
object_.set(key_, t);
// Note: This function shouldn't return *this, because it's a trap.
//
// In Json::Value, foo[jss::key] returns a reference to a
// mutable Json::Value contained _inside_ foo. But in the case of
// Json::Object, where we write once only, there isn't any such
// reference that can be returned. Returning *this would return an
// object "a level higher" than in Json::Value, leading to obscure bugs,
// particularly in generic code.
}
};
//------------------------------------------------------------------------------
template <typename Scalar>
void
Array::append(Scalar const& value)
{
checkWritable("append");
if (writer_)
writer_->append(value);
}
template <typename Scalar>
void
Object::set(std::string const& key, Scalar const& value)
{
checkWritable("set");
if (writer_)
writer_->set(key, value);
}
inline Json::Value&
setArray(Json::Value& json, Json::StaticString const& key)
{
return (json[key] = Json::arrayValue);
}
inline Array
setArray(Object& json, Json::StaticString const& key)
{
return json.setArray(std::string(key));
}
inline Json::Value&
addObject(Json::Value& json, Json::StaticString const& key)
{
return (json[key] = Json::objectValue);
}
inline Object
addObject(Object& object, Json::StaticString const& key)
{
return object.setObject(std::string(key));
}
inline Json::Value&
appendArray(Json::Value& json)
{
return json.append(Json::arrayValue);
}
inline Array
appendArray(Array& json)
{
return json.appendArray();
}
inline Json::Value&
appendObject(Json::Value& json)
{
return json.append(Json::objectValue);
}
inline Object
appendObject(Array& json)
{
return json.appendObject();
}
} // namespace Json
#endif

View File

@@ -1,6 +1,8 @@
#ifndef XRPL_JSON_JSON_READER_H_INCLUDED
#define XRPL_JSON_JSON_READER_H_INCLUDED
#define CPPTL_JSON_READER_H_INCLUDED
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
@@ -66,7 +68,7 @@ public:
* error occurred during parsing.
*/
std::string
getFormattedErrorMessages() const;
getFormatedErrorMessages() const;
static constexpr unsigned nest_limit{25};
@@ -229,4 +231,4 @@ operator>>(std::istream&, Value&);
} // namespace Json
#endif // XRPL_JSON_JSON_READER_H_INCLUDED
#endif // CPPTL_JSON_READER_H_INCLUDED

View File

@@ -44,7 +44,7 @@ enum ValueType {
class StaticString
{
public:
constexpr explicit StaticString(char const* czString) : str_(czString)
constexpr explicit StaticString(char const* czstring) : str_(czstring)
{
}
@@ -682,4 +682,4 @@ public:
} // namespace Json
#endif // XRPL_JSON_JSON_VALUE_H_INCLUDED
#endif // CPPTL_JSON_H_INCLUDED

View File

@@ -90,7 +90,7 @@ private:
void
writeArrayValue(Value const& value);
bool
isMultilineArray(Value const& value);
isMultineArray(Value const& value);
void
pushValue(std::string const& value);
void
@@ -157,7 +157,7 @@ private:
void
writeArrayValue(Value const& value);
bool
isMultilineArray(Value const& value);
isMultineArray(Value const& value);
void
pushValue(std::string const& value);
void

View File

@@ -15,7 +15,7 @@ namespace xrpl {
namespace credentials {
// These function will be used by the code that use DepositPreauth / Credentials
// (and any future pre-authorization modes) as part of authorization (all the
// (and any future preauthorization modes) as part of authorization (all the
// transfer funds transactions)
// Check if credential sfExpiration field has passed ledger's parentCloseTime
@@ -41,8 +41,7 @@ checkFields(STTx const& tx, beast::Journal j);
// Accessing the ledger to check if provided credentials are valid. Do not use
// in doApply (only in preclaim) since it does not remove expired credentials.
// If you call it in preclaim, you also must call verifyDepositPreauth in
// doApply
// If you call it in prelaim, you also must call verifyDepositPreauth in doApply
TER
valid(
STTx const& tx,

View File

@@ -6,7 +6,7 @@
namespace xrpl {
namespace NodeStore {
/** Simple NodeStore Scheduler that just performs the tasks synchronously. */
/** Simple NodeStore Scheduler that just peforms the tasks synchronously. */
class DummyScheduler : public Scheduler
{
public:

View File

@@ -55,7 +55,7 @@ public:
HyperLevelDB, LevelDBFactory, SQLite, MDB
If the fastBackendParameter is omitted or empty, no ephemeral database
is used. If the scheduler parameter is omitted or unspecified, a
is used. If the scheduler parameter is omited or unspecified, a
synchronous scheduler is used which performs all tasks immediately on
the caller's thread.

View File

@@ -96,7 +96,7 @@ Facebook's RocksDB database, builds on LevelDB.
Use SQLite.
'path' specifies where the backend will store its data files.
'path' speficies where the backend will store its data files.
Choices for 'compression'
@@ -130,7 +130,7 @@ newer versions of RocksDB (TBD).
## Discussion
RocksDBQuickFactory is intended to provide a testbed for comparing potential
rocksdb performance with the existing recommended configuration in xrpld.cfg.
rocksdb performance with the existing recommended configuration in rippled.cfg.
Through various executions and profiling some conclusions are presented below.
- If the write ahead log is enabled, insert speed soon clogs up under load. The
@@ -161,7 +161,7 @@ Through various executions and profiling some conclusions are presented below.
- Multiple runs of the benchmarks can yield surprisingly different results. This
can perhaps be attributed to the asynchronous nature of rocksdb's compaction
process. The benchmarks are artificial and create highly unlikely write load to
process. The benchmarks are artifical and create highly unlikely write load to
create the dataset to measure different read access patterns. Therefore multiple
runs of the benchmarks are required to get a feel for the effectiveness of the
changes. This contrasts sharply with the keyvadb benchmarking were highly

View File

@@ -9,7 +9,7 @@ import "org/xrpl/rpc/v1/get_ledger_entry.proto";
import "org/xrpl/rpc/v1/get_ledger_data.proto";
import "org/xrpl/rpc/v1/get_ledger_diff.proto";
// These methods are binary only methods for retrieving arbitrary ledger state
// These methods are binary only methods for retrieiving arbitrary ledger state
// via gRPC. These methods are used by clio, but can also be
// used by any client that wants to extract ledger state in an efficient manner.
// They do not directly mimic the JSON equivalent methods.

View File

@@ -58,14 +58,14 @@ static_assert(apiMaximumSupportedVersion >= apiMinimumSupportedVersion);
static_assert(apiBetaVersion >= apiMaximumSupportedVersion);
static_assert(apiMaximumValidVersion >= apiMaximumSupportedVersion);
inline void
setVersion(Json::Value& parent, unsigned int apiVersion, bool betaEnabled)
template <class JsonObject>
void
setVersion(JsonObject& parent, unsigned int apiVersion, bool betaEnabled)
{
XRPL_ASSERT(
apiVersion != apiInvalidVersion,
"xrpl::RPC::setVersion : input is valid");
auto& retObj = parent[jss::version] = Json::objectValue;
auto& retObj = addObject(parent, jss::version);
if (apiVersion == apiVersionIfUnspecified)
{

View File

@@ -209,11 +209,33 @@ get_error_info(error_code_i code);
/** Add or update the json update to reflect the error code. */
/** @{ */
template <class JsonValue>
void
inject_error(error_code_i code, Json::Value& json);
inject_error(error_code_i code, JsonValue& json)
{
ErrorInfo const& info(get_error_info(code));
json[jss::error] = info.token;
json[jss::error_code] = info.code;
json[jss::error_message] = info.message;
}
template <class JsonValue>
void
inject_error(error_code_i code, std::string const& message, Json::Value& json);
inject_error(int code, JsonValue& json)
{
inject_error(error_code_i(code), json);
}
template <class JsonValue>
void
inject_error(error_code_i code, std::string const& message, JsonValue& json)
{
ErrorInfo const& info(get_error_info(code));
json[jss::error] = info.token;
json[jss::error_code] = info.code;
json[jss::error_message] = message;
}
/** @} */
/** Returns a new json object that reflects the error code. */

View File

@@ -20,7 +20,7 @@ namespace xrpl {
Arithmetic operations can throw std::overflow_error during normalization
if the amount exceeds the largest representable amount, but underflows
will silently truncate to zero.
will silently trunctate to zero.
*/
class IOUAmount : private boost::totally_ordered<IOUAmount>,
private boost::additive<IOUAmount>

View File

@@ -18,7 +18,7 @@
namespace xrpl {
class SeqProxy;
/** Keylet computation functions.
/** Keylet computation funclets.
Entries in the ledger are located using 256-bit locators. The locators are
calculated using a wide range of parameters specific to the entry whose

View File

@@ -38,7 +38,7 @@ private:
std::unordered_map<std::uint16_t, uint256> txFeatureMap_;
std::unordered_map<std::uint16_t, Delegation> delegableTx_;
std::unordered_map<std::uint16_t, Delegation> delegatableTx_;
std::unordered_map<std::string, GranularPermissionType>
granularPermissionMap_;
@@ -71,7 +71,8 @@ public:
getTxFeature(TxType txType) const;
bool
isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const;
isDelegatable(std::uint32_t const& permissionValue, Rules const& rules)
const;
// for tx level permission, permission value is equal to tx type plus one
uint32_t

View File

@@ -179,7 +179,7 @@ static constexpr int loanPaymentsPerFeeIncrement = 5;
*
* This limit is enforced during the loan payment process, and thus is not
* estimated. If the limit is hit, no further payments or overpayments will be
* processed, no matter how much of the transaction Amount is left, but the
* processed, no matter how much of the transation Amount is left, but the
* transaction will succeed with the payments that have been processed up to
* that point.
*

View File

@@ -210,7 +210,7 @@ public:
private:
// The ceil_in and ceil_out methods that deal in TAmount all convert
// their arguments to STAmount and convert the result back to TAmount.
// their arguments to STAoumout and convert the result back to TAmount.
// This helper function takes care of all the conversion operations.
template <
class In,

View File

@@ -9,7 +9,7 @@ namespace xrpl {
bool
isRpcError(Json::Value jvResult);
Json::Value
rpcError(error_code_i iError);
rpcError(int iError);
} // namespace xrpl

View File

@@ -277,7 +277,7 @@ constexpr std::uint32_t const tfLoanOverpayment = 0x00010000;
// interest and fees, or it will fail. False: Not a full payment.
constexpr std::uint32_t const tfLoanFullPayment = 0x00020000;
// tfLoanLatePayment: True, indicates that the payment is late,
// and includes late interest and fees. If the loan is not late,
// and includes late iterest and fees. If the loan is not late,
// it will fail. False: not a late payment. If the current payment
// is overdue, the transaction will fail.
constexpr std::uint32_t const tfLoanLatePayment = 0x00040000;

View File

@@ -33,12 +33,12 @@ struct AttestationBase
// Account on the sending chain that triggered the event (sent the
// transaction)
AccountID sendingAccount;
// Amount transferred on the sending chain
// Amount transfered on the sending chain
STAmount sendingAmount;
// Account on the destination chain that collects a share of the attestation
// reward
AccountID rewardAccount;
// Amount was transferred on the locking chain
// Amount was transfered on the locking chain
bool wasLockingChainSend;
explicit AttestationBase(
@@ -354,7 +354,7 @@ struct XChainCreateAccountAttestation
XChainCreateAccountAttestation const& rhs);
};
// Attestations from witness servers for a particular claim ID and bridge.
// Attestations from witness servers for a particular claimid and bridge.
// Only one attestation per signature is allowed.
template <class TAttestation>
class XChainAttestationsBase

View File

@@ -42,7 +42,7 @@ concept ValidConstructSTArgs =
class STVar
{
private:
// The largest "small object" we can accommodate
// The largest "small object" we can accomodate
static std::size_t constexpr max_size = 72;
std::aligned_storage<max_size>::type d_;

View File

@@ -237,7 +237,7 @@ LEDGER_ENTRY(ltOFFER, 0x006f, Offer, offer, ({
{sfAdditionalBooks, soeOPTIONAL},
}))
/** A ledger object which describes a deposit pre-authorization.
/** A ledger object which describes a deposit preauthorization.
\sa keylet::depositPreauth
*/
@@ -577,7 +577,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
// - TrueTotalLoanValue = PaymentRemaining * PeriodicPayment
// The unrounded true total value of the loan.
//
// - TrueTotalPrincipalOutstanding can be computed using the algorithm
// - TrueTotalPrincialOutstanding can be computed using the algorithm
// in the ripple::detail::loanPrincipalFromPeriodicPayment function.
//
// - TrueTotalInterestOutstanding = TrueTotalLoanValue -

View File

@@ -66,7 +66,7 @@ values over time: this is implemented by the DecayingSample class.
Each server in a cluster creates a list of IP addresses of end points
that are imposing a significant load. This list is called Gossip, which
is passed to other nodes in that cluster. Gossip helps individual
servers in the cluster identify IP addresses that might be unduly loading
servers in the cluster identify IP addreses that might be unduly loading
the entire cluster. Again the recourse of the individual servers is to
drop connections to those IP addresses that occur commonly in the gossip.

View File

@@ -61,7 +61,7 @@ private:
// List of all active admin entries
EntryIntrusiveList admin_;
// List of all inactive entries
// List of all inactve entries
EntryIntrusiveList inactive_;
// All imported gossip data

View File

@@ -9,7 +9,7 @@
namespace xrpl {
// Before boost 1.70, get_lowest_layer required an explicit template parameter
// Before boost 1.70, get_lowest_layer required an explicit templat parameter
template <class T>
decltype(auto)
get_lowest_layer(T& t) noexcept

View File

@@ -226,7 +226,7 @@ The `fetchNodeNT()` method goes through three phases:
Any SHAMapLeafNode that is immutable has a sequence number of zero
(sharable). When a mutable `SHAMap` is created then its SHAMapTreeNodes are
given non-zero sequence numbers (unshareable). But all nodes in the
given non-zero sequence numbers (unsharable). But all nodes in the
TreeNodeCache are immutable, so if one is found here, its sequence number
will be 0.

View File

@@ -125,13 +125,13 @@ intrusive_ptr_release(SHAMapItem const* x)
{
auto p = reinterpret_cast<std::uint8_t const*>(x);
// The SHAMapItem constructor isn't trivial (because the destructor
// The SHAMapItem constuctor isn't trivial (because the destructor
// for CountedObject isn't) so we can't avoid calling it here, but
// plan for a future where we might not need to.
if constexpr (!std::is_trivially_destructible_v<SHAMapItem>)
std::destroy_at(x);
// If the slabber doesn't claim this pointer, it was allocated
// If the slabber doens't claim this pointer, it was allocated
// manually, so we free it manually.
if (!detail::slabber.deallocate(const_cast<std::uint8_t*>(p)))
delete[] p;

View 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
View File

@@ -0,0 +1,171 @@
# Doctest Migration Documentation
This document describes the migration of unit tests from the beast `unit_test` framework to the doctest framework.
## Overview
Tests were migrated from `src/test/` (beast unit_test format) to `src/doctest/` (doctest format), following the pattern established in `src/tests/libxrpl/`.
## Build Configuration
### CMakeLists.txt Structure
Created `src/doctest/CMakeLists.txt` with:
- Helper function `xrpl_add_doctest(name)` that creates per-module executables
- Compiler flags: `-m64 -g -std=c++20 -fPIE -Wno-unknown-warning-option -Wall -Wdeprecated -Wno-deprecated-declarations -Wextra -Wno-unused-parameter -Werror -fstack-protector -Wno-sign-compare -Wno-unused-but-set-variable -MD -MT -MF`
- Six module targets: `xrpl.doctest.basics`, `xrpl.doctest.beast`, `xrpl.doctest.core`, `xrpl.doctest.csf`, `xrpl.doctest.nodestore`, `xrpl.doctest.protocol`
### Module Structure
Each module has its own `main.cpp` following the pre-migrated test pattern:
```
src/doctest/
├── basics/main.cpp
├── beast/main.cpp
├── core/main.cpp
├── csf/main.cpp
├── nodestore/main.cpp
└── protocol/main.cpp
```
## Framework Conversion Patterns
| Beast Unit Test | Doctest Equivalent |
|-----------------|-------------------|
| `#include <xrpl/beast/unit_test.h>` | `#include <doctest/doctest.h>` |
| `BEAST_EXPECT(expr)` | `CHECK(expr)` |
| `BEAST_EXPECTS(expr, msg)` | `CHECK_MESSAGE(expr, msg)` |
| `testcase("name")` | `SUBCASE("name")` |
| `class X : public unit_test::suite { ... }` | Free functions with `TEST_CASE` |
| `BEAST_DEFINE_TESTSUITE(Name, Module, Lib)` | `TEST_CASE("Name")` |
| `pass()` / `fail()` | `CHECK(true)` / `CHECK(false)` |
## Namespace Changes
- Changed from `namespace ripple` to `namespace xrpl` where applicable
- Header paths changed from `xrpld/` to `xrpl/` (e.g., `xrpld/app/``xrpl/protocol/`)
## Common Migration Issues and Solutions
### 1. CHECK Macro with Complex Expressions
**Problem**: Doctest's CHECK macro doesn't support `&&` or `||` in expressions.
```cpp
// Doesn't work
CHECK(a && b);
// Solution: Split into separate checks
CHECK(a);
CHECK(b);
```
### 2. CHECK Macro with Custom Iterator Comparisons
**Problem**: CHECK wraps expressions in `Expression_lhs<>` which breaks template argument deduction for custom comparison operators (especially boost::intrusive iterators).
```cpp
// Doesn't compile
CHECK(iter != container.end());
// Solution: Store result in bool first
bool notEnd = (iter != container.end());
CHECK(notEnd);
```
### 3. Constructor Argument Order
Some container constructors have different argument order than initially assumed:
```cpp
// Wrong order
Container c(clock, first, last);
// Correct order (iterators before clock)
Container c(first, last, clock);
```
### 4. Return Type Differences
For map types with `P&&` insert overloads, return types differ:
```cpp
// Use if constexpr to handle both cases
if constexpr (!IsMulti && IsMap)
{
auto result = c.insert(c.end(), value); // returns pair<iterator, bool>
CHECK(result.first != c.end());
}
else
{
auto it = c.insert(c.end(), value); // returns iterator
CHECK(it != c.end());
}
```
## Files Migrated
### basics/ (13 files)
- Buffer.cpp, Expected.cpp, IOUAmount.cpp, KeyCache.cpp, Number.cpp
- StringUtilities.cpp, TaggedCache.cpp, Units.cpp, XRPAmount.cpp
- base58.cpp, base_uint.cpp, hardened_hash.cpp, join.cpp
### beast/ (11 files)
- CurrentThreadName.cpp, IPEndpoint.cpp, Journal.cpp, LexicalCast.cpp
- PropertyStream.cpp, SemanticVersion.cpp, aged_associative_container.cpp
- basic_seconds_clock.cpp, beast_Zero.cpp, xxhasher.cpp
### core/ (1 file)
- Workers.cpp
### csf/ (4 files)
- BasicNetwork.cpp, Digraph.cpp, Histogram.cpp, Scheduler.cpp
### nodestore/ (1 file)
- varint.cpp
### protocol/ (14 files)
- ApiVersion.cpp, BuildInfo.cpp, Issue.cpp, MultiApiJson.cpp
- PublicKey.cpp, Quality.cpp, STAccount.cpp, STInteger.cpp
- STNumber.cpp, SecretKey.cpp, Seed.cpp, SeqProxy.cpp
- Serializer.cpp, TER.cpp
## Test Results Summary
| Module | Test Cases | Assertions |
|--------|------------|------------|
| basics | 61 | 2,638,582 |
| beast | 48 | 162,715 |
| core | 6 | 66 |
| csf | 8 | 101 |
| nodestore | 1 | 68 |
| protocol | 73 | 20,372 |
| **Total** | **197** | **2,821,904** |
## Running Tests
```bash
# Build all doctest targets
cd .build
cmake --build . --target xrpl.doctests
# Run individual module
./src/doctest/xrpl.doctest.basics
./src/doctest/xrpl.doctest.beast
./src/doctest/xrpl.doctest.core
./src/doctest/xrpl.doctest.csf
./src/doctest/xrpl.doctest.nodestore
./src/doctest/xrpl.doctest.protocol
# Run all tests
for test in src/doctest/xrpl.doctest.*; do ./$test; done
```
## Tests Not Migrated
Some tests were not migrated due to dependencies:
- **Manual tests** requiring user interaction (e.g., `DetectCrash_test`)
- **Tests using xrpld infrastructure** (`test/jtx.h`, `unit_test/SuiteJournal.h`)
- **Complex async tests** using boost coroutines
- **Tests with FileDirGuard** or other test-specific utilities

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

View 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

View 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();

View 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();

View 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();

View File

@@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

View 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();

View 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();

View 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();

View 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();

View 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();

View 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();

File diff suppressed because it is too large Load Diff

View 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();

View 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();

View File

@@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

View 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();

View 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

View File

@@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

View 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();

View 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();

View 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();

Some files were not shown because too many files have changed in this diff Show More