diff --git a/retesteth/Options.cpp b/retesteth/Options.cpp index 2e7a0f9d3..4823dc903 100644 --- a/retesteth/Options.cpp +++ b/retesteth/Options.cpp @@ -75,8 +75,7 @@ void printHelp() cout << setw(30) << "--filltests" << setw(0) << "Run test fillers\n"; cout << setw(30) << "--fillchain" << setw(25) << "When filling the state tests, fill tests as blockchain instead\n"; cout << setw(30) << "--showhash" << setw(25) << "Show filler hash debug information\n"; - cout << setw(30) << "--poststate" << setw(25) - << "Show post state hash or fullstate if --fullstate defined\n"; + cout << setw(30) << "--poststate" << setw(25) << "Show post state hash or fullstate\n"; cout << setw(30) << "--fullstate" << setw(25) << "Do not compress large states to hash\n"; // cout << setw(30) << "--randomcode " << setw(25) << "Generate smart random EVM //code\n"; cout << setw(30) << "--createRandomTest" << setw(25) << "Create random test and @@ -225,7 +224,10 @@ Options::Options(int argc, const char** argv) else if (arg == "--fullstate") fullstate = true; else if (arg == "--poststate") + { poststate = true; + fullstate = true; + } else if (arg == "--verbosity") { throwIfNoArgumentFollows(); diff --git a/retesteth/dataObject/DataObject.h b/retesteth/dataObject/DataObject.h index c17055e81..ac52e9293 100644 --- a/retesteth/dataObject/DataObject.h +++ b/retesteth/dataObject/DataObject.h @@ -155,35 +155,16 @@ class DataObject _assert(m_type == DataType::Null, "m_type == DataType::Null (DataObject& operator=). Overwriting dataobject that is " "not NULL"); - else - { - // overwrite value and key - if (m_type != DataType::Null) - { - replace(_value); - return *this; - } - } - // initialize new element if it was null before, but keep the key; DataObject[key] = - m_type = _value.type(); - switch (_value.type()) + if (m_type != DataType::Null) + replace(_value); // overwrite value and key + else { - case DataType::Integer: - m_intVal = _value.asInt(); - break; - case DataType::String: - m_strVal = _value.asString(); - break; - case DataType::Bool: - m_boolVal = _value.asBool(); - break; - default: - break; + // keep the key "newkey" for object["newkey"] = object2; declarations when object["newkey"] is null; + string const currentKey = m_strKey; + replace(_value); + m_strKey = currentKey; } - m_allowOverwrite = _value.isOverwritable(); - setAutosort(_value.isAutosort()); - m_subObjects = _value.getSubObjects(); return *this; } diff --git a/retesteth/ethObjects/common.h b/retesteth/ethObjects/common.h index a8026ff80..2e226c031 100644 --- a/retesteth/ethObjects/common.h +++ b/retesteth/ethObjects/common.h @@ -9,4 +9,5 @@ #include "rpcResponse/scheme_debugTraceTransaction.h" #include "stateTest/scheme_env.h" #include "stateTest/scheme_stateTest.h" +#include "vmTest/scheme_vmTest.h" #include "stateTest/scheme_RPCTest.h" diff --git a/retesteth/ethObjects/expectSection/scheme_expectAccount.h b/retesteth/ethObjects/expectSection/scheme_expectAccount.h index 473c5fc04..d5d72d8d8 100644 --- a/retesteth/ethObjects/expectSection/scheme_expectAccount.h +++ b/retesteth/ethObjects/expectSection/scheme_expectAccount.h @@ -70,9 +70,10 @@ class scheme_expectAccount : public object { // check that empty element is not set // if a post state has the value of 'key' as zero, such 'key' does not listed + string const& val = _storage.count(key) ? _storage.atKey(key).asString() : ""; checkMessage(!_storage.count(key), CompareResult::IncorrectStorage, message + " has storage key '" + element.getKey() + - "'. Test expected storage key: '" + element.getKey() + + "' : '" + val + "'. Test expected storage key: '" + element.getKey() + "' to be set to zero"); } else @@ -96,15 +97,18 @@ class scheme_expectAccount : public object checkMessage(expectStorage.getSubObjects().size() >= _storage.getSubObjects().size(), CompareResult::IncorrectStorage, TestOutputHelper::get().testName() + " Remote account '" + address() + - "' storage has more storage records then expected!"); + "' storage has more storage records than expected!"); if (expectStorage.getSubObjects().size() < _storage.getSubObjects().size()) { for (auto const& element : _storage.getSubObjects()) { + string expected = "0"; + if (expectStorage.count(element.getKey())) + expected = expectStorage.atKey(element.getKey()).asString(); string const message = "incorrect remote storage [" + element.getKey() + "] = " + element.asString() + ", test expected [" + - element.getKey() + "] = 0"; + element.getKey() + "] = " + expected; ETH_MARK_ERROR(message); } } diff --git a/retesteth/ethObjects/vmTest/scheme_vmTest.cpp b/retesteth/ethObjects/vmTest/scheme_vmTest.cpp new file mode 100644 index 000000000..f0a5cfd9e --- /dev/null +++ b/retesteth/ethObjects/vmTest/scheme_vmTest.cpp @@ -0,0 +1,95 @@ +#include "scheme_vmTest.h" +using namespace test; +using namespace std; + +scheme_vmTestBase::scheme_vmTestBase(DataObject const& _test) + : object(_test), m_checker(_test), m_env(_test.atKey("env")), m_pre(_test.atKey("pre")) +{} + +scheme_vmTestBase::fieldChecker::fieldChecker(DataObject const& _test) +{ + ETH_ERROR_REQUIRE_MESSAGE(_test.count("env"), "VM test must have 'env' section"); + ETH_ERROR_REQUIRE_MESSAGE(_test.count("pre"), "VM test must have 'pre' section"); +} + +/* +scheme_vmTest::scheme_vmTest(DataObject const& _test) + : scheme_vmTestBase(_test), m_checker(_test), m_post(_test.atKey("post")) +{ +} + +scheme_vmTest::fieldChecker::fieldChecker(DataObject const& _test) +{ + requireJsonFields(_test, "stateTest " + _test.getKey(), { + {"_info", {DataType::Object} }, + {"env", {DataType::Object} }, + {"pre", {DataType::Object} }, + {"transaction", {DataType::Object} }, + {"post", {DataType::Object} } + }); + + requireJsonFields(_test.atKey("_info"), "stateTest " + _test.getKey() + " _info ", + { + {"comment", {{DataType::String}, jsonField::Required}}, + {"source", {{DataType::String}, jsonField::Required}}, + {"sourceHash", {{DataType::String}, jsonField::Required}}, + {"lllcversion", {{DataType::String}, jsonField::Required}}, + {"filledwith", {{DataType::String}, jsonField::Optional}}, + {"filling-rpc-server", {{DataType::String}, jsonField::Optional}}, + {"filling-tool-version", {{DataType::String}, jsonField::Optional}}, + }); + + // Check that `data` in compiled test is not just a string but a binary string + ETH_ERROR_REQUIRE_MESSAGE(_test.atKey("transaction").count("data"), + "Field `data` not found in `transaction` section (" + TestInfo::caseName() + ")"); + ETH_ERROR_REQUIRE_MESSAGE(_test.atKey("transaction").atKey("data").type() == DataType::Array, + "Field `data` in `transaction` section is expected to be Array! (" + + TestOutputHelper::get().testName() + ")"); + for (auto const& element : _test.atKey("transaction").atKey("data").getSubObjects()) + ETH_ERROR_REQUIRE_MESSAGE(stringIntegerType(element.asString()) == DigitsType::HexPrefixed, + "Field `data` in `transaction` section is expected to be binary prefixed with `0x` in " ++ TestOutputHelper::get().testName() + ", but got: `" + element.asString() + "`"); +} +*/ + +scheme_vmTestFiller::fieldChecker::fieldChecker(DataObject const& _test) +{ + requireJsonFields(_test, "vmTestFiller " + _test.getKey(), + {{"_info", {{DataType::Object}, jsonField::Optional}}, + {"env", {{DataType::Object}, jsonField::Required}}, + {"pre", {{DataType::Object}, jsonField::Required}}, + {"exec", {{DataType::Object}, jsonField::Required}}, + {"expect", {{DataType::Object}, jsonField::Optional}}, + {"expectOut", {{DataType::String}, jsonField::Optional}}}); + if (_test.count("expectOut")) + ETH_WARNING("Unable to verify `expectOut` when creating a stateTest from VMTest " + + TestOutputHelper::get().testInfo().getMessage()); +} + +DataObject translateExecToTransaction(DataObject const& _exec) +{ + DataObject gtransaction; + requireJsonFields(_exec, "vmTestFiller exec", + {{"address", {{DataType::String}, jsonField::Required}}, + {"caller", {{DataType::String}, jsonField::Required}}, + {"data", {{DataType::String}, jsonField::Required}}, + {"code", {{DataType::String}, jsonField::Optional}}, + {"gas", {{DataType::String}, jsonField::Required}}, + {"gasPrice", {{DataType::String}, jsonField::Required}}, + {"origin", {{DataType::String}, jsonField::Required}}, + {"value", {{DataType::String}, jsonField::Required}}}); + gtransaction["data"].addArrayObject(DataObject(_exec.atKey("data").asString())); + gtransaction["gasLimit"].addArrayObject(DataObject(_exec.atKey("gas").asString())); + gtransaction["gasPrice"] = _exec.atKey("gasPrice"); + gtransaction["nonce"] = "0"; + gtransaction["secretKey"] = "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"; + gtransaction["to"] = _exec.atKey("address"); + gtransaction["value"].addArrayObject(DataObject(_exec.atKey("value").asString())); + return gtransaction; +} + +scheme_vmTestFiller::scheme_vmTestFiller(DataObject const& _test) + : scheme_vmTestBase(_test), + m_checker(_test), + m_gtransaction(translateExecToTransaction(_test.atKey("exec"))) +{} diff --git a/retesteth/ethObjects/vmTest/scheme_vmTest.h b/retesteth/ethObjects/vmTest/scheme_vmTest.h new file mode 100644 index 000000000..cbe440ac7 --- /dev/null +++ b/retesteth/ethObjects/vmTest/scheme_vmTest.h @@ -0,0 +1,45 @@ +#pragma once +#include "../expectSection/scheme_expectSection.h" +#include "../object.h" +#include "scheme_vmTestBase.h" + +#include +#include + +using namespace test; +using namespace testprivate; + +namespace test +{ +/* + class scheme_vmTest : public scheme_vmTestBase + { + public: + scheme_vmTest(DataObject const& _test); + + private: + class fieldChecker + { + public: + fieldChecker(DataObject const& _test); + }; + fieldChecker m_checker; + }; +*/ + +class scheme_vmTestFiller : public scheme_vmTestBase +{ +public: + scheme_vmTestFiller(DataObject const& _test); + scheme_generalTransaction const& getTransaction() { return m_gtransaction; } + +private: + class fieldChecker + { + public: + fieldChecker(DataObject const& _test); + }; + fieldChecker m_checker; + scheme_generalTransaction m_gtransaction; +}; +} // namespace test diff --git a/retesteth/ethObjects/vmTest/scheme_vmTestBase.h b/retesteth/ethObjects/vmTest/scheme_vmTestBase.h new file mode 100644 index 000000000..ed3813dd0 --- /dev/null +++ b/retesteth/ethObjects/vmTest/scheme_vmTestBase.h @@ -0,0 +1,31 @@ +#pragma once +#include "../object.h" +#include "../stateTest/scheme_env.h" +#include "../stateTest/scheme_state.h" +#include "../stateTest/scheme_transaction.h" + +#include +#include +#include + +using namespace test; +namespace testprivate +{ +class scheme_vmTestBase : public object +{ +public: + scheme_vmTestBase(DataObject const& _test); + scheme_env const& getEnv() const { return m_env; } + scheme_state const& getPre() const { return m_pre; } + +private: + class fieldChecker + { + public: + fieldChecker(DataObject const& _test); + }; + fieldChecker m_checker; + scheme_env m_env; + scheme_state m_pre; +}; +} // namespace testprivate diff --git a/retesteth/testSuites/StateTests.cpp b/retesteth/testSuites/StateTests.cpp index d0587a5e7..f45935c7e 100644 --- a/retesteth/testSuites/StateTests.cpp +++ b/retesteth/testSuites/StateTests.cpp @@ -212,6 +212,10 @@ DataObject FillTest(DataObject const& _testFile) scheme_block blockInfo = session.eth_getBlockByNumber(latestBlockNumber, Options::get().vmtrace); + if (Options::get().poststate) + ETH_STDOUT_MESSAGE("PostState " + + TestOutputHelper::get().testInfo().getMessage() + + " : \n" + blockInfo.getStateHash()); if (Options::get().vmtrace) printVmTrace(session, trHash, blockInfo.getStateHash()); if (Options::get().fullstate) @@ -220,11 +224,7 @@ DataObject FillTest(DataObject const& _testFile) compareStates(expect.getExpectState(), remoteState); } else - { - if (Options::get().poststate) - ETH_STDOUT_MESSAGE("PostState " + TestOutputHelper::get().testInfo().getMessage() + " : \n" + blockInfo.getStateHash()); compareStates(expect.getExpectState(), session, blockInfo); - } DataObject indexes; DataObject transactionResults; diff --git a/retesteth/testSuites/VMTestsConverter.cpp b/retesteth/testSuites/VMTestsConverter.cpp new file mode 100644 index 000000000..d84ae5f78 --- /dev/null +++ b/retesteth/testSuites/VMTestsConverter.cpp @@ -0,0 +1,190 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ + +/** @file VMTestToStateConverterSuite.cpp + * @author Dimitry Khokhlov + * @date 2020 + * VM Tests parser. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +namespace fs = boost::filesystem; + +namespace +{ +DataObject getGenesisTemplate() +{ + // Blockchain Test Template + DataObject genesisBlockHeader; + genesisBlockHeader["number"] = "0"; + genesisBlockHeader["parentHash"] = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + genesisBlockHeader["bloom"] = + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000"; + genesisBlockHeader["extraData"] = "0x42"; + genesisBlockHeader["gasUsed"] = "0"; + genesisBlockHeader["mixHash"] = + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + genesisBlockHeader["nonce"] = "0x0102030405060708"; + genesisBlockHeader["receiptTrie"] = + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + genesisBlockHeader["stateRoot"] = + "0xf99eb1626cfa6db435c0836235942d7ccaa935f1ae247d3f1c21e495685f903a"; + genesisBlockHeader["transactionsTrie"] = + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + genesisBlockHeader["uncleHash"] = + "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; + return genesisBlockHeader; +} + +} // namespace + +namespace test +{ +// Most Recent StateTestSuite +TestSuite::TestPath VMTestConverterSuite::suiteFolder() const +{ + return TestSuite::TestPath(fs::path("BlockchainTests/ValidBlocks/VMTests")); +} + +TestSuite::FillerPath VMTestConverterSuite::suiteFillerFolder() const +{ + return TestSuite::FillerPath(fs::path("src") / "VMTestsFiller"); +} + +DataObject VMTestConverterSuite::doTests(DataObject const& _input, TestSuiteOptions& _opt) const +{ + DataObject obj; + checkAtLeastOneTest(_input); + if (_opt.doFilling) // convert vmTestFiller into StateTestFiller + { + DataObject blockFiller; + + DataObject const& vmFiller = _input.getSubObjects().at(0); + string const& testname = vmFiller.getKey(); + TestOutputHelper::get().setCurrentTestName(testname); + TestOutputHelper::get().setCurrentTestInfo(TestInfo("Filler init")); + + scheme_vmTestFiller vmTestFiller(vmFiller); + TestOutputHelper::get().setCurrentTestInfo(TestInfo("Converting to BlockchainTestFiller")); + + // Prepare _info comment + string comment; + if (vmFiller.count("_info")) + blockFiller["_info"] = vmFiller.atKey("_info"); + if (blockFiller["_info"].count("comment")) + comment = blockFiller["_info"]["comment"].asString(); + blockFiller["_info"]["comment"] = "Converted from VMTest source. " + comment; + + // Prepare genesisBlockHeader + blockFiller["genesisBlockHeader"] = getGenesisTemplate(); + blockFiller["genesisBlockHeader"]["coinbase"] = + vmFiller.atKey("env").atKey("currentCoinbase"); + blockFiller["genesisBlockHeader"]["difficulty"] = + vmFiller.atKey("env").atKey("currentDifficulty"); + blockFiller["genesisBlockHeader"]["gasLimit"] = + vmFiller.atKey("env").atKey("currentGasLimit"); + blockFiller["genesisBlockHeader"]["timestamp"] = "0"; + blockFiller["sealEngine"] = "NoProof"; // Disable mining + + // Prepare pre section + blockFiller["pre"] = vmFiller.atKey("pre"); + // Insert sender account + string const sender = "a94f5374fce5edbc8e2a8697c15331677e6ebf0b"; + if (!blockFiller["pre"].count(sender)) + { + blockFiller["pre"][sender]["balance"] = "0x7ffffffffffffff0"; + blockFiller["pre"][sender]["nonce"] = "0"; + blockFiller["pre"][sender]["code"] = ""; + blockFiller["pre"][sender]["storage"] = DataObject(DataType::Object); + } + + // Prepare block with transaction + DataObject blockInfo; + blockInfo["blockHeader"]["timestamp"] = vmFiller.atKey("env").atKey("currentTimestamp"); + blockInfo["blockHeader"]["difficulty"] = vmFiller.atKey("env").atKey("currentDifficulty"); + blockInfo["blockHeader"]["gasLimit"] = vmFiller.atKey("env").atKey("currentGasLimit"); + + scheme_transaction const& trInTest = + vmTestFiller.getTransaction().getTransactions().at(0).transaction; + blockInfo["transactions"].addArrayObject(trInTest.getData()); + blockInfo["uncleHeaders"] = DataObject(DataType::Array); + blockFiller["blocks"].addArrayObject(blockInfo); + + // Construct expect section + DataObject expectSection; + expectSection["network"].addArrayObject(DataObject(">=Istanbul")); + if (vmFiller.count("expect")) + expectSection["result"] = vmFiller.atKey("expect"); + else + { + ETH_WARNING("VMTest filler missing expect section! Empty section will be used. " + + TestOutputHelper::get().testInfo().getMessage()); + expectSection["result"] = DataObject(DataType::Object); + } + blockFiller["expect"].addArrayObject(expectSection); + obj[testname] = blockFiller; + + try + { + BlockchainTestValidSuite blockSuite; + TestSuiteOptions stateOpt; + stateOpt.doFilling = true; + return blockSuite.doTests(obj, stateOpt); + } + catch (std::exception const& _ex) + { + ETH_LOG("Error when filling the bc test! (" + testname + "): \n" + _ex.what(), 0); + } + } + else // run tests + { + // Execute block tests filled from VMTests + BlockchainTestValidSuite blockSuite; + blockSuite.doTests(_input, _opt); + } + return obj; + } + + } // namespace test diff --git a/retesteth/testSuites/VMTestsConverter.h b/retesteth/testSuites/VMTestsConverter.h new file mode 100644 index 000000000..c54d0fb4e --- /dev/null +++ b/retesteth/testSuites/VMTestsConverter.h @@ -0,0 +1,33 @@ +/* +This file is part of cpp-ethereum. + +cpp-ethereum is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +cpp-ethereum is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with cpp-ethereum. If not, see . +*/ + +#pragma once +#include +#include +#include + +namespace test +{ +class VMTestConverterSuite : public TestSuite +{ +public: + DataObject doTests(DataObject const& _input, TestSuiteOptions& _opt) const override; + TestSuite::TestPath suiteFolder() const override; + TestSuite::FillerPath suiteFillerFolder() const override; +}; + +} // namespace test diff --git a/retesteth/testSuites/blockchain/BlockchainTests.cpp b/retesteth/testSuites/blockchain/BlockchainTests.cpp index fcf8d3657..5dc32280b 100644 --- a/retesteth/testSuites/blockchain/BlockchainTests.cpp +++ b/retesteth/testSuites/blockchain/BlockchainTests.cpp @@ -23,6 +23,7 @@ along with cpp-ethereum. If not, see . #include #include #include +#include #include using namespace std; @@ -146,8 +147,26 @@ BOOST_AUTO_TEST_CASE(bcUncleSpecialTests) {} BOOST_AUTO_TEST_CASE(bcUncleTest) {} BOOST_AUTO_TEST_CASE(bcValidBlockTest) {} BOOST_AUTO_TEST_CASE(bcWalletTest) {} + +using VMTestsConverterFixture = TestFixture; +BOOST_FIXTURE_TEST_SUITE(VMTests, VMTestsConverterFixture) +BOOST_AUTO_TEST_CASE(vmArithmeticTest) {} +BOOST_AUTO_TEST_CASE(vmBitwiseLogicOperation) {} +BOOST_AUTO_TEST_CASE(vmBlockInfoTest) {} +BOOST_AUTO_TEST_CASE(vmEnvironmentalInfo) {} +BOOST_AUTO_TEST_CASE(vmIOandFlowOperations) {} +BOOST_AUTO_TEST_CASE(vmLogTest) {} +BOOST_AUTO_TEST_CASE(vmPerformance) {} +BOOST_AUTO_TEST_CASE(vmPushDupSwapTest) {} +BOOST_AUTO_TEST_CASE(vmRandomTest) {} +BOOST_AUTO_TEST_CASE(vmSha3Test) {} +BOOST_AUTO_TEST_CASE(vmSystemOperations) {} +BOOST_AUTO_TEST_CASE(vmTests) {} BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() // ValidBlocks + + // Tests that might have invalid blocks and check that those are rejected using BCInValidSuiteFixture = TestFixture; BOOST_FIXTURE_TEST_SUITE(InvalidBlocks, BCInValidSuiteFixture) diff --git a/retesteth/unitTests/dataObjectTests.cpp b/retesteth/unitTests/dataObjectTests.cpp index c2b67494f..85dc27a71 100644 --- a/retesteth/unitTests/dataObjectTests.cpp +++ b/retesteth/unitTests/dataObjectTests.cpp @@ -762,4 +762,21 @@ BOOST_AUTO_TEST_CASE(dataobject_jsonOrder) "\"7\",\"aa70\":\"7\",\"aa8\":\"8\"}"); } +BOOST_AUTO_TEST_CASE(dataobject_replace) +{ + DataObject data(DataType::Null); + DataObject data2(DataType::Null); + DataObject data3(DataType::Null); + data2.setKey("key2"); + data2.setString("value2"); + data3.setKey("key3"); + data3.setString("value3"); + + data["field1"] = data3; // null object with key "field1" keep the key "field1" + BOOST_CHECK(data.asJson(0,false) == "{\"field1\":\"value3\"}"); + + data["field1"] = data2; // not null object with key "field1" replaces the key "field1" to data2's key + BOOST_CHECK(data.asJson(0,false) == "{\"key2\":\"value2\"}"); +} + BOOST_AUTO_TEST_SUITE_END()