From 1845b1c656e5a9030e9f4caf02f2a99f9ce747fd Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 27 Oct 2025 14:43:45 +0000 Subject: [PATCH 01/11] chore: Better pre-commit failure message (#5940) --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 66ee2f3334..d0a657dd7e 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,7 +9,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@a8d7472b450eb53a1e5228f64552e5974457a21a + uses: XRPLF/actions/.github/workflows/pre-commit.yml@34790936fae4c6c751f62ec8c06696f9c1a5753a with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-a8c7be1" }' From 66f16469f9ed7923a1bd2003bc9e73ba10829a17 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 27 Oct 2025 17:27:56 +0000 Subject: [PATCH 02/11] fix: Upload all test binaries (#5932) --- .github/workflows/reusable-build.yml | 18 +++++++++++++++++- .github/workflows/reusable-test.yml | 21 +++++++++++++++++++-- cmake/xrpl_add_test.cmake | 2 +- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 24650858f9..09f7f25271 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -101,11 +101,27 @@ jobs: --parallel $(nproc) \ --target "${CMAKE_TARGET}" + - name: Put built binaries in one location + shell: bash + working-directory: ${{ inputs.build_dir }} + env: + BUILD_TYPE_DIR: ${{ runner.os == 'Windows' && inputs.build_type || '' }} + CMAKE_TARGET: ${{ inputs.cmake_target }} + run: | + mkdir -p ./binaries/doctest/ + + cp ./${BUILD_TYPE_DIR}/rippled* ./binaries/ + if [ "${CMAKE_TARGET}" != 'coverage' ]; then + cp ./src/tests/libxrpl/${BUILD_TYPE_DIR}/xrpl.test.* ./binaries/doctest/ + fi + - name: Upload rippled artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + env: + BUILD_DIR: ${{ inputs.build_dir }} with: name: rippled-${{ inputs.config_name }} - path: ${{ inputs.build_dir }}/${{ runner.os == 'Windows' && inputs.build_type || '' }}/rippled${{ runner.os == 'Windows' && '.exe' || '' }} + path: ${{ env.BUILD_DIR }}/binaries/ retention-days: 3 if-no-files-found: error diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index eb6a0271a4..cfd0fe6849 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -33,6 +33,10 @@ jobs: container: ${{ inputs.image != '' && inputs.image || null }} timeout-minutes: 30 steps: + - name: Cleanup workspace + if: ${{ runner.os == 'macOS' }} + uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e + - name: Download rippled artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: @@ -62,9 +66,22 @@ jobs: run: | ./rippled --version | grep libvoidstar - - name: Test the binary + - name: Run the embedded tests if: ${{ inputs.run_tests }} shell: bash run: | ./rippled --unittest --unittest-jobs $(nproc) - ctest -j $(nproc) --output-on-failure + + - name: Run the separate tests + if: ${{ inputs.run_tests }} + shell: bash + run: | + for test_file in ./doctest/*; do + echo "Executing $test_file" + chmod +x "$test_file" + if [[ "${{ runner.os }}" == "Windows" && "$test_file" == "./doctest/xrpl.test.net.exe" ]]; then + echo "Skipping $test_file on Windows" + else + "$test_file" + fi + done diff --git a/cmake/xrpl_add_test.cmake b/cmake/xrpl_add_test.cmake index d61f4ece3d..a1dd3847bd 100644 --- a/cmake/xrpl_add_test.cmake +++ b/cmake/xrpl_add_test.cmake @@ -7,7 +7,7 @@ function(xrpl_add_test name) "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp" ) - add_executable(${target} EXCLUDE_FROM_ALL ${ARGN} ${sources}) + add_executable(${target} ${ARGN} ${sources}) isolate_headers( ${target} From f4f76181732d1fbe1d31d15fb6ab47d79e067fe3 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:04:14 -0400 Subject: [PATCH 03/11] Change `fixMPTDeliveredAmount` to `Supported::yes` (#5833) Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- include/xrpl/protocol/detail/features.macro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 32dc1a1e3c..6052814357 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -37,7 +37,7 @@ XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (MPTDeliveredAmount, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMClawbackRounding, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) From 76f774e22d67490638121f9ce53da87495ed72f9 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Tue, 28 Oct 2025 14:19:39 +0000 Subject: [PATCH 04/11] refactor: Migrate json unit tests to use doctest (#5533) Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- .../scripts/levelization/results/ordering.txt | 1 + src/test/json/Writer_test.cpp | 217 --- src/test/json/json_value_test.cpp | 1398 ----------------- src/tests/libxrpl/CMakeLists.txt | 2 + .../libxrpl/json/Output.cpp} | 71 +- src/tests/libxrpl/json/Value.cpp | 1294 +++++++++++++++ src/tests/libxrpl/json/Writer.cpp | 192 +++ src/tests/libxrpl/json/main.cpp | 2 + 8 files changed, 1523 insertions(+), 1654 deletions(-) delete mode 100644 src/test/json/Writer_test.cpp delete mode 100644 src/test/json/json_value_test.cpp rename src/{test/json/Output_test.cpp => tests/libxrpl/json/Output.cpp} (51%) create mode 100644 src/tests/libxrpl/json/Value.cpp create mode 100644 src/tests/libxrpl/json/Writer.cpp create mode 100644 src/tests/libxrpl/json/main.cpp diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 55df4c2672..e17bd14bbc 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -138,6 +138,7 @@ test.toplevel > test.csf test.toplevel > xrpl.json test.unit_test > xrpl.basics tests.libxrpl > xrpl.basics +tests.libxrpl > xrpl.json tests.libxrpl > xrpl.net xrpl.json > xrpl.basics xrpl.ledger > xrpl.basics diff --git a/src/test/json/Writer_test.cpp b/src/test/json/Writer_test.cpp deleted file mode 100644 index 3739af07e1..0000000000 --- a/src/test/json/Writer_test.cpp +++ /dev/null @@ -1,217 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include - -#include -#include - -namespace Json { - -class JsonWriter_test : public ripple::test::TestOutputSuite -{ -public: - void - testTrivial() - { - setup("trivial"); - BEAST_EXPECT(output_.empty()); - expectResult(""); - } - - void - testNearTrivial() - { - setup("near trivial"); - BEAST_EXPECT(output_.empty()); - writer_->output(0); - expectResult("0"); - } - - void - testPrimitives() - { - setup("true"); - writer_->output(true); - expectResult("true"); - - setup("false"); - writer_->output(false); - expectResult("false"); - - setup("23"); - writer_->output(23); - expectResult("23"); - - setup("23.0"); - writer_->output(23.0); - expectResult("23.0"); - - setup("23.5"); - writer_->output(23.5); - expectResult("23.5"); - - setup("a string"); - writer_->output("a string"); - expectResult("\"a string\""); - - setup("nullptr"); - writer_->output(nullptr); - expectResult("null"); - } - - void - testEmpty() - { - setup("empty array"); - writer_->startRoot(Writer::array); - writer_->finish(); - expectResult("[]"); - - setup("empty object"); - writer_->startRoot(Writer::object); - writer_->finish(); - expectResult("{}"); - } - - void - testEscaping() - { - setup("backslash"); - writer_->output("\\"); - expectResult("\"\\\\\""); - - setup("quote"); - writer_->output("\""); - expectResult("\"\\\"\""); - - setup("backslash and quote"); - writer_->output("\\\""); - expectResult("\"\\\\\\\"\""); - - setup("escape embedded"); - writer_->output("this contains a \\ in the middle of it."); - expectResult("\"this contains a \\\\ in the middle of it.\""); - - setup("remaining escapes"); - writer_->output("\b\f\n\r\t"); - expectResult("\"\\b\\f\\n\\r\\t\""); - } - - void - testArray() - { - setup("empty array"); - writer_->startRoot(Writer::array); - writer_->append(12); - writer_->finish(); - expectResult("[12]"); - } - - void - testLongArray() - { - setup("long array"); - writer_->startRoot(Writer::array); - writer_->append(12); - writer_->append(true); - writer_->append("hello"); - writer_->finish(); - expectResult("[12,true,\"hello\"]"); - } - - void - testEmbeddedArraySimple() - { - setup("embedded array simple"); - writer_->startRoot(Writer::array); - writer_->startAppend(Writer::array); - writer_->finish(); - writer_->finish(); - expectResult("[[]]"); - } - - void - testObject() - { - setup("object"); - writer_->startRoot(Writer::object); - writer_->set("hello", "world"); - writer_->finish(); - - expectResult("{\"hello\":\"world\"}"); - } - - void - testComplexObject() - { - setup("complex object"); - writer_->startRoot(Writer::object); - - writer_->set("hello", "world"); - writer_->startSet(Writer::array, "array"); - - writer_->append(true); - writer_->append(12); - writer_->startAppend(Writer::array); - writer_->startAppend(Writer::object); - writer_->set("goodbye", "cruel world."); - writer_->startSet(Writer::array, "subarray"); - writer_->append(23.5); - writer_->finishAll(); - - expectResult( - "{\"hello\":\"world\",\"array\":[true,12," - "[{\"goodbye\":\"cruel world.\"," - "\"subarray\":[23.5]}]]}"); - } - - void - testJson() - { - setup("object"); - Json::Value value(Json::objectValue); - value["foo"] = 23; - writer_->startRoot(Writer::object); - writer_->set("hello", value); - writer_->finish(); - - expectResult("{\"hello\":{\"foo\":23}}"); - } - - void - run() override - { - testTrivial(); - testNearTrivial(); - testPrimitives(); - testEmpty(); - testEscaping(); - testArray(); - testLongArray(); - testEmbeddedArraySimple(); - testObject(); - testComplexObject(); - testJson(); - } -}; - -BEAST_DEFINE_TESTSUITE(JsonWriter, json, ripple); - -} // namespace Json diff --git a/src/test/json/json_value_test.cpp b/src/test/json/json_value_test.cpp deleted file mode 100644 index 0858786f04..0000000000 --- a/src/test/json/json_value_test.cpp +++ /dev/null @@ -1,1398 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include - -#include -#include - -namespace ripple { - -struct json_value_test : beast::unit_test::suite -{ - void - test_StaticString() - { - static constexpr char sample[]{"Contents of a Json::StaticString"}; - - static constexpr Json::StaticString test1(sample); - char const* addrTest1{test1}; - - BEAST_EXPECT(addrTest1 == &sample[0]); - BEAST_EXPECT(test1.c_str() == &sample[0]); - - static constexpr Json::StaticString test2{ - "Contents of a Json::StaticString"}; - static constexpr Json::StaticString test3{"Another StaticString"}; - - BEAST_EXPECT(test1 == test2); - BEAST_EXPECT(test1 != test3); - - std::string str{sample}; - BEAST_EXPECT(str == test2); - BEAST_EXPECT(str != test3); - BEAST_EXPECT(test2 == str); - BEAST_EXPECT(test3 != str); - } - - void - test_types() - { - // Exercise ValueType constructor - static constexpr Json::StaticString staticStr{"staticStr"}; - - auto testCopy = [this](Json::ValueType typ) { - Json::Value val{typ}; - Json::Value cpy{val}; - BEAST_EXPECT(val.type() == typ); - BEAST_EXPECT(cpy.type() == typ); - return val; - }; - { - Json::Value const nullV{testCopy(Json::nullValue)}; - BEAST_EXPECT(nullV.isNull()); - BEAST_EXPECT(!nullV.isBool()); - BEAST_EXPECT(!nullV.isInt()); - BEAST_EXPECT(!nullV.isUInt()); - BEAST_EXPECT(!nullV.isIntegral()); - BEAST_EXPECT(!nullV.isDouble()); - BEAST_EXPECT(!nullV.isNumeric()); - BEAST_EXPECT(!nullV.isString()); - BEAST_EXPECT(!nullV.isArray()); - BEAST_EXPECT(nullV.isArrayOrNull()); - BEAST_EXPECT(!nullV.isObject()); - BEAST_EXPECT(nullV.isObjectOrNull()); - } - { - Json::Value const intV{testCopy(Json::intValue)}; - BEAST_EXPECT(!intV.isNull()); - BEAST_EXPECT(!intV.isBool()); - BEAST_EXPECT(intV.isInt()); - BEAST_EXPECT(!intV.isUInt()); - BEAST_EXPECT(intV.isIntegral()); - BEAST_EXPECT(!intV.isDouble()); - BEAST_EXPECT(intV.isNumeric()); - BEAST_EXPECT(!intV.isString()); - BEAST_EXPECT(!intV.isArray()); - BEAST_EXPECT(!intV.isArrayOrNull()); - BEAST_EXPECT(!intV.isObject()); - BEAST_EXPECT(!intV.isObjectOrNull()); - } - { - Json::Value const uintV{testCopy(Json::uintValue)}; - BEAST_EXPECT(!uintV.isNull()); - BEAST_EXPECT(!uintV.isBool()); - BEAST_EXPECT(!uintV.isInt()); - BEAST_EXPECT(uintV.isUInt()); - BEAST_EXPECT(uintV.isIntegral()); - BEAST_EXPECT(!uintV.isDouble()); - BEAST_EXPECT(uintV.isNumeric()); - BEAST_EXPECT(!uintV.isString()); - BEAST_EXPECT(!uintV.isArray()); - BEAST_EXPECT(!uintV.isArrayOrNull()); - BEAST_EXPECT(!uintV.isObject()); - BEAST_EXPECT(!uintV.isObjectOrNull()); - } - { - Json::Value const realV{testCopy(Json::realValue)}; - BEAST_EXPECT(!realV.isNull()); - BEAST_EXPECT(!realV.isBool()); - BEAST_EXPECT(!realV.isInt()); - BEAST_EXPECT(!realV.isUInt()); - BEAST_EXPECT(!realV.isIntegral()); - BEAST_EXPECT(realV.isDouble()); - BEAST_EXPECT(realV.isNumeric()); - BEAST_EXPECT(!realV.isString()); - BEAST_EXPECT(!realV.isArray()); - BEAST_EXPECT(!realV.isArrayOrNull()); - BEAST_EXPECT(!realV.isObject()); - BEAST_EXPECT(!realV.isObjectOrNull()); - } - { - Json::Value const stringV{testCopy(Json::stringValue)}; - BEAST_EXPECT(!stringV.isNull()); - BEAST_EXPECT(!stringV.isBool()); - BEAST_EXPECT(!stringV.isInt()); - BEAST_EXPECT(!stringV.isUInt()); - BEAST_EXPECT(!stringV.isIntegral()); - BEAST_EXPECT(!stringV.isDouble()); - BEAST_EXPECT(!stringV.isNumeric()); - BEAST_EXPECT(stringV.isString()); - BEAST_EXPECT(!stringV.isArray()); - BEAST_EXPECT(!stringV.isArrayOrNull()); - BEAST_EXPECT(!stringV.isObject()); - BEAST_EXPECT(!stringV.isObjectOrNull()); - } - { - Json::Value const staticStrV{staticStr}; - { - Json::Value cpy{staticStrV}; - BEAST_EXPECT(staticStrV.type() == Json::stringValue); - BEAST_EXPECT(cpy.type() == Json::stringValue); - } - BEAST_EXPECT(!staticStrV.isNull()); - BEAST_EXPECT(!staticStrV.isBool()); - BEAST_EXPECT(!staticStrV.isInt()); - BEAST_EXPECT(!staticStrV.isUInt()); - BEAST_EXPECT(!staticStrV.isIntegral()); - BEAST_EXPECT(!staticStrV.isDouble()); - BEAST_EXPECT(!staticStrV.isNumeric()); - BEAST_EXPECT(staticStrV.isString()); - BEAST_EXPECT(!staticStrV.isArray()); - BEAST_EXPECT(!staticStrV.isArrayOrNull()); - BEAST_EXPECT(!staticStrV.isObject()); - BEAST_EXPECT(!staticStrV.isObjectOrNull()); - } - { - Json::Value const boolV{testCopy(Json::booleanValue)}; - BEAST_EXPECT(!boolV.isNull()); - BEAST_EXPECT(boolV.isBool()); - BEAST_EXPECT(!boolV.isInt()); - BEAST_EXPECT(!boolV.isUInt()); - BEAST_EXPECT(boolV.isIntegral()); - BEAST_EXPECT(!boolV.isDouble()); - BEAST_EXPECT(boolV.isNumeric()); - BEAST_EXPECT(!boolV.isString()); - BEAST_EXPECT(!boolV.isArray()); - BEAST_EXPECT(!boolV.isArrayOrNull()); - BEAST_EXPECT(!boolV.isObject()); - BEAST_EXPECT(!boolV.isObjectOrNull()); - } - { - Json::Value const arrayV{testCopy(Json::arrayValue)}; - BEAST_EXPECT(!arrayV.isNull()); - BEAST_EXPECT(!arrayV.isBool()); - BEAST_EXPECT(!arrayV.isInt()); - BEAST_EXPECT(!arrayV.isUInt()); - BEAST_EXPECT(!arrayV.isIntegral()); - BEAST_EXPECT(!arrayV.isDouble()); - BEAST_EXPECT(!arrayV.isNumeric()); - BEAST_EXPECT(!arrayV.isString()); - BEAST_EXPECT(arrayV.isArray()); - BEAST_EXPECT(arrayV.isArrayOrNull()); - BEAST_EXPECT(!arrayV.isObject()); - BEAST_EXPECT(!arrayV.isObjectOrNull()); - } - { - Json::Value const objectV{testCopy(Json::objectValue)}; - BEAST_EXPECT(!objectV.isNull()); - BEAST_EXPECT(!objectV.isBool()); - BEAST_EXPECT(!objectV.isInt()); - BEAST_EXPECT(!objectV.isUInt()); - BEAST_EXPECT(!objectV.isIntegral()); - BEAST_EXPECT(!objectV.isDouble()); - BEAST_EXPECT(!objectV.isNumeric()); - BEAST_EXPECT(!objectV.isString()); - BEAST_EXPECT(!objectV.isArray()); - BEAST_EXPECT(!objectV.isArrayOrNull()); - BEAST_EXPECT(objectV.isObject()); - BEAST_EXPECT(objectV.isObjectOrNull()); - } - } - - void - test_compare() - { - auto doCompare = [this]( - Json::Value const& lhs, - Json::Value const& rhs, - bool lhsEqRhs, - bool lhsLtRhs, - int line) { - auto fmt = [this](bool cond, char const* text, int line) { - if (cond) - this->pass(); - else - this->fail(text, __FILE__, line); - }; - fmt((lhs == rhs) == lhsEqRhs, "Value ==", line); - fmt((lhs != rhs) != lhsEqRhs, "Value !=", line); - fmt((lhs < rhs) == (!(lhsEqRhs | !lhsLtRhs)), "Value <", line); - fmt((lhs <= rhs) == (lhsEqRhs | lhsLtRhs), "Value <=", line); - fmt((lhs >= rhs) == (lhsEqRhs | !lhsLtRhs), "Value >=", line); - fmt((lhs > rhs) == (!(lhsEqRhs | lhsLtRhs)), "Value >", line); - }; - - Json::Value const null0; - Json::Value const intNeg1{-1}; - Json::Value const int0{Json::intValue}; - Json::Value const intPos1{1}; - Json::Value const uint0{Json::uintValue}; - Json::Value const uint1{1u}; - Json::Value const realNeg1{-1.0}; - Json::Value const real0{Json::realValue}; - Json::Value const realPos1{1.0}; - Json::Value const str0{Json::stringValue}; - Json::Value const str1{"1"}; - Json::Value const boolF{false}; - Json::Value const boolT{true}; - Json::Value const array0{Json::arrayValue}; - Json::Value const array1{[]() { - Json::Value array1; - array1[0u] = 1; - return array1; - }()}; - Json::Value const obj0{Json::objectValue}; - Json::Value const obj1{[]() { - Json::Value obj1; - obj1["one"] = 1; - return obj1; - }()}; - // lhs == rhs lhs < rhs - doCompare(null0, Json::Value{}, true, false, __LINE__); - doCompare(null0, intNeg1, false, true, __LINE__); - doCompare(null0, int0, false, true, __LINE__); - doCompare(null0, intPos1, false, true, __LINE__); - doCompare(null0, uint0, false, true, __LINE__); - doCompare(null0, uint1, false, true, __LINE__); - doCompare(null0, realNeg1, false, true, __LINE__); - doCompare(null0, real0, false, true, __LINE__); - doCompare(null0, realPos1, false, true, __LINE__); - doCompare(null0, str0, false, true, __LINE__); - doCompare(null0, str1, false, true, __LINE__); - doCompare(null0, boolF, false, true, __LINE__); - doCompare(null0, boolT, false, true, __LINE__); - doCompare(null0, array0, false, true, __LINE__); - doCompare(null0, array1, false, true, __LINE__); - doCompare(null0, obj0, false, true, __LINE__); - doCompare(null0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(intNeg1, null0, false, false, __LINE__); - doCompare(intNeg1, intNeg1, true, false, __LINE__); - doCompare(intNeg1, int0, false, true, __LINE__); - doCompare(intNeg1, intPos1, false, true, __LINE__); - doCompare(intNeg1, uint0, false, true, __LINE__); - doCompare(intNeg1, uint1, false, true, __LINE__); - doCompare(intNeg1, realNeg1, false, true, __LINE__); - doCompare(intNeg1, real0, false, true, __LINE__); - doCompare(intNeg1, realPos1, false, true, __LINE__); - doCompare(intNeg1, str0, false, true, __LINE__); - doCompare(intNeg1, str1, false, true, __LINE__); - doCompare(intNeg1, boolF, false, true, __LINE__); - doCompare(intNeg1, boolT, false, true, __LINE__); - doCompare(intNeg1, array0, false, true, __LINE__); - doCompare(intNeg1, array1, false, true, __LINE__); - doCompare(intNeg1, obj0, false, true, __LINE__); - doCompare(intNeg1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(int0, null0, false, false, __LINE__); - doCompare(int0, intNeg1, false, false, __LINE__); - doCompare(int0, int0, true, false, __LINE__); - doCompare(int0, intPos1, false, true, __LINE__); - doCompare(int0, uint0, true, false, __LINE__); - doCompare(int0, uint1, false, true, __LINE__); - doCompare(int0, realNeg1, false, true, __LINE__); - doCompare(int0, real0, false, true, __LINE__); - doCompare(int0, realPos1, false, true, __LINE__); - doCompare(int0, str0, false, true, __LINE__); - doCompare(int0, str1, false, true, __LINE__); - doCompare(int0, boolF, false, true, __LINE__); - doCompare(int0, boolT, false, true, __LINE__); - doCompare(int0, array0, false, true, __LINE__); - doCompare(int0, array1, false, true, __LINE__); - doCompare(int0, obj0, false, true, __LINE__); - doCompare(int0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(intPos1, null0, false, false, __LINE__); - doCompare(intPos1, intNeg1, false, false, __LINE__); - doCompare(intPos1, int0, false, false, __LINE__); - doCompare(intPos1, intPos1, true, false, __LINE__); - doCompare(intPos1, uint0, false, false, __LINE__); - doCompare(intPos1, uint1, true, false, __LINE__); - doCompare(intPos1, realNeg1, false, true, __LINE__); - doCompare(intPos1, real0, false, true, __LINE__); - doCompare(intPos1, realPos1, false, true, __LINE__); - doCompare(intPos1, str0, false, true, __LINE__); - doCompare(intPos1, str1, false, true, __LINE__); - doCompare(intPos1, boolF, false, true, __LINE__); - doCompare(intPos1, boolT, false, true, __LINE__); - doCompare(intPos1, array0, false, true, __LINE__); - doCompare(intPos1, array1, false, true, __LINE__); - doCompare(intPos1, obj0, false, true, __LINE__); - doCompare(intPos1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(uint0, null0, false, false, __LINE__); - doCompare(uint0, intNeg1, false, false, __LINE__); - doCompare(uint0, int0, true, false, __LINE__); - doCompare(uint0, intPos1, false, true, __LINE__); - doCompare(uint0, uint0, true, false, __LINE__); - doCompare(uint0, uint1, false, true, __LINE__); - doCompare(uint0, realNeg1, false, true, __LINE__); - doCompare(uint0, real0, false, true, __LINE__); - doCompare(uint0, realPos1, false, true, __LINE__); - doCompare(uint0, str0, false, true, __LINE__); - doCompare(uint0, str1, false, true, __LINE__); - doCompare(uint0, boolF, false, true, __LINE__); - doCompare(uint0, boolT, false, true, __LINE__); - doCompare(uint0, array0, false, true, __LINE__); - doCompare(uint0, array1, false, true, __LINE__); - doCompare(uint0, obj0, false, true, __LINE__); - doCompare(uint0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(uint1, null0, false, false, __LINE__); - doCompare(uint1, intNeg1, false, false, __LINE__); - doCompare(uint1, int0, false, false, __LINE__); - doCompare(uint1, intPos1, true, false, __LINE__); - doCompare(uint1, uint0, false, false, __LINE__); - doCompare(uint1, uint1, true, false, __LINE__); - doCompare(uint1, realNeg1, false, true, __LINE__); - doCompare(uint1, real0, false, true, __LINE__); - doCompare(uint1, realPos1, false, true, __LINE__); - doCompare(uint1, str0, false, true, __LINE__); - doCompare(uint1, str1, false, true, __LINE__); - doCompare(uint1, boolF, false, true, __LINE__); - doCompare(uint1, boolT, false, true, __LINE__); - doCompare(uint1, array0, false, true, __LINE__); - doCompare(uint1, array1, false, true, __LINE__); - doCompare(uint1, obj0, false, true, __LINE__); - doCompare(uint1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(realNeg1, null0, false, false, __LINE__); - doCompare(realNeg1, intNeg1, false, false, __LINE__); - doCompare(realNeg1, int0, false, false, __LINE__); - doCompare(realNeg1, intPos1, false, false, __LINE__); - doCompare(realNeg1, uint0, false, false, __LINE__); - doCompare(realNeg1, uint1, false, false, __LINE__); - doCompare(realNeg1, realNeg1, true, false, __LINE__); - doCompare(realNeg1, real0, false, true, __LINE__); - doCompare(realNeg1, realPos1, false, true, __LINE__); - doCompare(realNeg1, str0, false, true, __LINE__); - doCompare(realNeg1, str1, false, true, __LINE__); - doCompare(realNeg1, boolF, false, true, __LINE__); - doCompare(realNeg1, boolT, false, true, __LINE__); - doCompare(realNeg1, array0, false, true, __LINE__); - doCompare(realNeg1, array1, false, true, __LINE__); - doCompare(realNeg1, obj0, false, true, __LINE__); - doCompare(realNeg1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(real0, null0, false, false, __LINE__); - doCompare(real0, intNeg1, false, false, __LINE__); - doCompare(real0, int0, false, false, __LINE__); - doCompare(real0, intPos1, false, false, __LINE__); - doCompare(real0, uint0, false, false, __LINE__); - doCompare(real0, uint1, false, false, __LINE__); - doCompare(real0, realNeg1, false, false, __LINE__); - doCompare(real0, real0, true, false, __LINE__); - doCompare(real0, realPos1, false, true, __LINE__); - doCompare(real0, str0, false, true, __LINE__); - doCompare(real0, str1, false, true, __LINE__); - doCompare(real0, boolF, false, true, __LINE__); - doCompare(real0, boolT, false, true, __LINE__); - doCompare(real0, array0, false, true, __LINE__); - doCompare(real0, array1, false, true, __LINE__); - doCompare(real0, obj0, false, true, __LINE__); - doCompare(real0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(realPos1, null0, false, false, __LINE__); - doCompare(realPos1, intNeg1, false, false, __LINE__); - doCompare(realPos1, int0, false, false, __LINE__); - doCompare(realPos1, intPos1, false, false, __LINE__); - doCompare(realPos1, uint0, false, false, __LINE__); - doCompare(realPos1, uint1, false, false, __LINE__); - doCompare(realPos1, realNeg1, false, false, __LINE__); - doCompare(realPos1, real0, false, false, __LINE__); - doCompare(realPos1, realPos1, true, false, __LINE__); - doCompare(realPos1, str0, false, true, __LINE__); - doCompare(realPos1, str1, false, true, __LINE__); - doCompare(realPos1, boolF, false, true, __LINE__); - doCompare(realPos1, boolT, false, true, __LINE__); - doCompare(realPos1, array0, false, true, __LINE__); - doCompare(realPos1, array1, false, true, __LINE__); - doCompare(realPos1, obj0, false, true, __LINE__); - doCompare(realPos1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(str0, null0, false, false, __LINE__); - doCompare(str0, intNeg1, false, false, __LINE__); - doCompare(str0, int0, false, false, __LINE__); - doCompare(str0, intPos1, false, false, __LINE__); - doCompare(str0, uint0, false, false, __LINE__); - doCompare(str0, uint1, false, false, __LINE__); - doCompare(str0, realNeg1, false, false, __LINE__); - doCompare(str0, real0, false, false, __LINE__); - doCompare(str0, realPos1, false, false, __LINE__); - doCompare(str0, str0, true, false, __LINE__); - doCompare(str0, str1, false, true, __LINE__); - doCompare(str0, boolF, false, true, __LINE__); - doCompare(str0, boolT, false, true, __LINE__); - doCompare(str0, array0, false, true, __LINE__); - doCompare(str0, array1, false, true, __LINE__); - doCompare(str0, obj0, false, true, __LINE__); - doCompare(str0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(str1, null0, false, false, __LINE__); - doCompare(str1, intNeg1, false, false, __LINE__); - doCompare(str1, int0, false, false, __LINE__); - doCompare(str1, intPos1, false, false, __LINE__); - doCompare(str1, uint0, false, false, __LINE__); - doCompare(str1, uint1, false, false, __LINE__); - doCompare(str1, realNeg1, false, false, __LINE__); - doCompare(str1, real0, false, false, __LINE__); - doCompare(str1, realPos1, false, false, __LINE__); - doCompare(str1, str0, false, false, __LINE__); - doCompare(str1, str1, true, false, __LINE__); - doCompare(str1, boolF, false, true, __LINE__); - doCompare(str1, boolT, false, true, __LINE__); - doCompare(str1, array0, false, true, __LINE__); - doCompare(str1, array1, false, true, __LINE__); - doCompare(str1, obj0, false, true, __LINE__); - doCompare(str1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(boolF, null0, false, false, __LINE__); - doCompare(boolF, intNeg1, false, false, __LINE__); - doCompare(boolF, int0, false, false, __LINE__); - doCompare(boolF, intPos1, false, false, __LINE__); - doCompare(boolF, uint0, false, false, __LINE__); - doCompare(boolF, uint1, false, false, __LINE__); - doCompare(boolF, realNeg1, false, false, __LINE__); - doCompare(boolF, real0, false, false, __LINE__); - doCompare(boolF, realPos1, false, false, __LINE__); - doCompare(boolF, str0, false, false, __LINE__); - doCompare(boolF, str1, false, false, __LINE__); - doCompare(boolF, boolF, true, false, __LINE__); - doCompare(boolF, boolT, false, true, __LINE__); - doCompare(boolF, array0, false, true, __LINE__); - doCompare(boolF, array1, false, true, __LINE__); - doCompare(boolF, obj0, false, true, __LINE__); - doCompare(boolF, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(boolT, null0, false, false, __LINE__); - doCompare(boolT, intNeg1, false, false, __LINE__); - doCompare(boolT, int0, false, false, __LINE__); - doCompare(boolT, intPos1, false, false, __LINE__); - doCompare(boolT, uint0, false, false, __LINE__); - doCompare(boolT, uint1, false, false, __LINE__); - doCompare(boolT, realNeg1, false, false, __LINE__); - doCompare(boolT, real0, false, false, __LINE__); - doCompare(boolT, realPos1, false, false, __LINE__); - doCompare(boolT, str0, false, false, __LINE__); - doCompare(boolT, str1, false, false, __LINE__); - doCompare(boolT, boolF, false, false, __LINE__); - doCompare(boolT, boolT, true, false, __LINE__); - doCompare(boolT, array0, false, true, __LINE__); - doCompare(boolT, array1, false, true, __LINE__); - doCompare(boolT, obj0, false, true, __LINE__); - doCompare(boolT, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(array0, null0, false, false, __LINE__); - doCompare(array0, intNeg1, false, false, __LINE__); - doCompare(array0, int0, false, false, __LINE__); - doCompare(array0, intPos1, false, false, __LINE__); - doCompare(array0, uint0, false, false, __LINE__); - doCompare(array0, uint1, false, false, __LINE__); - doCompare(array0, realNeg1, false, false, __LINE__); - doCompare(array0, real0, false, false, __LINE__); - doCompare(array0, realPos1, false, false, __LINE__); - doCompare(array0, str0, false, false, __LINE__); - doCompare(array0, str1, false, false, __LINE__); - doCompare(array0, boolF, false, false, __LINE__); - doCompare(array0, boolT, false, false, __LINE__); - doCompare(array0, array0, true, false, __LINE__); - doCompare(array0, array1, false, true, __LINE__); - doCompare(array0, obj0, false, true, __LINE__); - doCompare(array0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(array1, null0, false, false, __LINE__); - doCompare(array1, intNeg1, false, false, __LINE__); - doCompare(array1, int0, false, false, __LINE__); - doCompare(array1, intPos1, false, false, __LINE__); - doCompare(array1, uint0, false, false, __LINE__); - doCompare(array1, uint1, false, false, __LINE__); - doCompare(array1, realNeg1, false, false, __LINE__); - doCompare(array1, real0, false, false, __LINE__); - doCompare(array1, realPos1, false, false, __LINE__); - doCompare(array1, str0, false, false, __LINE__); - doCompare(array1, str1, false, false, __LINE__); - doCompare(array1, boolF, false, false, __LINE__); - doCompare(array1, boolT, false, false, __LINE__); - doCompare(array1, array0, false, false, __LINE__); - doCompare(array1, array1, true, false, __LINE__); - doCompare(array1, obj0, false, true, __LINE__); - doCompare(array1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(obj0, null0, false, false, __LINE__); - doCompare(obj0, intNeg1, false, false, __LINE__); - doCompare(obj0, int0, false, false, __LINE__); - doCompare(obj0, intPos1, false, false, __LINE__); - doCompare(obj0, uint0, false, false, __LINE__); - doCompare(obj0, uint1, false, false, __LINE__); - doCompare(obj0, realNeg1, false, false, __LINE__); - doCompare(obj0, real0, false, false, __LINE__); - doCompare(obj0, realPos1, false, false, __LINE__); - doCompare(obj0, str0, false, false, __LINE__); - doCompare(obj0, str1, false, false, __LINE__); - doCompare(obj0, boolF, false, false, __LINE__); - doCompare(obj0, boolT, false, false, __LINE__); - doCompare(obj0, array0, false, false, __LINE__); - doCompare(obj0, array1, false, false, __LINE__); - doCompare(obj0, obj0, true, false, __LINE__); - doCompare(obj0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(obj1, null0, false, false, __LINE__); - doCompare(obj1, intNeg1, false, false, __LINE__); - doCompare(obj1, int0, false, false, __LINE__); - doCompare(obj1, intPos1, false, false, __LINE__); - doCompare(obj1, uint0, false, false, __LINE__); - doCompare(obj1, uint1, false, false, __LINE__); - doCompare(obj1, realNeg1, false, false, __LINE__); - doCompare(obj1, real0, false, false, __LINE__); - doCompare(obj1, realPos1, false, false, __LINE__); - doCompare(obj1, str0, false, false, __LINE__); - doCompare(obj1, str1, false, false, __LINE__); - doCompare(obj1, boolF, false, false, __LINE__); - doCompare(obj1, boolT, false, false, __LINE__); - doCompare(obj1, array0, false, false, __LINE__); - doCompare(obj1, array1, false, false, __LINE__); - doCompare(obj1, obj0, false, false, __LINE__); - doCompare(obj1, obj1, true, false, __LINE__); - } - - void - test_bool() - { - BEAST_EXPECT(!Json::Value()); - - BEAST_EXPECT(!Json::Value("")); - - BEAST_EXPECT(bool(Json::Value("empty"))); - BEAST_EXPECT(bool(Json::Value(false))); - BEAST_EXPECT(bool(Json::Value(true))); - BEAST_EXPECT(bool(Json::Value(0))); - BEAST_EXPECT(bool(Json::Value(1))); - - Json::Value array(Json::arrayValue); - BEAST_EXPECT(!array); - array.append(0); - BEAST_EXPECT(bool(array)); - - Json::Value object(Json::objectValue); - BEAST_EXPECT(!object); - object[""] = false; - BEAST_EXPECT(bool(object)); - } - - void - test_bad_json() - { - char const* s( - "{\"method\":\"ledger\",\"params\":[{\"ledger_index\":1e300}]}"); - - Json::Value j; - Json::Reader r; - - r.parse(s, j); - pass(); - } - - void - test_edge_cases() - { - std::string json; - - std::uint32_t max_uint = std::numeric_limits::max(); - std::int32_t max_int = std::numeric_limits::max(); - std::int32_t min_int = std::numeric_limits::min(); - - std::uint32_t a_uint = max_uint - 1978; - std::int32_t a_large_int = max_int - 1978; - std::int32_t a_small_int = min_int + 1978; - - json = "{\"max_uint\":" + std::to_string(max_uint); - json += ",\"max_int\":" + std::to_string(max_int); - json += ",\"min_int\":" + std::to_string(min_int); - json += ",\"a_uint\":" + std::to_string(a_uint); - json += ",\"a_large_int\":" + std::to_string(a_large_int); - json += ",\"a_small_int\":" + std::to_string(a_small_int); - json += "}"; - - Json::Value j1; - Json::Reader r1; - - BEAST_EXPECT(r1.parse(json, j1)); - BEAST_EXPECT(j1["max_uint"].asUInt() == max_uint); - BEAST_EXPECT(j1["max_int"].asInt() == max_int); - BEAST_EXPECT(j1["min_int"].asInt() == min_int); - BEAST_EXPECT(j1["a_uint"].asUInt() == a_uint); - BEAST_EXPECT(j1["a_uint"] > a_large_int); - BEAST_EXPECT(j1["a_uint"] > a_small_int); - BEAST_EXPECT(j1["a_large_int"].asInt() == a_large_int); - BEAST_EXPECT(j1["a_large_int"].asUInt() == a_large_int); - BEAST_EXPECT(j1["a_large_int"] < a_uint); - BEAST_EXPECT(j1["a_small_int"].asInt() == a_small_int); - BEAST_EXPECT(j1["a_small_int"] < a_uint); - - json = "{\"overflow\":"; - json += std::to_string(std::uint64_t(max_uint) + 1); - json += "}"; - - Json::Value j2; - Json::Reader r2; - - BEAST_EXPECT(!r2.parse(json, j2)); - - json = "{\"underflow\":"; - json += std::to_string(std::int64_t(min_int) - 1); - json += "}"; - - Json::Value j3; - Json::Reader r3; - - BEAST_EXPECT(!r3.parse(json, j3)); - - Json::Value intString{"4294967296"}; - try - { - [[maybe_unused]] std::uint32_t const uTooBig{intString.asUInt()}; - fail("4294967296", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - - intString = "4294967295"; - BEAST_EXPECT(intString.asUInt() == 4294967295u); - - intString = "0"; - BEAST_EXPECT(intString.asUInt() == 0); - - intString = "-1"; - try - { - [[maybe_unused]] std::uint32_t const uTooSmall{intString.asUInt()}; - fail("-1", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - - intString = "2147483648"; - try - { - [[maybe_unused]] std::int32_t tooPos{intString.asInt()}; - fail("2147483648", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - - intString = "2147483647"; - BEAST_EXPECT(intString.asInt() == 2147483647); - - intString = "-2147483648"; - BEAST_EXPECT(intString.asInt() == -2147483648LL); // MSVC wants the LL - - intString = "-2147483649"; - try - { - [[maybe_unused]] std::int32_t tooNeg{intString.asInt()}; - fail("-2147483649", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - } - - void - test_copy() - { - Json::Value v1{2.5}; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - - Json::Value v2 = v1; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - BEAST_EXPECT(v2.isDouble()); - BEAST_EXPECT(v2.asDouble() == 2.5); - BEAST_EXPECT(v1 == v2); - - v1 = v2; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - BEAST_EXPECT(v2.isDouble()); - BEAST_EXPECT(v2.asDouble() == 2.5); - BEAST_EXPECT(v1 == v2); - - pass(); - } - - void - test_move() - { - Json::Value v1{2.5}; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - - Json::Value v2 = std::move(v1); - BEAST_EXPECT(!v1); - BEAST_EXPECT(v2.isDouble()); - BEAST_EXPECT(v2.asDouble() == 2.5); - BEAST_EXPECT(v1 != v2); - - v1 = std::move(v2); - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - BEAST_EXPECT(!v2); - BEAST_EXPECT(v1 != v2); - - pass(); - } - - void - test_comparisons() - { - Json::Value a, b; - auto testEquals = [&](std::string const& name) { - BEAST_EXPECT(a == b); - BEAST_EXPECT(a <= b); - BEAST_EXPECT(a >= b); - - BEAST_EXPECT(!(a != b)); - BEAST_EXPECT(!(a < b)); - BEAST_EXPECT(!(a > b)); - - BEAST_EXPECT(b == a); - BEAST_EXPECT(b <= a); - BEAST_EXPECT(b >= a); - - BEAST_EXPECT(!(b != a)); - BEAST_EXPECT(!(b < a)); - BEAST_EXPECT(!(b > a)); - }; - - auto testGreaterThan = [&](std::string const& name) { - BEAST_EXPECT(!(a == b)); - BEAST_EXPECT(!(a <= b)); - BEAST_EXPECT(a >= b); - - BEAST_EXPECT(a != b); - BEAST_EXPECT(!(a < b)); - BEAST_EXPECT(a > b); - - BEAST_EXPECT(!(b == a)); - BEAST_EXPECT(b <= a); - BEAST_EXPECT(!(b >= a)); - - BEAST_EXPECT(b != a); - BEAST_EXPECT(b < a); - BEAST_EXPECT(!(b > a)); - }; - - a["a"] = Json::UInt(0); - b["a"] = Json::Int(0); - testEquals("zero"); - - b["a"] = Json::Int(-1); - testGreaterThan("negative"); - - Json::Int big = std::numeric_limits::max(); - Json::UInt bigger = big; - bigger++; - - a["a"] = bigger; - b["a"] = big; - testGreaterThan("big"); - } - - void - test_compact() - { - Json::Value j; - Json::Reader r; - char const* s("{\"array\":[{\"12\":23},{},null,false,0.5]}"); - - auto countLines = [](std::string const& str) { - return 1 + std::count_if(str.begin(), str.end(), [](char c) { - return c == '\n'; - }); - }; - - BEAST_EXPECT(r.parse(s, j)); - { - std::stringstream ss; - ss << j; - BEAST_EXPECT(countLines(ss.str()) > 1); - } - { - std::stringstream ss; - ss << Json::Compact(std::move(j)); - BEAST_EXPECT(countLines(ss.str()) == 1); - } - } - - void - test_conversions() - { - // We have Json::Int, but not Json::Double or Json::Real. - // We have Json::Int, Json::Value::Int, and Json::ValueType::intValue. - // We have Json::ValueType::realValue but Json::Value::asDouble. - // TODO: What's the thinking here? - { - // null - Json::Value val; - BEAST_EXPECT(val.isNull()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == ""); - BEAST_EXPECT(val.asInt() == 0); - BEAST_EXPECT(val.asUInt() == 0); - BEAST_EXPECT(val.asDouble() == 0.0); - BEAST_EXPECT(val.asBool() == false); - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::objectValue)); - } - { - // int - Json::Value val = -1234; - BEAST_EXPECT(val.isInt()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "-1234"); - BEAST_EXPECT(val.asInt() == -1234); - // BEAST_EXPECT(val.asUInt() == ?); // - // asserts or throws - BEAST_EXPECT(val.asDouble() == -1234.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // uint - Json::Value val = 1234U; - BEAST_EXPECT(val.isUInt()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "1234"); - BEAST_EXPECT(val.asInt() == 1234); - BEAST_EXPECT(val.asUInt() == 1234u); - BEAST_EXPECT(val.asDouble() == 1234.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // real - Json::Value val = 2.0; - BEAST_EXPECT(val.isDouble()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT( - std::regex_match(val.asString(), std::regex("^2\\.0*$"))); - BEAST_EXPECT(val.asInt() == 2); - BEAST_EXPECT(val.asUInt() == 2u); - BEAST_EXPECT(val.asDouble() == 2.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // numeric string - Json::Value val = "54321"; - BEAST_EXPECT(val.isString()); - BEAST_EXPECT(strcmp(val.asCString(), "54321") == 0); - BEAST_EXPECT(val.asString() == "54321"); - BEAST_EXPECT(val.asInt() == 54321); - BEAST_EXPECT(val.asUInt() == 54321u); - // BEAST_EXPECT(val.asDouble() == 54321.0); // - // asserts or throws - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // non-numeric string - Json::Value val(Json::stringValue); - BEAST_EXPECT(val.isString()); - BEAST_EXPECT(val.asCString() == nullptr); - BEAST_EXPECT(val.asString() == ""); - try - { - BEAST_EXPECT(val.asInt() == 0); - fail("expected exception", __FILE__, __LINE__); - } - catch (std::exception const&) - { - pass(); - } - try - { - BEAST_EXPECT(val.asUInt() == 0); - fail("expected exception", __FILE__, __LINE__); - } - catch (std::exception const&) - { - pass(); - } - // BEAST_EXPECT(val.asDouble() == ?); // - // asserts or throws - BEAST_EXPECT(val.asBool() == false); - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // bool false - Json::Value val = false; - BEAST_EXPECT(val.isBool()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "false"); - BEAST_EXPECT(val.asInt() == 0); - BEAST_EXPECT(val.asUInt() == 0); - BEAST_EXPECT(val.asDouble() == 0.0); - BEAST_EXPECT(val.asBool() == false); - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // bool true - Json::Value val = true; - BEAST_EXPECT(val.isBool()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "true"); - BEAST_EXPECT(val.asInt() == 1); - BEAST_EXPECT(val.asUInt() == 1); - BEAST_EXPECT(val.asDouble() == 1.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // array type - Json::Value val(Json::arrayValue); - BEAST_EXPECT(val.isArray()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts BEAST_EXPECT(val.asString() == ?); // asserts or - // throws BEAST_EXPECT(val.asInt() == ?); // asserts or - // throws BEAST_EXPECT(val.asUInt() == ?); // asserts or - // throws BEAST_EXPECT(val.asDouble() == ?); // asserts or - // throws - BEAST_EXPECT(val.asBool() == false); // empty or not - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // object type - Json::Value val(Json::objectValue); - BEAST_EXPECT(val.isObject()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); - // // asserts BEAST_EXPECT(val.asString() == ?); // asserts - // or throws BEAST_EXPECT(val.asInt() == ?); // asserts or - // throws BEAST_EXPECT(val.asUInt() == ?); // asserts or - // throws - BEAST_EXPECT(val.asBool() == false); // empty or not - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::objectValue)); - } - } - - void - test_access() - { - Json::Value val; - BEAST_EXPECT(val.type() == Json::nullValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - { - Json::Value const constVal = val; - BEAST_EXPECT(constVal[7u].type() == Json::nullValue); - BEAST_EXPECT(!constVal.isMember("key")); - BEAST_EXPECT(constVal["key"].type() == Json::nullValue); - BEAST_EXPECT(constVal.getMemberNames().empty()); - BEAST_EXPECT(constVal.get(1u, "default0") == "default0"); - BEAST_EXPECT(constVal.get(std::string("not"), "oh") == "oh"); - BEAST_EXPECT(constVal.get("missing", "default2") == "default2"); - } - - val = -7; - BEAST_EXPECT(val.type() == Json::intValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = 42u; - BEAST_EXPECT(val.type() == Json::uintValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = 3.14159; - BEAST_EXPECT(val.type() == Json::realValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = true; - BEAST_EXPECT(val.type() == Json::booleanValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = "string"; - BEAST_EXPECT(val.type() == Json::stringValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = Json::Value(Json::objectValue); - BEAST_EXPECT(val.type() == Json::objectValue); - BEAST_EXPECT(val.size() == 0); - static Json::StaticString const staticThree("three"); - val[staticThree] = 3; - val["two"] = 2; - BEAST_EXPECT(val.size() == 2); - BEAST_EXPECT(val.isValidIndex(1)); - BEAST_EXPECT(!val.isValidIndex(2)); - BEAST_EXPECT(val[staticThree] == 3); - BEAST_EXPECT(val.isMember("two")); - BEAST_EXPECT(val.isMember(staticThree)); - BEAST_EXPECT(!val.isMember("key")); - { - Json::Value const constVal = val; - BEAST_EXPECT(constVal["two"] == 2); - BEAST_EXPECT(constVal["four"].type() == Json::nullValue); - BEAST_EXPECT(constVal[staticThree] == 3); - BEAST_EXPECT(constVal.isMember("two")); - BEAST_EXPECT(constVal.isMember(staticThree)); - BEAST_EXPECT(!constVal.isMember("key")); - BEAST_EXPECT(val.get(std::string("two"), "backup") == 2); - BEAST_EXPECT(val.get("missing", "default2") == "default2"); - } - - val = Json::Value(Json::arrayValue); - BEAST_EXPECT(val.type() == Json::arrayValue); - BEAST_EXPECT(val.size() == 0); - val[0u] = "zero"; - val[1u] = "one"; - BEAST_EXPECT(val.size() == 2); - BEAST_EXPECT(val.isValidIndex(1)); - BEAST_EXPECT(!val.isValidIndex(2)); - BEAST_EXPECT(val[20u].type() == Json::nullValue); - BEAST_EXPECT(!val.isMember("key")); - { - Json::Value const constVal = val; - BEAST_EXPECT(constVal[0u] == "zero"); - BEAST_EXPECT(constVal[2u].type() == Json::nullValue); - BEAST_EXPECT(!constVal.isMember("key")); - BEAST_EXPECT(val.get(1u, "default0") == "one"); - BEAST_EXPECT(val.get(3u, "default1") == "default1"); - } - } - - void - test_removeMember() - { - Json::Value val; - BEAST_EXPECT( - val.removeMember(std::string("member")).type() == Json::nullValue); - - val = Json::Value(Json::objectValue); - static Json::StaticString const staticThree("three"); - val[staticThree] = 3; - val["two"] = 2; - BEAST_EXPECT(val.size() == 2); - - BEAST_EXPECT( - val.removeMember(std::string("six")).type() == Json::nullValue); - BEAST_EXPECT(val.size() == 2); - - BEAST_EXPECT(val.removeMember(staticThree) == 3); - BEAST_EXPECT(val.size() == 1); - - BEAST_EXPECT(val.removeMember(staticThree).type() == Json::nullValue); - BEAST_EXPECT(val.size() == 1); - - BEAST_EXPECT(val.removeMember(std::string("two")) == 2); - BEAST_EXPECT(val.size() == 0); - - BEAST_EXPECT( - val.removeMember(std::string("two")).type() == Json::nullValue); - BEAST_EXPECT(val.size() == 0); - } - - void - test_iterator() - { - { - // Iterating an array. - Json::Value arr{Json::arrayValue}; - arr[0u] = "zero"; - arr[1u] = "one"; - arr[2u] = "two"; - arr[3u] = "three"; - - Json::ValueIterator const b{arr.begin()}; - Json::ValueIterator const e{arr.end()}; - - Json::ValueIterator i1 = b; - Json::ValueIterator i2 = e; - --i2; - - // key(), index(), and memberName() on an object iterator. - BEAST_EXPECT(b != e); - BEAST_EXPECT(!(b == e)); - BEAST_EXPECT(i1.key() == 0); - BEAST_EXPECT(i2.key() == 3); - BEAST_EXPECT(i1.index() == 0); - BEAST_EXPECT(i2.index() == 3); - BEAST_EXPECT(std::strcmp(i1.memberName(), "") == 0); - BEAST_EXPECT(std::strcmp(i2.memberName(), "") == 0); - - // Pre and post increment and decrement. - *i1++ = "0"; - BEAST_EXPECT(*i1 == "one"); - *i1 = "1"; - ++i1; - - *i2-- = "3"; - BEAST_EXPECT(*i2 == "two"); - BEAST_EXPECT(i1 == i2); - *i2 = "2"; - BEAST_EXPECT(*i1 == "2"); - } - { - // Iterating a const object. - Json::Value const obj{[]() { - Json::Value obj{Json::objectValue}; - obj["0"] = 0; - obj["1"] = 1; - obj["2"] = 2; - obj["3"] = 3; - return obj; - }()}; - - Json::ValueConstIterator i1{obj.begin()}; - Json::ValueConstIterator i2{obj.end()}; - --i2; - - // key(), index(), and memberName() on an object iterator. - BEAST_EXPECT(i1 != i2); - BEAST_EXPECT(!(i1 == i2)); - BEAST_EXPECT(i1.key() == "0"); - BEAST_EXPECT(i2.key() == "3"); - BEAST_EXPECT(i1.index() == -1); - BEAST_EXPECT(i2.index() == -1); - BEAST_EXPECT(std::strcmp(i1.memberName(), "0") == 0); - BEAST_EXPECT(std::strcmp(i2.memberName(), "3") == 0); - - // Pre and post increment and decrement. - BEAST_EXPECT(*i1++ == 0); - BEAST_EXPECT(*i1 == 1); - ++i1; - - BEAST_EXPECT(*i2-- == 3); - BEAST_EXPECT(*i2 == 2); - BEAST_EXPECT(i1 == i2); - BEAST_EXPECT(*i1 == 2); - } - { - // Iterating a non-const null object. - Json::Value nul{}; - BEAST_EXPECT(nul.begin() == nul.end()); - } - { - // Iterating a const Int. - Json::Value const i{-3}; - BEAST_EXPECT(i.begin() == i.end()); - } - } - - void - test_nest_limits() - { - Json::Reader r; - { - auto nest = [](std::uint32_t depth) -> std::string { - std::string s = "{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "\"obj\":{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "}"; - s += "}"; - return s; - }; - - { - // Within object nest limit - auto json{nest(std::min(10u, Json::Reader::nest_limit))}; - Json::Value j; - BEAST_EXPECT(r.parse(json, j)); - } - - { - // Exceed object nest limit - auto json{nest(Json::Reader::nest_limit + 1)}; - Json::Value j; - BEAST_EXPECT(!r.parse(json, j)); - } - } - - auto nest = [](std::uint32_t depth) -> std::string { - std::string s = "{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "\"array\":[{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "]}"; - s += "}"; - return s; - }; - { - // Exceed array nest limit - auto json{nest(Json::Reader::nest_limit + 1)}; - Json::Value j; - BEAST_EXPECT(!r.parse(json, j)); - } - } - - void - test_leak() - { - // When run with the address sanitizer, this test confirms there is no - // memory leak with the scenarios below. - { - Json::Value a; - a[0u] = 1; - BEAST_EXPECT(a.type() == Json::arrayValue); - BEAST_EXPECT(a[0u].type() == Json::intValue); - a = std::move(a[0u]); - BEAST_EXPECT(a.type() == Json::intValue); - } - { - Json::Value b; - Json::Value temp; - temp["a"] = "Probably avoids the small string optimization"; - temp["b"] = "Also probably avoids the small string optimization"; - BEAST_EXPECT(temp.type() == Json::objectValue); - b.append(temp); - BEAST_EXPECT(temp.type() == Json::objectValue); - BEAST_EXPECT(b.size() == 1); - - b.append(std::move(temp)); - BEAST_EXPECT(b.size() == 2); - - // Note that the type() == nullValue check is implementation - // specific and not guaranteed to be valid in the future. - BEAST_EXPECT(temp.type() == Json::nullValue); - } - } - - void - run() override - { - test_StaticString(); - test_types(); - test_compare(); - test_bool(); - test_bad_json(); - test_edge_cases(); - test_copy(); - test_move(); - test_comparisons(); - test_compact(); - test_conversions(); - test_access(); - test_removeMember(); - test_iterator(); - test_nest_limits(); - test_leak(); - } -}; - -BEAST_DEFINE_TESTSUITE(json_value, json, ripple); - -} // namespace ripple diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index f97283c955..caa308dd1f 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -12,5 +12,7 @@ xrpl_add_test(basics) target_link_libraries(xrpl.test.basics PRIVATE xrpl.imports.test) xrpl_add_test(crypto) target_link_libraries(xrpl.test.crypto PRIVATE xrpl.imports.test) +xrpl_add_test(json) +target_link_libraries(xrpl.test.json PRIVATE xrpl.imports.test) xrpl_add_test(net) target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test) diff --git a/src/test/json/Output_test.cpp b/src/tests/libxrpl/json/Output.cpp similarity index 51% rename from src/test/json/Output_test.cpp rename to src/tests/libxrpl/json/Output.cpp index 6421682b01..3b542d81b0 100644 --- a/src/test/json/Output_test.cpp +++ b/src/tests/libxrpl/json/Output.cpp @@ -17,50 +17,43 @@ */ //============================================================================== -#include - +#include #include #include -namespace Json { +#include -struct Output_test : ripple::test::TestOutputSuite +#include + +using namespace ripple; +using namespace Json; + +TEST_SUITE_BEGIN("JsonOutput"); + +static void +checkOutput(std::string const& valueDesc) { - void - runTest(std::string const& name, std::string const& valueDesc) - { - setup(name); - Json::Value value; - BEAST_EXPECT(Json::Reader().parse(valueDesc, value)); - auto out = stringOutput(output_); - outputJson(value, out); + std::string output; + Json::Value value; + REQUIRE(Json::Reader().parse(valueDesc, value)); + auto out = stringOutput(output); + outputJson(value, out); - // Compare with the original version. - auto expected = Json::FastWriter().write(value); - expectResult(expected); - expectResult(valueDesc); - expectResult(jsonAsString(value)); - } + auto expected = Json::FastWriter().write(value); + CHECK(output == expected); + CHECK(output == valueDesc); + CHECK(output == jsonAsString(value)); +} - void - runTest(std::string const& name) - { - runTest(name, name); - } +TEST_CASE("output cases") +{ + checkOutput("{}"); + checkOutput("[]"); + checkOutput(R"([23,4.25,true,null,"string"])"); + checkOutput(R"({"hello":"world"})"); + checkOutput("[{}]"); + checkOutput("[[]]"); + checkOutput(R"({"array":[{"12":23},{},null,false,0.5]})"); +} - void - run() override - { - runTest("empty dict", "{}"); - runTest("empty array", "[]"); - runTest("array", "[23,4.25,true,null,\"string\"]"); - runTest("dict", "{\"hello\":\"world\"}"); - runTest("array dict", "[{}]"); - runTest("array array", "[[]]"); - runTest("more complex", "{\"array\":[{\"12\":23},{},null,false,0.5]}"); - } -}; - -BEAST_DEFINE_TESTSUITE(Output, json, ripple); - -} // namespace Json +TEST_SUITE_END(); diff --git a/src/tests/libxrpl/json/Value.cpp b/src/tests/libxrpl/json/Value.cpp new file mode 100644 index 0000000000..3da8e14ba4 --- /dev/null +++ b/src/tests/libxrpl/json/Value.cpp @@ -0,0 +1,1294 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace ripple { + +TEST_SUITE_BEGIN("json_value"); + +TEST_CASE("construct and compare Json::StaticString") +{ + static constexpr char sample[]{"Contents of a Json::StaticString"}; + + static constexpr Json::StaticString test1(sample); + char const* addrTest1{test1}; + + CHECK(addrTest1 == &sample[0]); + CHECK(test1.c_str() == &sample[0]); + + static constexpr Json::StaticString test2{ + "Contents of a Json::StaticString"}; + static constexpr Json::StaticString test3{"Another StaticString"}; + + CHECK(test1 == test2); + CHECK(test1 != test3); + + std::string str{sample}; + CHECK(str == test2); + CHECK(str != test3); + CHECK(test2 == str); + CHECK(test3 != str); +} + +TEST_CASE("different types") +{ + // Exercise ValueType constructor + static constexpr Json::StaticString staticStr{"staticStr"}; + + auto testCopy = [](Json::ValueType typ) { + Json::Value val{typ}; + Json::Value cpy{val}; + CHECK(val.type() == typ); + CHECK(cpy.type() == typ); + return val; + }; + { + Json::Value const nullV{testCopy(Json::nullValue)}; + CHECK(nullV.isNull()); + CHECK(!nullV.isBool()); + CHECK(!nullV.isInt()); + CHECK(!nullV.isUInt()); + CHECK(!nullV.isIntegral()); + CHECK(!nullV.isDouble()); + CHECK(!nullV.isNumeric()); + CHECK(!nullV.isString()); + CHECK(!nullV.isArray()); + CHECK(nullV.isArrayOrNull()); + CHECK(!nullV.isObject()); + CHECK(nullV.isObjectOrNull()); + } + { + Json::Value const intV{testCopy(Json::intValue)}; + CHECK(!intV.isNull()); + CHECK(!intV.isBool()); + CHECK(intV.isInt()); + CHECK(!intV.isUInt()); + CHECK(intV.isIntegral()); + CHECK(!intV.isDouble()); + CHECK(intV.isNumeric()); + CHECK(!intV.isString()); + CHECK(!intV.isArray()); + CHECK(!intV.isArrayOrNull()); + CHECK(!intV.isObject()); + CHECK(!intV.isObjectOrNull()); + } + { + Json::Value const uintV{testCopy(Json::uintValue)}; + CHECK(!uintV.isNull()); + CHECK(!uintV.isBool()); + CHECK(!uintV.isInt()); + CHECK(uintV.isUInt()); + CHECK(uintV.isIntegral()); + CHECK(!uintV.isDouble()); + CHECK(uintV.isNumeric()); + CHECK(!uintV.isString()); + CHECK(!uintV.isArray()); + CHECK(!uintV.isArrayOrNull()); + CHECK(!uintV.isObject()); + CHECK(!uintV.isObjectOrNull()); + } + { + Json::Value const realV{testCopy(Json::realValue)}; + CHECK(!realV.isNull()); + CHECK(!realV.isBool()); + CHECK(!realV.isInt()); + CHECK(!realV.isUInt()); + CHECK(!realV.isIntegral()); + CHECK(realV.isDouble()); + CHECK(realV.isNumeric()); + CHECK(!realV.isString()); + CHECK(!realV.isArray()); + CHECK(!realV.isArrayOrNull()); + CHECK(!realV.isObject()); + CHECK(!realV.isObjectOrNull()); + } + { + Json::Value const stringV{testCopy(Json::stringValue)}; + CHECK(!stringV.isNull()); + CHECK(!stringV.isBool()); + CHECK(!stringV.isInt()); + CHECK(!stringV.isUInt()); + CHECK(!stringV.isIntegral()); + CHECK(!stringV.isDouble()); + CHECK(!stringV.isNumeric()); + CHECK(stringV.isString()); + CHECK(!stringV.isArray()); + CHECK(!stringV.isArrayOrNull()); + CHECK(!stringV.isObject()); + CHECK(!stringV.isObjectOrNull()); + } + { + Json::Value const staticStrV{staticStr}; + { + Json::Value cpy{staticStrV}; + CHECK(staticStrV.type() == Json::stringValue); + CHECK(cpy.type() == Json::stringValue); + } + CHECK(!staticStrV.isNull()); + CHECK(!staticStrV.isBool()); + CHECK(!staticStrV.isInt()); + CHECK(!staticStrV.isUInt()); + CHECK(!staticStrV.isIntegral()); + CHECK(!staticStrV.isDouble()); + CHECK(!staticStrV.isNumeric()); + CHECK(staticStrV.isString()); + CHECK(!staticStrV.isArray()); + CHECK(!staticStrV.isArrayOrNull()); + CHECK(!staticStrV.isObject()); + CHECK(!staticStrV.isObjectOrNull()); + } + { + Json::Value const boolV{testCopy(Json::booleanValue)}; + CHECK(!boolV.isNull()); + CHECK(boolV.isBool()); + CHECK(!boolV.isInt()); + CHECK(!boolV.isUInt()); + CHECK(boolV.isIntegral()); + CHECK(!boolV.isDouble()); + CHECK(boolV.isNumeric()); + CHECK(!boolV.isString()); + CHECK(!boolV.isArray()); + CHECK(!boolV.isArrayOrNull()); + CHECK(!boolV.isObject()); + CHECK(!boolV.isObjectOrNull()); + } + { + Json::Value const arrayV{testCopy(Json::arrayValue)}; + CHECK(!arrayV.isNull()); + CHECK(!arrayV.isBool()); + CHECK(!arrayV.isInt()); + CHECK(!arrayV.isUInt()); + CHECK(!arrayV.isIntegral()); + CHECK(!arrayV.isDouble()); + CHECK(!arrayV.isNumeric()); + CHECK(!arrayV.isString()); + CHECK(arrayV.isArray()); + CHECK(arrayV.isArrayOrNull()); + CHECK(!arrayV.isObject()); + CHECK(!arrayV.isObjectOrNull()); + } + { + Json::Value const objectV{testCopy(Json::objectValue)}; + CHECK(!objectV.isNull()); + CHECK(!objectV.isBool()); + CHECK(!objectV.isInt()); + CHECK(!objectV.isUInt()); + CHECK(!objectV.isIntegral()); + CHECK(!objectV.isDouble()); + CHECK(!objectV.isNumeric()); + CHECK(!objectV.isString()); + CHECK(!objectV.isArray()); + CHECK(!objectV.isArrayOrNull()); + CHECK(objectV.isObject()); + CHECK(objectV.isObjectOrNull()); + } +} + +TEST_CASE("compare strings") +{ + auto doCompare = [&](Json::Value const& lhs, + Json::Value const& rhs, + bool lhsEqRhs, + bool lhsLtRhs, + int line) { + CAPTURE(line); + CHECK((lhs == rhs) == lhsEqRhs); + CHECK((lhs != rhs) != lhsEqRhs); + CHECK((lhs < rhs) == (!(lhsEqRhs || !lhsLtRhs))); + CHECK((lhs <= rhs) == (lhsEqRhs || lhsLtRhs)); + CHECK((lhs >= rhs) == (lhsEqRhs || !lhsLtRhs)); + CHECK((lhs > rhs) == (!(lhsEqRhs || lhsLtRhs))); + }; + + Json::Value const null0; + Json::Value const intNeg1{-1}; + Json::Value const int0{Json::intValue}; + Json::Value const intPos1{1}; + Json::Value const uint0{Json::uintValue}; + Json::Value const uint1{1u}; + Json::Value const realNeg1{-1.0}; + Json::Value const real0{Json::realValue}; + Json::Value const realPos1{1.0}; + Json::Value const str0{Json::stringValue}; + Json::Value const str1{"1"}; + Json::Value const boolF{false}; + Json::Value const boolT{true}; + Json::Value const array0{Json::arrayValue}; + Json::Value const array1{[]() { + Json::Value array1; + array1[0u] = 1; + return array1; + }()}; + Json::Value const obj0{Json::objectValue}; + Json::Value const obj1{[]() { + Json::Value obj1; + obj1["one"] = 1; + return obj1; + }()}; + +#pragma push_macro("DO_COMPARE") + // DO_COMPARE(lhs, rhs, lhsEqualsToRhs lhsLessThanRhs) +#define DO_COMPARE(lhs, rhs, eq, lt) doCompare(lhs, rhs, eq, lt, __LINE__) + DO_COMPARE(null0, Json::Value{}, true, false); + DO_COMPARE(null0, intNeg1, false, true); + DO_COMPARE(null0, int0, false, true); + DO_COMPARE(null0, intPos1, false, true); + DO_COMPARE(null0, uint0, false, true); + DO_COMPARE(null0, uint1, false, true); + DO_COMPARE(null0, realNeg1, false, true); + DO_COMPARE(null0, real0, false, true); + DO_COMPARE(null0, realPos1, false, true); + DO_COMPARE(null0, str0, false, true); + DO_COMPARE(null0, str1, false, true); + DO_COMPARE(null0, boolF, false, true); + DO_COMPARE(null0, boolT, false, true); + DO_COMPARE(null0, array0, false, true); + DO_COMPARE(null0, array1, false, true); + DO_COMPARE(null0, obj0, false, true); + DO_COMPARE(null0, obj1, false, true); + + DO_COMPARE(intNeg1, null0, false, false); + DO_COMPARE(intNeg1, intNeg1, true, false); + DO_COMPARE(intNeg1, int0, false, true); + DO_COMPARE(intNeg1, intPos1, false, true); + DO_COMPARE(intNeg1, uint0, false, true); + DO_COMPARE(intNeg1, uint1, false, true); + DO_COMPARE(intNeg1, realNeg1, false, true); + DO_COMPARE(intNeg1, real0, false, true); + DO_COMPARE(intNeg1, realPos1, false, true); + DO_COMPARE(intNeg1, str0, false, true); + DO_COMPARE(intNeg1, str1, false, true); + DO_COMPARE(intNeg1, boolF, false, true); + DO_COMPARE(intNeg1, boolT, false, true); + DO_COMPARE(intNeg1, array0, false, true); + DO_COMPARE(intNeg1, array1, false, true); + DO_COMPARE(intNeg1, obj0, false, true); + DO_COMPARE(intNeg1, obj1, false, true); + + DO_COMPARE(int0, null0, false, false); + DO_COMPARE(int0, intNeg1, false, false); + DO_COMPARE(int0, int0, true, false); + DO_COMPARE(int0, intPos1, false, true); + DO_COMPARE(int0, uint0, true, false); + DO_COMPARE(int0, uint1, false, true); + DO_COMPARE(int0, realNeg1, false, true); + DO_COMPARE(int0, real0, false, true); + DO_COMPARE(int0, realPos1, false, true); + DO_COMPARE(int0, str0, false, true); + DO_COMPARE(int0, str1, false, true); + DO_COMPARE(int0, boolF, false, true); + DO_COMPARE(int0, boolT, false, true); + DO_COMPARE(int0, array0, false, true); + DO_COMPARE(int0, array1, false, true); + DO_COMPARE(int0, obj0, false, true); + DO_COMPARE(int0, obj1, false, true); + + DO_COMPARE(intPos1, null0, false, false); + DO_COMPARE(intPos1, intNeg1, false, false); + DO_COMPARE(intPos1, int0, false, false); + DO_COMPARE(intPos1, intPos1, true, false); + DO_COMPARE(intPos1, uint0, false, false); + DO_COMPARE(intPos1, uint1, true, false); + DO_COMPARE(intPos1, realNeg1, false, true); + DO_COMPARE(intPos1, real0, false, true); + DO_COMPARE(intPos1, realPos1, false, true); + DO_COMPARE(intPos1, str0, false, true); + DO_COMPARE(intPos1, str1, false, true); + DO_COMPARE(intPos1, boolF, false, true); + DO_COMPARE(intPos1, boolT, false, true); + DO_COMPARE(intPos1, array0, false, true); + DO_COMPARE(intPos1, array1, false, true); + DO_COMPARE(intPos1, obj0, false, true); + DO_COMPARE(intPos1, obj1, false, true); + + DO_COMPARE(uint0, null0, false, false); + DO_COMPARE(uint0, intNeg1, false, false); + DO_COMPARE(uint0, int0, true, false); + DO_COMPARE(uint0, intPos1, false, true); + DO_COMPARE(uint0, uint0, true, false); + DO_COMPARE(uint0, uint1, false, true); + DO_COMPARE(uint0, realNeg1, false, true); + DO_COMPARE(uint0, real0, false, true); + DO_COMPARE(uint0, realPos1, false, true); + DO_COMPARE(uint0, str0, false, true); + DO_COMPARE(uint0, str1, false, true); + DO_COMPARE(uint0, boolF, false, true); + DO_COMPARE(uint0, boolT, false, true); + DO_COMPARE(uint0, array0, false, true); + DO_COMPARE(uint0, array1, false, true); + DO_COMPARE(uint0, obj0, false, true); + DO_COMPARE(uint0, obj1, false, true); + + DO_COMPARE(uint1, null0, false, false); + DO_COMPARE(uint1, intNeg1, false, false); + DO_COMPARE(uint1, int0, false, false); + DO_COMPARE(uint1, intPos1, true, false); + DO_COMPARE(uint1, uint0, false, false); + DO_COMPARE(uint1, uint1, true, false); + DO_COMPARE(uint1, realNeg1, false, true); + DO_COMPARE(uint1, real0, false, true); + DO_COMPARE(uint1, realPos1, false, true); + DO_COMPARE(uint1, str0, false, true); + DO_COMPARE(uint1, str1, false, true); + DO_COMPARE(uint1, boolF, false, true); + DO_COMPARE(uint1, boolT, false, true); + DO_COMPARE(uint1, array0, false, true); + DO_COMPARE(uint1, array1, false, true); + DO_COMPARE(uint1, obj0, false, true); + DO_COMPARE(uint1, obj1, false, true); + + DO_COMPARE(realNeg1, null0, false, false); + DO_COMPARE(realNeg1, intNeg1, false, false); + DO_COMPARE(realNeg1, int0, false, false); + DO_COMPARE(realNeg1, intPos1, false, false); + DO_COMPARE(realNeg1, uint0, false, false); + DO_COMPARE(realNeg1, uint1, false, false); + DO_COMPARE(realNeg1, realNeg1, true, false); + DO_COMPARE(realNeg1, real0, false, true); + DO_COMPARE(realNeg1, realPos1, false, true); + DO_COMPARE(realNeg1, str0, false, true); + DO_COMPARE(realNeg1, str1, false, true); + DO_COMPARE(realNeg1, boolF, false, true); + DO_COMPARE(realNeg1, boolT, false, true); + DO_COMPARE(realNeg1, array0, false, true); + DO_COMPARE(realNeg1, array1, false, true); + DO_COMPARE(realNeg1, obj0, false, true); + DO_COMPARE(realNeg1, obj1, false, true); + + DO_COMPARE(real0, null0, false, false); + DO_COMPARE(real0, intNeg1, false, false); + DO_COMPARE(real0, int0, false, false); + DO_COMPARE(real0, intPos1, false, false); + DO_COMPARE(real0, uint0, false, false); + DO_COMPARE(real0, uint1, false, false); + DO_COMPARE(real0, realNeg1, false, false); + DO_COMPARE(real0, real0, true, false); + DO_COMPARE(real0, realPos1, false, true); + DO_COMPARE(real0, str0, false, true); + DO_COMPARE(real0, str1, false, true); + DO_COMPARE(real0, boolF, false, true); + DO_COMPARE(real0, boolT, false, true); + DO_COMPARE(real0, array0, false, true); + DO_COMPARE(real0, array1, false, true); + DO_COMPARE(real0, obj0, false, true); + DO_COMPARE(real0, obj1, false, true); + + DO_COMPARE(realPos1, null0, false, false); + DO_COMPARE(realPos1, intNeg1, false, false); + DO_COMPARE(realPos1, int0, false, false); + DO_COMPARE(realPos1, intPos1, false, false); + DO_COMPARE(realPos1, uint0, false, false); + DO_COMPARE(realPos1, uint1, false, false); + DO_COMPARE(realPos1, realNeg1, false, false); + DO_COMPARE(realPos1, real0, false, false); + DO_COMPARE(realPos1, realPos1, true, false); + DO_COMPARE(realPos1, str0, false, true); + DO_COMPARE(realPos1, str1, false, true); + DO_COMPARE(realPos1, boolF, false, true); + DO_COMPARE(realPos1, boolT, false, true); + DO_COMPARE(realPos1, array0, false, true); + DO_COMPARE(realPos1, array1, false, true); + DO_COMPARE(realPos1, obj0, false, true); + DO_COMPARE(realPos1, obj1, false, true); + + DO_COMPARE(str0, null0, false, false); + DO_COMPARE(str0, intNeg1, false, false); + DO_COMPARE(str0, int0, false, false); + DO_COMPARE(str0, intPos1, false, false); + DO_COMPARE(str0, uint0, false, false); + DO_COMPARE(str0, uint1, false, false); + DO_COMPARE(str0, realNeg1, false, false); + DO_COMPARE(str0, real0, false, false); + DO_COMPARE(str0, realPos1, false, false); + DO_COMPARE(str0, str0, true, false); + DO_COMPARE(str0, str1, false, true); + DO_COMPARE(str0, boolF, false, true); + DO_COMPARE(str0, boolT, false, true); + DO_COMPARE(str0, array0, false, true); + DO_COMPARE(str0, array1, false, true); + DO_COMPARE(str0, obj0, false, true); + DO_COMPARE(str0, obj1, false, true); + + DO_COMPARE(str1, null0, false, false); + DO_COMPARE(str1, intNeg1, false, false); + DO_COMPARE(str1, int0, false, false); + DO_COMPARE(str1, intPos1, false, false); + DO_COMPARE(str1, uint0, false, false); + DO_COMPARE(str1, uint1, false, false); + DO_COMPARE(str1, realNeg1, false, false); + DO_COMPARE(str1, real0, false, false); + DO_COMPARE(str1, realPos1, false, false); + DO_COMPARE(str1, str0, false, false); + DO_COMPARE(str1, str1, true, false); + DO_COMPARE(str1, boolF, false, true); + DO_COMPARE(str1, boolT, false, true); + DO_COMPARE(str1, array0, false, true); + DO_COMPARE(str1, array1, false, true); + DO_COMPARE(str1, obj0, false, true); + DO_COMPARE(str1, obj1, false, true); + + DO_COMPARE(boolF, null0, false, false); + DO_COMPARE(boolF, intNeg1, false, false); + DO_COMPARE(boolF, int0, false, false); + DO_COMPARE(boolF, intPos1, false, false); + DO_COMPARE(boolF, uint0, false, false); + DO_COMPARE(boolF, uint1, false, false); + DO_COMPARE(boolF, realNeg1, false, false); + DO_COMPARE(boolF, real0, false, false); + DO_COMPARE(boolF, realPos1, false, false); + DO_COMPARE(boolF, str0, false, false); + DO_COMPARE(boolF, str1, false, false); + DO_COMPARE(boolF, boolF, true, false); + DO_COMPARE(boolF, boolT, false, true); + DO_COMPARE(boolF, array0, false, true); + DO_COMPARE(boolF, array1, false, true); + DO_COMPARE(boolF, obj0, false, true); + DO_COMPARE(boolF, obj1, false, true); + + DO_COMPARE(boolT, null0, false, false); + DO_COMPARE(boolT, intNeg1, false, false); + DO_COMPARE(boolT, int0, false, false); + DO_COMPARE(boolT, intPos1, false, false); + DO_COMPARE(boolT, uint0, false, false); + DO_COMPARE(boolT, uint1, false, false); + DO_COMPARE(boolT, realNeg1, false, false); + DO_COMPARE(boolT, real0, false, false); + DO_COMPARE(boolT, realPos1, false, false); + DO_COMPARE(boolT, str0, false, false); + DO_COMPARE(boolT, str1, false, false); + DO_COMPARE(boolT, boolF, false, false); + DO_COMPARE(boolT, boolT, true, false); + DO_COMPARE(boolT, array0, false, true); + DO_COMPARE(boolT, array1, false, true); + DO_COMPARE(boolT, obj0, false, true); + DO_COMPARE(boolT, obj1, false, true); + + DO_COMPARE(array0, null0, false, false); + DO_COMPARE(array0, intNeg1, false, false); + DO_COMPARE(array0, int0, false, false); + DO_COMPARE(array0, intPos1, false, false); + DO_COMPARE(array0, uint0, false, false); + DO_COMPARE(array0, uint1, false, false); + DO_COMPARE(array0, realNeg1, false, false); + DO_COMPARE(array0, real0, false, false); + DO_COMPARE(array0, realPos1, false, false); + DO_COMPARE(array0, str0, false, false); + DO_COMPARE(array0, str1, false, false); + DO_COMPARE(array0, boolF, false, false); + DO_COMPARE(array0, boolT, false, false); + DO_COMPARE(array0, array0, true, false); + DO_COMPARE(array0, array1, false, true); + DO_COMPARE(array0, obj0, false, true); + DO_COMPARE(array0, obj1, false, true); + + DO_COMPARE(array1, null0, false, false); + DO_COMPARE(array1, intNeg1, false, false); + DO_COMPARE(array1, int0, false, false); + DO_COMPARE(array1, intPos1, false, false); + DO_COMPARE(array1, uint0, false, false); + DO_COMPARE(array1, uint1, false, false); + DO_COMPARE(array1, realNeg1, false, false); + DO_COMPARE(array1, real0, false, false); + DO_COMPARE(array1, realPos1, false, false); + DO_COMPARE(array1, str0, false, false); + DO_COMPARE(array1, str1, false, false); + DO_COMPARE(array1, boolF, false, false); + DO_COMPARE(array1, boolT, false, false); + DO_COMPARE(array1, array0, false, false); + DO_COMPARE(array1, array1, true, false); + DO_COMPARE(array1, obj0, false, true); + DO_COMPARE(array1, obj1, false, true); + + DO_COMPARE(obj0, null0, false, false); + DO_COMPARE(obj0, intNeg1, false, false); + DO_COMPARE(obj0, int0, false, false); + DO_COMPARE(obj0, intPos1, false, false); + DO_COMPARE(obj0, uint0, false, false); + DO_COMPARE(obj0, uint1, false, false); + DO_COMPARE(obj0, realNeg1, false, false); + DO_COMPARE(obj0, real0, false, false); + DO_COMPARE(obj0, realPos1, false, false); + DO_COMPARE(obj0, str0, false, false); + DO_COMPARE(obj0, str1, false, false); + DO_COMPARE(obj0, boolF, false, false); + DO_COMPARE(obj0, boolT, false, false); + DO_COMPARE(obj0, array0, false, false); + DO_COMPARE(obj0, array1, false, false); + DO_COMPARE(obj0, obj0, true, false); + DO_COMPARE(obj0, obj1, false, true); + + DO_COMPARE(obj1, null0, false, false); + DO_COMPARE(obj1, intNeg1, false, false); + DO_COMPARE(obj1, int0, false, false); + DO_COMPARE(obj1, intPos1, false, false); + DO_COMPARE(obj1, uint0, false, false); + DO_COMPARE(obj1, uint1, false, false); + DO_COMPARE(obj1, realNeg1, false, false); + DO_COMPARE(obj1, real0, false, false); + DO_COMPARE(obj1, realPos1, false, false); + DO_COMPARE(obj1, str0, false, false); + DO_COMPARE(obj1, str1, false, false); + DO_COMPARE(obj1, boolF, false, false); + DO_COMPARE(obj1, boolT, false, false); + DO_COMPARE(obj1, array0, false, false); + DO_COMPARE(obj1, array1, false, false); + DO_COMPARE(obj1, obj0, false, false); + DO_COMPARE(obj1, obj1, true, false); +#undef DO_COMPARE +#pragma pop_macro("DO_COMPARE") +} + +TEST_CASE("bool") +{ + CHECK(!Json::Value()); + + CHECK(!Json::Value("")); + + CHECK(bool(Json::Value("empty"))); + CHECK(bool(Json::Value(false))); + CHECK(bool(Json::Value(true))); + CHECK(bool(Json::Value(0))); + CHECK(bool(Json::Value(1))); + + Json::Value array(Json::arrayValue); + CHECK(!array); + array.append(0); + CHECK(bool(array)); + + Json::Value object(Json::objectValue); + CHECK(!object); + object[""] = false; + CHECK(bool(object)); +} + +TEST_CASE("bad json") +{ + char const* s(R"({"method":"ledger","params":[{"ledger_index":1e300}]})"); + + Json::Value j; + Json::Reader r; + + CHECK(r.parse(s, j)); +} + +TEST_CASE("edge cases") +{ + std::string json; + + std::uint32_t max_uint = std::numeric_limits::max(); + std::int32_t max_int = std::numeric_limits::max(); + std::int32_t min_int = std::numeric_limits::min(); + + std::uint32_t a_uint = max_uint - 1978; + std::int32_t a_large_int = max_int - 1978; + std::int32_t a_small_int = min_int + 1978; + + json = "{\"max_uint\":" + std::to_string(max_uint); + json += ",\"max_int\":" + std::to_string(max_int); + json += ",\"min_int\":" + std::to_string(min_int); + json += ",\"a_uint\":" + std::to_string(a_uint); + json += ",\"a_large_int\":" + std::to_string(a_large_int); + json += ",\"a_small_int\":" + std::to_string(a_small_int); + json += "}"; + + Json::Value j1; + Json::Reader r1; + + CHECK(r1.parse(json, j1)); + CHECK(j1["max_uint"].asUInt() == max_uint); + CHECK(j1["max_int"].asInt() == max_int); + CHECK(j1["min_int"].asInt() == min_int); + CHECK(j1["a_uint"].asUInt() == a_uint); + CHECK(j1["a_uint"] > a_large_int); + CHECK(j1["a_uint"] > a_small_int); + CHECK(j1["a_large_int"].asInt() == a_large_int); + CHECK(j1["a_large_int"].asUInt() == a_large_int); + CHECK(j1["a_large_int"] < a_uint); + CHECK(j1["a_small_int"].asInt() == a_small_int); + CHECK(j1["a_small_int"] < a_uint); + + json = "{\"overflow\":"; + json += std::to_string(std::uint64_t(max_uint) + 1); + json += "}"; + + Json::Value j2; + Json::Reader r2; + + CHECK(!r2.parse(json, j2)); + + json = "{\"underflow\":"; + json += std::to_string(std::int64_t(min_int) - 1); + json += "}"; + + Json::Value j3; + Json::Reader r3; + + CHECK(!r3.parse(json, j3)); + + Json::Value intString{"4294967296"}; + CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + + intString = "4294967295"; + CHECK(intString.asUInt() == 4294967295u); + + intString = "0"; + CHECK(intString.asUInt() == 0); + + intString = "-1"; + CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + + intString = "2147483648"; + CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); + + intString = "2147483647"; + CHECK(intString.asInt() == 2147483647); + + intString = "-2147483648"; + CHECK(intString.asInt() == -2147483648LL); // MSVC wants the LL + + intString = "-2147483649"; + CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); +} + +TEST_CASE("copy") +{ + Json::Value v1{2.5}; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + + Json::Value v2 = v1; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + CHECK(v2.isDouble()); + CHECK(v2.asDouble() == 2.5); + CHECK(v1 == v2); + + v1 = v2; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + CHECK(v2.isDouble()); + CHECK(v2.asDouble() == 2.5); + CHECK(v1 == v2); +} + +TEST_CASE("move") +{ + Json::Value v1{2.5}; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + + Json::Value v2 = std::move(v1); + CHECK(!v1); + CHECK(v2.isDouble()); + CHECK(v2.asDouble() == 2.5); + CHECK(v1 != v2); + + v1 = std::move(v2); + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + CHECK(!v2); + CHECK(v1 != v2); +} + +TEST_CASE("comparisons") +{ + Json::Value a, b; + auto testEquals = [&](std::string const& name) { + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + + CHECK(!(a != b)); + CHECK(!(a < b)); + CHECK(!(a > b)); + + CHECK(b == a); + CHECK(b <= a); + CHECK(b >= a); + + CHECK(!(b != a)); + CHECK(!(b < a)); + CHECK(!(b > a)); + }; + + auto testGreaterThan = [&](std::string const& name) { + CHECK(!(a == b)); + CHECK(!(a <= b)); + CHECK(a >= b); + + CHECK(a != b); + CHECK(!(a < b)); + CHECK(a > b); + + CHECK(!(b == a)); + CHECK(b <= a); + CHECK(!(b >= a)); + + CHECK(b != a); + CHECK(b < a); + CHECK(!(b > a)); + }; + + a["a"] = Json::UInt(0); + b["a"] = Json::Int(0); + testEquals("zero"); + + b["a"] = Json::Int(-1); + testGreaterThan("negative"); + + Json::Int big = std::numeric_limits::max(); + Json::UInt bigger = big; + bigger++; + + a["a"] = bigger; + b["a"] = big; + testGreaterThan("big"); +} + +TEST_CASE("compact") +{ + Json::Value j; + Json::Reader r; + char const* s("{\"array\":[{\"12\":23},{},null,false,0.5]}"); + + auto countLines = [](std::string const& str) { + return 1 + std::count_if(str.begin(), str.end(), [](char c) { + return c == '\n'; + }); + }; + + CHECK(r.parse(s, j)); + { + std::stringstream ss; + ss << j; + CHECK(countLines(ss.str()) > 1); + } + { + std::stringstream ss; + ss << Json::Compact(std::move(j)); + CHECK(countLines(ss.str()) == 1); + } +} + +TEST_CASE("conversions") +{ + // We have Json::Int, but not Json::Double or Json::Real. + // We have Json::Int, Json::Value::Int, and Json::ValueType::intValue. + // We have Json::ValueType::realValue but Json::Value::asDouble. + // TODO: What's the thinking here? + { + // null + Json::Value val; + CHECK(val.isNull()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == ""); + CHECK(val.asInt() == 0); + CHECK(val.asUInt() == 0); + CHECK(val.asDouble() == 0.0); + CHECK(val.asBool() == false); + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(val.isConvertibleTo(Json::arrayValue)); + CHECK(val.isConvertibleTo(Json::objectValue)); + } + { + // int + Json::Value val = -1234; + CHECK(val.isInt()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "-1234"); + CHECK(val.asInt() == -1234); + CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK(val.asDouble() == -1234.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // uint + Json::Value val = 1234U; + CHECK(val.isUInt()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "1234"); + CHECK(val.asInt() == 1234); + CHECK(val.asUInt() == 1234u); + CHECK(val.asDouble() == 1234.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // real + Json::Value val = 2.0; + CHECK(val.isDouble()); + // val.asCString() should trigger an assertion failure + CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$"))); + CHECK(val.asInt() == 2); + CHECK(val.asUInt() == 2u); + CHECK(val.asDouble() == 2.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // numeric string + Json::Value val = "54321"; + CHECK(val.isString()); + CHECK(strcmp(val.asCString(), "54321") == 0); + CHECK(val.asString() == "54321"); + CHECK(val.asInt() == 54321); + CHECK(val.asUInt() == 54321u); + CHECK_THROWS_AS(val.asDouble(), Json::error); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // non-numeric string + Json::Value val(Json::stringValue); + CHECK(val.isString()); + CHECK(val.asCString() == nullptr); + CHECK(val.asString() == ""); + CHECK_THROWS_AS(val.asInt(), std::exception); + CHECK_THROWS_AS(val.asUInt(), std::exception); + CHECK_THROWS_AS(val.asDouble(), std::exception); + CHECK(val.asBool() == false); + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // bool false + Json::Value val = false; + CHECK(val.isBool()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "false"); + CHECK(val.asInt() == 0); + CHECK(val.asUInt() == 0); + CHECK(val.asDouble() == 0.0); + CHECK(val.asBool() == false); + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // bool true + Json::Value val = true; + CHECK(val.isBool()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "true"); + CHECK(val.asInt() == 1); + CHECK(val.asUInt() == 1); + CHECK(val.asDouble() == 1.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // array type + Json::Value val(Json::arrayValue); + CHECK(val.isArray()); + // val.asCString should trigger an assertion failure + CHECK_THROWS_AS(val.asString(), Json::error); + CHECK_THROWS_AS(val.asInt(), Json::error); + CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK_THROWS_AS(val.asDouble(), Json::error); + CHECK(val.asBool() == false); // empty or not + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(!val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // object type + Json::Value val(Json::objectValue); + CHECK(val.isObject()); + // val.asCString should trigger an assertion failure + CHECK_THROWS_AS(val.asString(), Json::error); + CHECK_THROWS_AS(val.asInt(), Json::error); + CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK_THROWS_AS(val.asDouble(), Json::error); + CHECK(val.asBool() == false); // empty or not + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(!val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(val.isConvertibleTo(Json::objectValue)); + } +} + +TEST_CASE("access members") +{ + Json::Value val; + CHECK(val.type() == Json::nullValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + { + Json::Value const constVal = val; + CHECK(constVal[7u].type() == Json::nullValue); + CHECK(!constVal.isMember("key")); + CHECK(constVal["key"].type() == Json::nullValue); + CHECK(constVal.getMemberNames().empty()); + CHECK(constVal.get(1u, "default0") == "default0"); + CHECK(constVal.get(std::string("not"), "oh") == "oh"); + CHECK(constVal.get("missing", "default2") == "default2"); + } + + val = -7; + CHECK(val.type() == Json::intValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = 42u; + CHECK(val.type() == Json::uintValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = 3.14159; + CHECK(val.type() == Json::realValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = true; + CHECK(val.type() == Json::booleanValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = "string"; + CHECK(val.type() == Json::stringValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = Json::Value(Json::objectValue); + CHECK(val.type() == Json::objectValue); + CHECK(val.size() == 0); + static Json::StaticString const staticThree("three"); + val[staticThree] = 3; + val["two"] = 2; + CHECK(val.size() == 2); + CHECK(val.isValidIndex(1)); + CHECK(!val.isValidIndex(2)); + CHECK(val[staticThree] == 3); + CHECK(val.isMember("two")); + CHECK(val.isMember(staticThree)); + CHECK(!val.isMember("key")); + { + Json::Value const constVal = val; + CHECK(constVal["two"] == 2); + CHECK(constVal["four"].type() == Json::nullValue); + CHECK(constVal[staticThree] == 3); + CHECK(constVal.isMember("two")); + CHECK(constVal.isMember(staticThree)); + CHECK(!constVal.isMember("key")); + CHECK(val.get(std::string("two"), "backup") == 2); + CHECK(val.get("missing", "default2") == "default2"); + } + + val = Json::Value(Json::arrayValue); + CHECK(val.type() == Json::arrayValue); + CHECK(val.size() == 0); + val[0u] = "zero"; + val[1u] = "one"; + CHECK(val.size() == 2); + CHECK(val.isValidIndex(1)); + CHECK(!val.isValidIndex(2)); + CHECK(val[20u].type() == Json::nullValue); + CHECK(!val.isMember("key")); + { + Json::Value const constVal = val; + CHECK(constVal[0u] == "zero"); + CHECK(constVal[2u].type() == Json::nullValue); + CHECK(!constVal.isMember("key")); + CHECK(val.get(1u, "default0") == "one"); + CHECK(val.get(3u, "default1") == "default1"); + } +} + +TEST_CASE("remove members") +{ + Json::Value val; + CHECK(val.removeMember(std::string("member")).type() == Json::nullValue); + + val = Json::Value(Json::objectValue); + static Json::StaticString const staticThree("three"); + val[staticThree] = 3; + val["two"] = 2; + CHECK(val.size() == 2); + + CHECK(val.removeMember(std::string("six")).type() == Json::nullValue); + CHECK(val.size() == 2); + + CHECK(val.removeMember(staticThree) == 3); + CHECK(val.size() == 1); + + CHECK(val.removeMember(staticThree).type() == Json::nullValue); + CHECK(val.size() == 1); + + CHECK(val.removeMember(std::string("two")) == 2); + CHECK(val.size() == 0); + + CHECK(val.removeMember(std::string("two")).type() == Json::nullValue); + CHECK(val.size() == 0); +} + +TEST_CASE("iterator") +{ + { + // Iterating an array. + Json::Value arr{Json::arrayValue}; + arr[0u] = "zero"; + arr[1u] = "one"; + arr[2u] = "two"; + arr[3u] = "three"; + + Json::ValueIterator const b{arr.begin()}; + Json::ValueIterator const e{arr.end()}; + + Json::ValueIterator i1 = b; + Json::ValueIterator i2 = e; + --i2; + + // key(), index(), and memberName() on an object iterator. + CHECK(b != e); + CHECK(!(b == e)); + CHECK(i1.key() == 0); + CHECK(i2.key() == 3); + CHECK(i1.index() == 0); + CHECK(i2.index() == 3); + CHECK(std::strcmp(i1.memberName(), "") == 0); + CHECK(std::strcmp(i2.memberName(), "") == 0); + + // Pre and post increment and decrement. + *i1++ = "0"; + CHECK(*i1 == "one"); + *i1 = "1"; + ++i1; + + *i2-- = "3"; + CHECK(*i2 == "two"); + CHECK(i1 == i2); + *i2 = "2"; + CHECK(*i1 == "2"); + } + { + // Iterating a const object. + Json::Value const obj{[]() { + Json::Value obj{Json::objectValue}; + obj["0"] = 0; + obj["1"] = 1; + obj["2"] = 2; + obj["3"] = 3; + return obj; + }()}; + + Json::ValueConstIterator i1{obj.begin()}; + Json::ValueConstIterator i2{obj.end()}; + --i2; + + // key(), index(), and memberName() on an object iterator. + CHECK(i1 != i2); + CHECK(!(i1 == i2)); + CHECK(i1.key() == "0"); + CHECK(i2.key() == "3"); + CHECK(i1.index() == -1); + CHECK(i2.index() == -1); + CHECK(std::strcmp(i1.memberName(), "0") == 0); + CHECK(std::strcmp(i2.memberName(), "3") == 0); + + // Pre and post increment and decrement. + CHECK(*i1++ == 0); + CHECK(*i1 == 1); + ++i1; + + CHECK(*i2-- == 3); + CHECK(*i2 == 2); + CHECK(i1 == i2); + CHECK(*i1 == 2); + } + { + // Iterating a non-const null object. + Json::Value nul{}; + CHECK(nul.begin() == nul.end()); + } + { + // Iterating a const Int. + Json::Value const i{-3}; + CHECK(i.begin() == i.end()); + } +} + +TEST_CASE("nest limits") +{ + Json::Reader r; + { + auto nest = [](std::uint32_t depth) -> std::string { + std::string s = "{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "\"obj\":{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "}"; + s += "}"; + return s; + }; + + { + // Within object nest limit + auto json{nest(std::min(10u, Json::Reader::nest_limit))}; + Json::Value j; + CHECK(r.parse(json, j)); + } + + { + // Exceed object nest limit + auto json{nest(Json::Reader::nest_limit + 1)}; + Json::Value j; + CHECK(!r.parse(json, j)); + } + } + + auto nest = [](std::uint32_t depth) -> std::string { + std::string s = "{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "\"array\":[{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "]}"; + s += "}"; + return s; + }; + { + // Exceed array nest limit + auto json{nest(Json::Reader::nest_limit + 1)}; + Json::Value j; + CHECK(!r.parse(json, j)); + } +} + +TEST_CASE("memory leak") +{ + // When run with the address sanitizer, this test confirms there is no + // memory leak with the scenarios below. + { + Json::Value a; + a[0u] = 1; + CHECK(a.type() == Json::arrayValue); + CHECK(a[0u].type() == Json::intValue); + a = std::move(a[0u]); + CHECK(a.type() == Json::intValue); + } + { + Json::Value b; + Json::Value temp; + temp["a"] = "Probably avoids the small string optimization"; + temp["b"] = "Also probably avoids the small string optimization"; + CHECK(temp.type() == Json::objectValue); + b.append(temp); + CHECK(temp.type() == Json::objectValue); + CHECK(b.size() == 1); + + b.append(std::move(temp)); + CHECK(b.size() == 2); + + // Note that the type() == nullValue check is implementation + // specific and not guaranteed to be valid in the future. + CHECK(temp.type() == Json::nullValue); + } +} + +TEST_SUITE_END(); + +} // namespace ripple diff --git a/src/tests/libxrpl/json/Writer.cpp b/src/tests/libxrpl/json/Writer.cpp new file mode 100644 index 0000000000..59251aaf60 --- /dev/null +++ b/src/tests/libxrpl/json/Writer.cpp @@ -0,0 +1,192 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include +#include + +#include +#include + +using namespace ripple; +using namespace Json; + +TEST_SUITE_BEGIN("JsonWriter"); + +struct WriterFixture +{ + std::string output; + std::unique_ptr writer; + + WriterFixture() + { + writer = std::make_unique(stringOutput(output)); + } + + void + reset() + { + output.clear(); + writer = std::make_unique(stringOutput(output)); + } + + void + expectOutput(std::string const& expected) const + { + CHECK(output == expected); + } + + void + checkOutputAndReset(std::string const& expected) + { + expectOutput(expected); + reset(); + } +}; + +TEST_CASE_FIXTURE(WriterFixture, "trivial") +{ + CHECK(output.empty()); + checkOutputAndReset(""); +} + +TEST_CASE_FIXTURE(WriterFixture, "near trivial") +{ + CHECK(output.empty()); + writer->output(0); + checkOutputAndReset("0"); +} + +TEST_CASE_FIXTURE(WriterFixture, "primitives") +{ + writer->output(true); + checkOutputAndReset("true"); + + writer->output(false); + checkOutputAndReset("false"); + + writer->output(23); + checkOutputAndReset("23"); + + writer->output(23.0); + checkOutputAndReset("23.0"); + + writer->output(23.5); + checkOutputAndReset("23.5"); + + writer->output("a string"); + checkOutputAndReset(R"("a string")"); + + writer->output(nullptr); + checkOutputAndReset("null"); +} + +TEST_CASE_FIXTURE(WriterFixture, "empty") +{ + writer->startRoot(Writer::array); + writer->finish(); + checkOutputAndReset("[]"); + + writer->startRoot(Writer::object); + writer->finish(); + checkOutputAndReset("{}"); +} + +TEST_CASE_FIXTURE(WriterFixture, "escaping") +{ + writer->output("\\"); + checkOutputAndReset(R"("\\")"); + + writer->output("\""); + checkOutputAndReset(R"("\"")"); + + writer->output("\\\""); + checkOutputAndReset(R"("\\\"")"); + + writer->output("this contains a \\ in the middle of it."); + checkOutputAndReset(R"("this contains a \\ in the middle of it.")"); + + writer->output("\b\f\n\r\t"); + checkOutputAndReset(R"("\b\f\n\r\t")"); +} + +TEST_CASE_FIXTURE(WriterFixture, "array") +{ + writer->startRoot(Writer::array); + writer->append(12); + writer->finish(); + checkOutputAndReset("[12]"); +} + +TEST_CASE_FIXTURE(WriterFixture, "long array") +{ + writer->startRoot(Writer::array); + writer->append(12); + writer->append(true); + writer->append("hello"); + writer->finish(); + checkOutputAndReset(R"([12,true,"hello"])"); +} + +TEST_CASE_FIXTURE(WriterFixture, "embedded array simple") +{ + writer->startRoot(Writer::array); + writer->startAppend(Writer::array); + writer->finish(); + writer->finish(); + checkOutputAndReset("[[]]"); +} + +TEST_CASE_FIXTURE(WriterFixture, "object") +{ + writer->startRoot(Writer::object); + writer->set("hello", "world"); + writer->finish(); + checkOutputAndReset(R"({"hello":"world"})"); +} + +TEST_CASE_FIXTURE(WriterFixture, "complex object") +{ + writer->startRoot(Writer::object); + writer->set("hello", "world"); + writer->startSet(Writer::array, "array"); + writer->append(true); + writer->append(12); + writer->startAppend(Writer::array); + writer->startAppend(Writer::object); + writer->set("goodbye", "cruel world."); + writer->startSet(Writer::array, "subarray"); + writer->append(23.5); + writer->finishAll(); + checkOutputAndReset( + R"({"hello":"world","array":[true,12,[{"goodbye":"cruel world.","subarray":[23.5]}]]})"); +} + +TEST_CASE_FIXTURE(WriterFixture, "json value") +{ + Json::Value value(Json::objectValue); + value["foo"] = 23; + writer->startRoot(Writer::object); + writer->set("hello", value); + writer->finish(); + checkOutputAndReset(R"({"hello":{"foo":23}})"); +} + +TEST_SUITE_END(); diff --git a/src/tests/libxrpl/json/main.cpp b/src/tests/libxrpl/json/main.cpp new file mode 100644 index 0000000000..0a3f254ea8 --- /dev/null +++ b/src/tests/libxrpl/json/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include From 91fa6b229599b76b777fdd67ae5c33330da649e1 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 28 Oct 2025 14:26:25 +0000 Subject: [PATCH 05/11] ci: Only run .exe files during test phase on Windows (#5947) --- .github/workflows/reusable-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index cfd0fe6849..4b9c75334a 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -74,9 +74,11 @@ jobs: - name: Run the separate tests if: ${{ inputs.run_tests }} + env: + EXT: ${{ runner.os == 'Windows' && '.exe' || '' }} shell: bash run: | - for test_file in ./doctest/*; do + for test_file in ./doctest/*${EXT}; do echo "Executing $test_file" chmod +x "$test_file" if [[ "${{ runner.os }}" == "Windows" && "$test_file" == "./doctest/xrpl.test.net.exe" ]]; then From d9960d5ba0f65d60d5963e63a6848ba1150f11da Mon Sep 17 00:00:00 2001 From: Pratik Mankawde Date: Tue, 28 Oct 2025 15:10:38 +0000 Subject: [PATCH 06/11] refactor: Retire fix1543 amendment (#5926) Amendments activated for more than 2 years can be retired. This change retires the fix1543 amendment. Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- include/xrpl/protocol/detail/features.macro | 2 +- src/xrpld/app/tx/detail/Escrow.cpp | 21 --------------------- src/xrpld/app/tx/detail/Escrow.h | 9 --------- src/xrpld/app/tx/detail/PayChan.cpp | 19 ++----------------- src/xrpld/app/tx/detail/PayChan.h | 6 ------ 5 files changed, 3 insertions(+), 54 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 6052814357..3e3309b444 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -111,7 +111,6 @@ XRPL_FEATURE(MultiSignReserve, Supported::yes, VoteBehavior::DefaultYe XRPL_FIX (1578, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DepositPreauth, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (1623, Supported::yes, VoteBehavior::DefaultYes) -XRPL_FIX (1543, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (1571, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYes) @@ -155,3 +154,4 @@ XRPL_RETIRE(fix1528) XRPL_RETIRE(FlowCross) XRPL_RETIRE(fix1513) XRPL_RETIRE(fix1515) +XRPL_RETIRE(fix1543) diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 42b7c8e458..a5c44b1e2e 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -118,13 +118,6 @@ escrowCreatePreflightHelper(PreflightContext const& ctx) return tesSUCCESS; } -std::uint32_t -EscrowCreate::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { @@ -639,13 +632,6 @@ EscrowFinish::checkExtraFeatures(PreflightContext const& ctx) ctx.rules.enabled(featureCredentials); } -std::uint32_t -EscrowFinish::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC EscrowFinish::preflight(PreflightContext const& ctx) { @@ -1225,13 +1211,6 @@ EscrowFinish::doApply() //------------------------------------------------------------------------------ -std::uint32_t -EscrowCancel::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC EscrowCancel::preflight(PreflightContext const& ctx) { diff --git a/src/xrpld/app/tx/detail/Escrow.h b/src/xrpld/app/tx/detail/Escrow.h index 8956be2939..55816734eb 100644 --- a/src/xrpld/app/tx/detail/Escrow.h +++ b/src/xrpld/app/tx/detail/Escrow.h @@ -36,9 +36,6 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); @@ -63,9 +60,6 @@ public: static bool checkExtraFeatures(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); @@ -93,9 +87,6 @@ public: { } - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index b495e00b3f..b2353e3078 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -175,13 +175,6 @@ PayChanCreate::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; } -std::uint32_t -PayChanCreate::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC PayChanCreate::preflight(PreflightContext const& ctx) { @@ -335,13 +328,6 @@ PayChanFund::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; } -std::uint32_t -PayChanFund::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC PayChanFund::preflight(PreflightContext const& ctx) { @@ -434,10 +420,9 @@ PayChanClaim::checkExtraFeatures(PreflightContext const& ctx) } std::uint32_t -PayChanClaim::getFlagsMask(PreflightContext const& ctx) +PayChanClaim::getFlagsMask(PreflightContext const&) { - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfPayChanClaimMask : 0; + return tfPayChanClaimMask; } NotTEC diff --git a/src/xrpld/app/tx/detail/PayChan.h b/src/xrpld/app/tx/detail/PayChan.h index b25a4529be..0042f5c7c9 100644 --- a/src/xrpld/app/tx/detail/PayChan.h +++ b/src/xrpld/app/tx/detail/PayChan.h @@ -36,9 +36,6 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); @@ -65,9 +62,6 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); From 7d5ed0cd8d9e82e0bd741a87836878e5f7d678fd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:42:43 +0000 Subject: [PATCH 07/11] fix: account_tx limit parameter validation for malformed values (#5891) This change fixes the `account_tx` RPC method to properly validate malformed limit parameter values. Previously, invalid values like `0`, `1.2`, `"10"`, `true`, `false`, `-1`, `[]`, `{}`, etc. were either accepted without errors or caused internal errors. Now all malformed values correctly return the `invalidParams` error. Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- src/test/rpc/AccountOffers_test.cpp | 19 +-- src/test/rpc/AccountTx_test.cpp | 145 ++++++++++++++++++--- src/test/rpc/Book_test.cpp | 19 ++- src/xrpld/rpc/detail/RPCHelpers.cpp | 22 ++-- src/xrpld/rpc/detail/Tuning.h | 3 + src/xrpld/rpc/handlers/AccountChannels.cpp | 3 - src/xrpld/rpc/handlers/AccountLines.cpp | 3 - src/xrpld/rpc/handlers/AccountOffers.cpp | 3 - src/xrpld/rpc/handlers/AccountTx.cpp | 7 +- 9 files changed, 166 insertions(+), 58 deletions(-) diff --git a/src/test/rpc/AccountOffers_test.cpp b/src/test/rpc/AccountOffers_test.cpp index 2aa85ac9b9..59a6942ed2 100644 --- a/src/test/rpc/AccountOffers_test.cpp +++ b/src/test/rpc/AccountOffers_test.cpp @@ -190,12 +190,6 @@ public: } { - // now make a limit (= 0) query for the same data - // since we operate on the admin port, the limit - // value of 0 is not adjusted into tuned ranges for admin requests - // so we literally get 0 elements in that case. For non-admin - // requests, we get limit defaults applied thus all our results - // come back (we are below the min results limit) Json::Value jvParams; jvParams[jss::account] = bob.human(); jvParams[jss::limit] = 0u; @@ -203,18 +197,7 @@ public: "json", "account_offers", jvParams.toStyledString())[jss::result]; - auto const& jro = jrr[jss::offers]; - if (asAdmin) - { - // limit == 0 is invalid - BEAST_EXPECT(jrr.isMember(jss::error_message)); - } - else - { - // Call should enforce min limit of 10 - BEAST_EXPECT(checkArraySize(jro, 3u)); - BEAST_EXPECT(!jrr.isMember(jss::marker)); - } + BEAST_EXPECT(jrr.isMember(jss::error_message)); } } diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 82809b5c5b..cbe7cc8f4a 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -193,26 +193,26 @@ class AccountTx_test : public beast::unit_test::suite j[jss::result][jss::error] == RPC::get_error_info(code).token; }; - Json::Value jParms; - jParms[jss::api_version] = apiVersion; + Json::Value jParams; + jParams[jss::api_version] = apiVersion; BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(jParms)), + env.rpc("json", "account_tx", to_string(jParams)), rpcINVALID_PARAMS)); - jParms[jss::account] = "0xDEADBEEF"; + jParams[jss::account] = "0xDEADBEEF"; BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(jParms)), + env.rpc("json", "account_tx", to_string(jParams)), rpcACT_MALFORMED)); - jParms[jss::account] = A1.human(); + jParams[jss::account] = A1.human(); BEAST_EXPECT(hasTxs( - env.rpc(apiVersion, "json", "account_tx", to_string(jParms)))); + env.rpc(apiVersion, "json", "account_tx", to_string(jParams)))); // Ledger min/max index { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_min] = -1; p[jss::ledger_index_max] = -1; BEAST_EXPECT(hasTxs( @@ -247,7 +247,7 @@ class AccountTx_test : public beast::unit_test::suite } // Ledger index min only { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_min] = -1; BEAST_EXPECT(hasTxs( env.rpc(apiVersion, "json", "account_tx", to_string(p)))); @@ -270,7 +270,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger index max only { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_max] = -1; BEAST_EXPECT(hasTxs( env.rpc(apiVersion, "json", "account_tx", to_string(p)))); @@ -298,7 +298,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger Sequence { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index] = env.closed()->info().seq; BEAST_EXPECT(hasTxs( @@ -319,7 +319,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger Hash { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_hash] = to_string(env.closed()->info().hash); BEAST_EXPECT(hasTxs( @@ -332,9 +332,9 @@ class AccountTx_test : public beast::unit_test::suite // Ledger index max/min/index all specified // ERRORS out with invalid Parenthesis { - jParms[jss::account] = "0xDEADBEEF"; - jParms[jss::account] = A1.human(); - Json::Value p{jParms}; + jParams[jss::account] = "0xDEADBEEF"; + jParams[jss::account] = A1.human(); + Json::Value p{jParams}; p[jss::ledger_index_max] = -1; p[jss::ledger_index_min] = -1; @@ -351,7 +351,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger index max only { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_max] = env.current()->info().seq; if (apiVersion < 2u) BEAST_EXPECT(hasTxs( @@ -382,7 +382,7 @@ class AccountTx_test : public beast::unit_test::suite } // test binary and forward for bool/non bool values { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::binary] = "asdf"; if (apiVersion < 2u) { @@ -410,6 +410,117 @@ class AccountTx_test : public beast::unit_test::suite result = env.rpc("json", "account_tx", to_string(p)); BEAST_EXPECT(result[jss::result][jss::status] == "success"); } + // test limit with malformed values + { + Json::Value p{jParams}; + + // Test case: limit = 0 should fail (below minimum) + p[jss::limit] = 0; + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + rpcINVALID_PARAMS)); + + // Test case: limit = 1.2 should fail (not an integer) + p[jss::limit] = 1.2; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = "10" should fail (string instead of integer) + p[jss::limit] = "10"; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = true should fail (boolean instead of integer) + p[jss::limit] = true; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = false should fail (boolean instead of integer) + p[jss::limit] = false; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = -1 should fail (negative number) + p[jss::limit] = -1; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = [] should fail (array instead of integer) + p[jss::limit] = Json::Value(Json::arrayValue); + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = {} should fail (object instead of integer) + p[jss::limit] = Json::Value(Json::objectValue); + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = "malformed" should fail (malformed string) + p[jss::limit] = "malformed"; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = ["limit"] should fail (array with string) + p[jss::limit] = Json::Value(Json::arrayValue); + p[jss::limit].append("limit"); + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = {"limit": 10} should fail (object with + // property) + p[jss::limit] = Json::Value(Json::objectValue); + p[jss::limit][jss::limit] = 10; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = 10 should succeed (valid integer) + p[jss::limit] = 10; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::status] == "success"); + } } void diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 7177ab847c..0d5366d3ef 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1633,6 +1633,20 @@ public: "Invalid field 'limit', not unsigned integer."); } + { + Json::Value jvParams; + jvParams[jss::ledger_index] = "validated"; + jvParams[jss::taker] = env.master.human(); + jvParams[jss::limit] = 0; // must be > 0 + jvParams[jss::taker_pays][jss::currency] = "XRP"; + jvParams[jss::taker_gets][jss::currency] = "USD"; + jvParams[jss::taker_gets][jss::issuer] = gw.human(); + auto const jrr = env.rpc( + "json", "book_offers", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::error] == "invalidParams"); + BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit'."); + } + { Json::Value jvParams; jvParams[jss::ledger_index] = "validated"; @@ -1710,11 +1724,6 @@ public: BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? 1u : 0u)); // NOTE - a marker field is not returned for this method - jvParams[jss::limit] = 0u; - jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::offers].isArray()); - BEAST_EXPECT(jrr[jss::offers].size() == 0u); - jvParams[jss::limit] = RPC::Tuning::bookOffers.rmax + 1; jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result]; BEAST_EXPECT(jrr[jss::offers].isArray()); diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index edb91611be..9c49751bcd 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -704,15 +704,21 @@ readLimitField( JsonContext const& context) { limit = range.rdefault; - if (auto const& jvLimit = context.params[jss::limit]) - { - if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0))) - return RPC::expected_field_error(jss::limit, "unsigned integer"); + if (!context.params.isMember(jss::limit) || + context.params[jss::limit].isNull()) + return std::nullopt; + + auto const& jvLimit = context.params[jss::limit]; + if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0))) + return RPC::expected_field_error(jss::limit, "unsigned integer"); + + limit = jvLimit.asUInt(); + if (limit == 0) + return RPC::invalid_field_error(jss::limit); + + if (!isUnlimited(context.role)) + limit = std::max(range.rmin, std::min(range.rmax, limit)); - limit = jvLimit.asUInt(); - if (!isUnlimited(context.role)) - limit = std::max(range.rmin, std::min(range.rmax, limit)); - } return std::nullopt; } diff --git a/src/xrpld/rpc/detail/Tuning.h b/src/xrpld/rpc/detail/Tuning.h index 4f4a8be1bf..f3218a3fb1 100644 --- a/src/xrpld/rpc/detail/Tuning.h +++ b/src/xrpld/rpc/detail/Tuning.h @@ -45,6 +45,9 @@ static LimitRange constexpr accountObjects = {10, 200, 400}; /** Limits for the account_offers command. */ static LimitRange constexpr accountOffers = {10, 200, 400}; +/** Limits for the account_tx command. */ +static LimitRange constexpr accountTx = {10, 200, 400}; + /** Limits for the book_offers command. */ static LimitRange constexpr bookOffers = {0, 60, 100}; diff --git a/src/xrpld/rpc/handlers/AccountChannels.cpp b/src/xrpld/rpc/handlers/AccountChannels.cpp index 17e46f052f..1a21f67860 100644 --- a/src/xrpld/rpc/handlers/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/AccountChannels.cpp @@ -103,9 +103,6 @@ doAccountChannels(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountChannels, context)) return *err; - if (limit == 0u) - return rpcError(rpcINVALID_PARAMS); - Json::Value jsonChannels{Json::arrayValue}; struct VisitData { diff --git a/src/xrpld/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/AccountLines.cpp index 146a9527a9..9fb076445b 100644 --- a/src/xrpld/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/AccountLines.cpp @@ -120,9 +120,6 @@ doAccountLines(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountLines, context)) return *err; - if (limit == 0) - return rpcError(rpcINVALID_PARAMS); - // this flag allows the requester to ask incoming trustlines in default // state be omitted bool ignoreDefault = params.isMember(jss::ignore_default) && diff --git a/src/xrpld/rpc/handlers/AccountOffers.cpp b/src/xrpld/rpc/handlers/AccountOffers.cpp index 1f2b76efe4..737f39cc13 100644 --- a/src/xrpld/rpc/handlers/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/AccountOffers.cpp @@ -86,9 +86,6 @@ doAccountOffers(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountOffers, context)) return *err; - if (limit == 0) - return RPC::invalid_field_error(jss::limit); - Json::Value& jsonOffers(result[jss::offers] = Json::arrayValue); std::vector> offers; uint256 startAfter = beast::zero; diff --git a/src/xrpld/rpc/handlers/AccountTx.cpp b/src/xrpld/rpc/handlers/AccountTx.cpp index e053c2adc0..64247c732e 100644 --- a/src/xrpld/rpc/handlers/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/AccountTx.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -429,7 +431,10 @@ doAccountTxJson(RPC::JsonContext& context) return RPC::invalid_field_error(jss::forward); } - args.limit = params.isMember(jss::limit) ? params[jss::limit].asUInt() : 0; + if (auto const err = + RPC::readLimitField(args.limit, RPC::Tuning::accountTx, context)) + return *err; + args.binary = params.isMember(jss::binary) && params[jss::binary].asBool(); args.forward = params.isMember(jss::forward) && params[jss::forward].asBool(); From 21f3c12d85a34dcc077287b0b42db5a44dac3814 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Tue, 28 Oct 2025 18:12:11 +0000 Subject: [PATCH 08/11] fix: invariant error in fee-sized `VaultWithdraw` (#5876) This changes fixes an invariant error where the amount withdrawn is equal to the transaction fee. Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- src/test/app/Invariants_test.cpp | 153 +++++++++++----- src/test/app/Vault_test.cpp | 194 +++++++++++++++++++++ src/xrpld/app/tx/detail/InvariantCheck.cpp | 79 ++++----- 3 files changed, 338 insertions(+), 88 deletions(-) diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 8781eee72b..925776a1b4 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -1751,7 +1751,7 @@ class Invariants_test : public beast::unit_test::suite AccountID account; int amount; }; - struct Adjustements + struct Adjustments { std::optional assetsTotal = {}; std::optional assetsAvailable = {}; @@ -1764,7 +1764,7 @@ class Invariants_test : public beast::unit_test::suite }; auto constexpr adjust = [&](ApplyView& ac, ripple::Keylet keylet, - Adjustements args) { + Adjustments args) { auto sleVault = ac.peek(keylet); if (!sleVault) return false; @@ -1790,9 +1790,11 @@ class Invariants_test : public beast::unit_test::suite ac.update(sleVault); if (args.sharesTotal) + { (*sleShares)[sfOutstandingAmount] = *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal; - ac.update(sleShares); + ac.update(sleShares); + } auto const assets = *(*sleVault)[sfAsset]; auto const pseudoId = *(*sleVault)[sfAccount]; @@ -1863,17 +1865,17 @@ class Invariants_test : public beast::unit_test::suite }; constexpr auto args = - [](AccountID id, int adjustement, auto fn) -> Adjustements { - Adjustements sample = { - .assetsTotal = adjustement, - .assetsAvailable = adjustement, + [](AccountID id, int adjustment, auto fn) -> Adjustments { + Adjustments sample = { + .assetsTotal = adjustment, + .assetsAvailable = adjustment, .lossUnrealized = 0, - .sharesTotal = adjustement, - .vaultAssets = adjustement, + .sharesTotal = adjustment, + .vaultAssets = adjustment, .accountAssets = // - AccountAmount{id, -adjustement}, + AccountAmount{id, -adjustment}, .accountShares = // - AccountAmount{id, adjustement}}; + AccountAmount{id, adjustment}}; fn(sample); return sample; }; @@ -2285,7 +2287,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsAvailable = (DROPS_PER_XRP * -100).value(); sample.assetsTotal = (DROPS_PER_XRP * -200).value(); sample.sharesTotal = -1; @@ -2354,7 +2356,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.lossUnrealized = 13; sample.assetsTotal = 20; })); @@ -2374,7 +2376,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 100, [&](Adjustements& sample) { + args(A2.id(), 100, [&](Adjustments& sample) { sample.lossUnrealized = 13; })); }, @@ -2395,7 +2397,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsMaximum = 1; })); }, @@ -2412,7 +2414,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsMaximum = -1; })); }, @@ -2461,7 +2463,7 @@ class Invariants_test : public beast::unit_test::suite ac.view().update(sleShares); return adjust( - ac.view(), keylet, args(A2.id(), 10, [](Adjustements&) {})); + ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {})); }, XRPAmount{}, STTx{ttVAULT_DEPOSIT, [](STObject&) {}}, @@ -2474,7 +2476,7 @@ class Invariants_test : public beast::unit_test::suite [&](Account const& A1, Account const& A2, ApplyContext& ac) { auto const keylet = keylet::vault(A1.id(), ac.view().seq()); adjust( - ac.view(), keylet, args(A2.id(), 10, [](Adjustements&) {})); + ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {})); auto sleVault = ac.view().peek(keylet); if (!sleVault) @@ -2850,7 +2852,9 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [](Adjustments& sample) { + sample.vaultAssets.reset(); + })); }, XRPAmount{}, STTx{ttVAULT_DEPOSIT, [](STObject&) {}}, @@ -2864,7 +2868,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 200, [&](Adjustements& sample) { + args(A2.id(), 200, [&](Adjustments& sample) { sample.assetsMaximum = 1; })); }, @@ -2898,7 +2902,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A3.id(), -10, [&](Adjustements& sample) { + args(A3.id(), -10, [&](Adjustments& sample) { sample.accountAssets->amount = -100; })); }, @@ -2931,7 +2935,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.vaultAssets = -20; sample.accountAssets->amount = 10; })); @@ -2959,7 +2963,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.accountAssets->amount = 0; })); }, @@ -2978,8 +2982,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { - sample.accountShares->amount = 0; + args(A2.id(), 10, [&](Adjustments& sample) { + sample.accountShares.reset(); })); }, XRPAmount{}, @@ -2994,10 +2998,11 @@ class Invariants_test : public beast::unit_test::suite {"deposit must change vault shares"}, [&](Account const& A1, Account const& A2, ApplyContext& ac) { auto const keylet = keylet::vault(A1.id(), ac.view().seq()); + return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [](Adjustments& sample) { sample.sharesTotal = 0; })); }, @@ -3019,7 +3024,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.accountShares->amount = -5; sample.sharesTotal = -10; })); @@ -3032,6 +3037,33 @@ class Invariants_test : public beast::unit_test::suite precloseXrp, TxAccount::A2); + doInvariantCheck( + {"deposit and assets outstanding must add up"}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto sleA3 = ac.view().peek(keylet::account(A3.id())); + (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000; + ac.view().update(sleA3); + + auto const keylet = keylet::vault(A1.id(), ac.view().seq()); + return adjust( + ac.view(), + keylet, + args(A2.id(), 10, [&](Adjustments& sample) { + sample.assetsTotal = 11; + })); + }, + XRPAmount{2000}, + STTx{ + ttVAULT_DEPOSIT, + [&](STObject& tx) { + tx[sfAmount] = XRPAmount(10); + tx[sfDelegate] = A3.id(); + tx[sfFee] = XRPAmount(2000); + }}, + {tecINVARIANT_FAILED, tecINVARIANT_FAILED}, + precloseXrp, + TxAccount::A2); + doInvariantCheck( {"deposit and assets outstanding must add up", "deposit and assets available must add up"}, @@ -3040,7 +3072,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.assetsTotal = 7; sample.assetsAvailable = 7; })); @@ -3061,7 +3093,9 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [](Adjustments& sample) { + sample.vaultAssets.reset(); + })); }, XRPAmount{}, STTx{ttVAULT_WITHDRAW, [](STObject&) {}}, @@ -3087,7 +3121,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A3.id(), -10, [&](Adjustements& sample) { + args(A3.id(), -10, [&](Adjustments& sample) { sample.accountAssets->amount = -100; })); }, @@ -3123,7 +3157,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.vaultAssets = 10; sample.accountAssets->amount = -20; })); @@ -3141,7 +3175,7 @@ class Invariants_test : public beast::unit_test::suite if (!adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { *sample.vaultAssets -= 5; }))) return false; @@ -3167,8 +3201,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { - sample.accountShares->amount = 0; + args(A2.id(), -10, [&](Adjustments& sample) { + sample.accountShares.reset(); })); }, XRPAmount{}, @@ -3184,7 +3218,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [](Adjustments& sample) { sample.sharesTotal = 0; })); }, @@ -3203,7 +3237,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.accountShares->amount = 5; sample.sharesTotal = 10; })); @@ -3222,7 +3256,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.assetsTotal = -15; sample.assetsAvailable = -15; })); @@ -3233,6 +3267,33 @@ class Invariants_test : public beast::unit_test::suite precloseXrp, TxAccount::A2); + doInvariantCheck( + {"withdrawal and assets outstanding must add up"}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto sleA3 = ac.view().peek(keylet::account(A3.id())); + (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000; + ac.view().update(sleA3); + + auto const keylet = keylet::vault(A1.id(), ac.view().seq()); + return adjust( + ac.view(), + keylet, + args(A2.id(), -10, [&](Adjustments& sample) { + sample.assetsTotal = -7; + })); + }, + XRPAmount{2000}, + STTx{ + ttVAULT_WITHDRAW, + [&](STObject& tx) { + tx[sfAmount] = XRPAmount(10); + tx[sfDelegate] = A3.id(); + tx[sfFee] = XRPAmount(2000); + }}, + {tecINVARIANT_FAILED, tecINVARIANT_FAILED}, + precloseXrp, + TxAccount::A2); + auto const precloseMpt = [&](Account const& A1, Account const& A2, Env& env) -> bool { env.fund(XRP(1000), A3, A4); @@ -3292,7 +3353,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.accountShares->amount = 5; })); }, @@ -3312,8 +3373,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -1, [&](Adjustements& sample) { - sample.vaultAssets = 0; + args(A2.id(), -1, [&](Adjustments& sample) { + sample.vaultAssets.reset(); })); }, XRPAmount{}, @@ -3331,7 +3392,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [&](Adjustments& sample) {})); }, XRPAmount{}, STTx{ttVAULT_CLAWBACK, [](STObject&) {}}, @@ -3346,7 +3407,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [&](Adjustments& sample) {})); }, XRPAmount{}, STTx{ @@ -3364,7 +3425,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A4.id(), 10, [&](Adjustements& sample) { + args(A4.id(), 10, [&](Adjustments& sample) { sample.sharesTotal = 0; })); }, @@ -3385,8 +3446,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A4.id(), -10, [&](Adjustements& sample) { - sample.accountShares->amount = 0; + args(A4.id(), -10, [&](Adjustments& sample) { + sample.accountShares.reset(); })); }, XRPAmount{}, @@ -3408,7 +3469,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A4.id(), -10, [&](Adjustements& sample) { + args(A4.id(), -10, [&](Adjustments& sample) { sample.accountShares->amount = -8; sample.assetsTotal = -7; sample.assetsAvailable = -7; diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 8b2254a840..bdebb70d09 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -43,6 +44,8 @@ #include #include +#include + namespace ripple { class Vault_test : public beast::unit_test::suite @@ -303,6 +306,55 @@ class Vault_test : public beast::unit_test::suite BEAST_EXPECT( env.balance(depositor, shares) == share(200 * scale)); } + else + { + testcase(prefix + " deposit/withdrawal same or less than fee"); + auto const amount = env.current()->fees().base; + + auto tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + env(tx); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + env(tx); + env.close(); + + // Withdraw to 3rd party + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + tx[sfDestination] = charlie.human(); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = amount - 1}); + env(tx); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = amount - 1}); + env(tx); + env.close(); + } { testcase( @@ -4795,6 +4847,147 @@ class Vault_test : public beast::unit_test::suite } } + void + testDelegate() + { + using namespace test::jtx; + + Env env(*this, testable_amendments()); + Account alice{"alice"}; + Account bob{"bob"}; + Account carol{"carol"}; + + struct CaseArgs + { + PrettyAsset asset = xrpIssue(); + }; + + auto const xrpBalance = + [this]( + Env const& env, Account const& account) -> std::optional { + auto sle = env.le(keylet::account(account.id())); + if (BEAST_EXPECT(sle != nullptr)) + return sle->getFieldAmount(sfBalance).xrp().drops(); + return std::nullopt; + }; + + auto testCase = [&, this](auto test, CaseArgs args = {}) { + Env env{*this, testable_amendments() | featureSingleAssetVault}; + + Vault vault{env}; + + // use different initial amount to distinguish the source balance + env.fund(XRP(10000), alice); + env.fund(XRP(20000), bob); + env.fund(XRP(30000), carol); + env.close(); + + env(delegate::set( + carol, + alice, + {"Payment", + "VaultCreate", + "VaultSet", + "VaultDelete", + "VaultDeposit", + "VaultWithdraw", + "VaultClawback"})); + + test(env, vault, args.asset); + }; + + testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) { + testcase("delegated vault creation"); + auto startBalance = xrpBalance(env, carol); + if (!BEAST_EXPECT(startBalance.has_value())) + return; + + auto [tx, keylet] = vault.create({.owner = carol, .asset = asset}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance); + }); + + testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) { + testcase("delegated deposit and withdrawal"); + auto [tx, keylet] = vault.create({.owner = carol, .asset = asset}); + env(tx); + env.close(); + + auto const amount = 1513; + auto const baseFee = env.current()->fees().base; + + auto startBalance = xrpBalance(env, carol); + if (!BEAST_EXPECT(startBalance.has_value())) + return; + + tx = vault.deposit( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount); + + tx = vault.withdraw( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount - 1)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - 1); + + tx = vault.withdraw( + {.depositor = carol, .id = keylet.key, .amount = asset(1)}); + env(tx); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee); + }); + + testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) { + testcase("delegated withdrawal same as base fee and deletion"); + auto [tx, keylet] = vault.create({.owner = carol, .asset = asset}); + env(tx); + env.close(); + + auto const amount = 25537; + auto const baseFee = env.current()->fees().base; + + auto startBalance = xrpBalance(env, carol); + if (!BEAST_EXPECT(startBalance.has_value())) + return; + + tx = vault.deposit( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount)}); + env(tx); + env.close(); + BEAST_EXPECT( + xrpBalance(env, carol) == *startBalance - amount - baseFee); + + tx = vault.withdraw( + {.depositor = carol, + .id = keylet.key, + .amount = asset(baseFee)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount); + + tx = vault.withdraw( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount - baseFee)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee); + + tx = vault.del({.owner = carol, .id = keylet.key}); + env(tx, delegate::as(alice)); + env.close(); + }); + } + public: void run() override @@ -4812,6 +5005,7 @@ public: testFailedPseudoAccount(); testScaleIOU(); testRPC(); + testDelegate(); } }; diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 2cfcb6d258..f7f67ee1d6 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -2230,13 +2230,12 @@ ValidVault::visitEntry( after != nullptr && (before != nullptr || !isDelete), "ripple::ValidVault::visitEntry : some object is available"); - // `Number balance` will capture the difference (delta) between "before" + // Number balanceDelta will capture the difference (delta) between "before" // state (zero if created) and "after" state (zero if destroyed), so the // invariants can validate that the change in account balances matches the // change in vault balances, stored to deltas_ at the end of this function. - Number balance{}; + Number balanceDelta{}; - // By default do not add anything to deltas std::int8_t sign = 0; if (before) { @@ -2249,18 +2248,18 @@ ValidVault::visitEntry( // At this moment we have no way of telling if this object holds // vault shares or something else. Save it for finalize. beforeMPTs_.push_back(Shares::make(*before)); - balance = static_cast( + balanceDelta = static_cast( before->getFieldU64(sfOutstandingAmount)); sign = 1; break; case ltMPTOKEN: - balance = + balanceDelta = static_cast(before->getFieldU64(sfMPTAmount)); sign = -1; break; case ltACCOUNT_ROOT: case ltRIPPLE_STATE: - balance = before->getFieldAmount(sfBalance); + balanceDelta = before->getFieldAmount(sfBalance); sign = -1; break; default:; @@ -2278,18 +2277,18 @@ ValidVault::visitEntry( // At this moment we have no way of telling if this object holds // vault shares or something else. Save it for finalize. afterMPTs_.push_back(Shares::make(*after)); - balance -= Number(static_cast( + balanceDelta -= Number(static_cast( after->getFieldU64(sfOutstandingAmount))); sign = 1; break; case ltMPTOKEN: - balance -= Number( + balanceDelta -= Number( static_cast(after->getFieldU64(sfMPTAmount))); sign = -1; break; case ltACCOUNT_ROOT: case ltRIPPLE_STATE: - balance -= Number(after->getFieldAmount(sfBalance)); + balanceDelta -= Number(after->getFieldAmount(sfBalance)); sign = -1; break; default:; @@ -2297,8 +2296,13 @@ ValidVault::visitEntry( } uint256 const key = (before ? before->key() : after->key()); - if (sign && balance != zero) - deltas_[key] = balance * sign; + // Append to deltas if sign is non-zero, i.e. an object of an interesting + // type has been updated. A transaction may update an object even when + // its balance has not changed, e.g. transaction fee equals the amount + // transferred to the account. We intentionally do not compare balanceDelta + // against zero, to avoid missing such updates. + if (sign != 0) + deltas_[key] = balanceDelta * sign; } bool @@ -2604,6 +2608,23 @@ ValidVault::finalize( }, vaultAsset.value()); }; + auto const deltaAssetsTxAccount = [&]() -> std::optional { + auto ret = deltaAssets(tx[sfAccount]); + // Nothing returned or not XRP transaction + if (!ret.has_value() || !vaultAsset.native()) + return ret; + + // Delegated transaction; no need to compensate for fees + if (auto const delegate = tx[~sfDelegate]; + delegate.has_value() && *delegate != tx[sfAccount]) + return ret; + + *ret += fee.drops(); + if (*ret == zero) + return std::nullopt; + + return ret; + }; auto const deltaShares = [&](AccountID const& id) -> std::optional { auto const it = [&]() { if (id == afterVault.pseudoId) @@ -2774,20 +2795,7 @@ ValidVault::finalize( if (!issuerDeposit) { - auto const accountDeltaAssets = - [&]() -> std::optional { - if (auto ret = deltaAssets(tx[sfAccount]); ret) - { - // Compensate for transaction fee deduced from - // sfAccount - if (vaultAsset.native()) - *ret += fee.drops(); - if (*ret != zero) - return ret; - } - return std::nullopt; - }(); - + auto const accountDeltaAssets = deltaAssetsTxAccount(); if (!accountDeltaAssets) { JLOG(j.fatal()) << // @@ -2840,7 +2848,7 @@ ValidVault::finalize( } auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares) + if (!vaultDeltaShares || *vaultDeltaShares == zero) { JLOG(j.fatal()) << // "Invariant failed: deposit must change vault shares"; @@ -2909,20 +2917,7 @@ ValidVault::finalize( if (!issuerWithdrawal) { - auto const accountDeltaAssets = - [&]() -> std::optional { - if (auto ret = deltaAssets(tx[sfAccount]); ret) - { - // Compensate for transaction fee deduced from - // sfAccount - if (vaultAsset.native()) - *ret += fee.drops(); - if (*ret != zero) - return ret; - } - return std::nullopt; - }(); - + auto const accountDeltaAssets = deltaAssetsTxAccount(); auto const otherAccountDelta = [&]() -> std::optional { if (auto const destination = tx[~sfDestination]; @@ -2979,7 +2974,7 @@ ValidVault::finalize( } auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares) + if (!vaultDeltaShares || *vaultDeltaShares == zero) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must change vault shares"; @@ -3064,7 +3059,7 @@ ValidVault::finalize( } auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares) + if (!vaultDeltaShares || *vaultDeltaShares == zero) { JLOG(j.fatal()) << // "Invariant failed: clawback must change vault shares"; From 0aa23933eabf1692ffaa2ecfc58b7eff849bbc0a Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 28 Oct 2025 15:52:13 -0400 Subject: [PATCH 09/11] ci: Only log into Conan when uploading packages (#5952) There are separate steps for logging into Conan and uploading packages. However, at the moment sometimes the login step is executed even though no packages will be uploaded. The condition for performing both steps should be the same. Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- .github/workflows/upload-conan-deps.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 0bea36cea7..538280676b 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -84,11 +84,11 @@ jobs: verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }} - name: Log into Conan remote - if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' }} + if: ${{ github.repository_owner == 'XRPLF' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }} run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}" - name: Upload Conan packages - if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' }} + if: ${{ github.repository_owner == 'XRPLF' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }} env: FORCE_OPTION: ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }} run: conan upload "*" --remote="${CONAN_REMOTE_NAME}" --confirm ${FORCE_OPTION} From 5efaf0c328e4ccb5d4b9e0974437f6a6e551274d Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 28 Oct 2025 15:52:52 -0400 Subject: [PATCH 10/11] ci: Only upload codecov reports in the original repo, not in forks (#5953) Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- .github/workflows/reusable-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 09f7f25271..8aa84608d4 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -126,7 +126,7 @@ jobs: if-no-files-found: error - name: Upload coverage report - if: ${{ inputs.cmake_target == 'coverage' }} + if: ${{ github.repository_owner == 'XRPLF' && inputs.cmake_target == 'coverage' }} uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: disable_search: true From 76611c3f46b3a063750aaa83cc4551128e06e7b5 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 28 Oct 2025 16:08:08 -0400 Subject: [PATCH 11/11] ci: Use commit hash so workflows are not canceled when merging multiple PRs (#5950) This change changes the CI concurrency group for pushes to the `develop` branch to use the commit hash instead of the target branch. Co-authored-by: Bart Thomee <11445373+bthomee@users.noreply.github.com> --- .github/workflows/on-trigger.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index c1f6839d2d..9df6417c07 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -50,7 +50,12 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # When a PR is merged into the develop branch it will be assigned a unique + # group identifier, so execution will continue even if another PR is merged + # while it is still running. In all other cases the group identifier is shared + # per branch, so that any in-progress runs are cancelled when a new commit is + # pushed. + group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }} cancel-in-progress: true defaults: