diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 0171b9934..0f52295b1 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -1,192 +1,194 @@ // Copyright (c) 2012-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include +#include + #include #include #include #include #include BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) static void ResetArgs(ArgsManager &am, const std::string &strArg) { std::vector vecArg; if (strArg.size()) { boost::split(vecArg, strArg, boost::is_space(), boost::token_compress_on); } // Insert dummy executable name: vecArg.insert(vecArg.begin(), "testbitcoin"); // Convert to char*: std::vector vecChar; for (const std::string &s : vecArg) { vecChar.push_back(s.c_str()); } std::string error; BOOST_CHECK(am.ParseParameters(vecChar.size(), vecChar.data(), error)); } static void SetupArgs(ArgsManager &am, const std::vector> &args) { am.ClearArgs(); for (const auto &arg : args) { am.AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } BOOST_AUTO_TEST_CASE(boolarg) { ArgsManager am; const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); SetupArgs(am, {foo}); ResetArgs(am, "-foo"); BOOST_CHECK(am.GetBoolArg("-foo", false)); BOOST_CHECK(am.GetBoolArg("-foo", true)); BOOST_CHECK(!am.GetBoolArg("-fo", false)); BOOST_CHECK(am.GetBoolArg("-fo", true)); BOOST_CHECK(!am.GetBoolArg("-fooo", false)); BOOST_CHECK(am.GetBoolArg("-fooo", true)); ResetArgs(am, "-foo=0"); BOOST_CHECK(!am.GetBoolArg("-foo", false)); BOOST_CHECK(!am.GetBoolArg("-foo", true)); ResetArgs(am, "-foo=1"); BOOST_CHECK(am.GetBoolArg("-foo", false)); BOOST_CHECK(am.GetBoolArg("-foo", true)); // New 0.6 feature: auto-map -nosomething to !-something: ResetArgs(am, "-nofoo"); BOOST_CHECK(!am.GetBoolArg("-foo", false)); BOOST_CHECK(!am.GetBoolArg("-foo", true)); ResetArgs(am, "-nofoo=1"); BOOST_CHECK(!am.GetBoolArg("-foo", false)); BOOST_CHECK(!am.GetBoolArg("-foo", true)); // -nofoo should win ResetArgs(am, "-foo -nofoo"); BOOST_CHECK(!am.GetBoolArg("-foo", false)); BOOST_CHECK(!am.GetBoolArg("-foo", true)); // -nofoo should win ResetArgs(am, "-foo=1 -nofoo=1"); BOOST_CHECK(!am.GetBoolArg("-foo", false)); BOOST_CHECK(!am.GetBoolArg("-foo", true)); // -nofoo=0 should win ResetArgs(am, "-foo=0 -nofoo=0"); BOOST_CHECK(am.GetBoolArg("-foo", false)); BOOST_CHECK(am.GetBoolArg("-foo", true)); // New 0.6 feature: treat -- same as -: ResetArgs(am, "--foo=1"); BOOST_CHECK(am.GetBoolArg("-foo", false)); BOOST_CHECK(am.GetBoolArg("-foo", true)); ResetArgs(am, "--nofoo=1"); BOOST_CHECK(!am.GetBoolArg("-foo", false)); BOOST_CHECK(!am.GetBoolArg("-foo", true)); } BOOST_AUTO_TEST_CASE(stringarg) { ArgsManager am; const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_STRING); const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_STRING); SetupArgs(am, {foo, bar}); ResetArgs(am, ""); BOOST_CHECK_EQUAL(am.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(am.GetArg("-foo", "eleven"), "eleven"); ResetArgs(am, "-foo -bar"); BOOST_CHECK_EQUAL(am.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(am.GetArg("-foo", "eleven"), ""); ResetArgs(am, "-foo="); BOOST_CHECK_EQUAL(am.GetArg("-foo", ""), ""); BOOST_CHECK_EQUAL(am.GetArg("-foo", "eleven"), ""); ResetArgs(am, "-foo=11"); BOOST_CHECK_EQUAL(am.GetArg("-foo", ""), "11"); BOOST_CHECK_EQUAL(am.GetArg("-foo", "eleven"), "11"); ResetArgs(am, "-foo=eleven"); BOOST_CHECK_EQUAL(am.GetArg("-foo", ""), "eleven"); BOOST_CHECK_EQUAL(am.GetArg("-foo", "eleven"), "eleven"); } BOOST_AUTO_TEST_CASE(intarg) { ArgsManager am; const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_INT); const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_INT); SetupArgs(am, {foo, bar}); ResetArgs(am, ""); BOOST_CHECK_EQUAL(am.GetArg("-foo", 11), 11); BOOST_CHECK_EQUAL(am.GetArg("-foo", 0), 0); ResetArgs(am, "-foo -bar"); BOOST_CHECK_EQUAL(am.GetArg("-foo", 11), 0); BOOST_CHECK_EQUAL(am.GetArg("-bar", 11), 0); ResetArgs(am, "-foo=11 -bar=12"); BOOST_CHECK_EQUAL(am.GetArg("-foo", 0), 11); BOOST_CHECK_EQUAL(am.GetArg("-bar", 11), 12); ResetArgs(am, "-foo=NaN -bar=NotANumber"); BOOST_CHECK_EQUAL(am.GetArg("-foo", 1), 0); BOOST_CHECK_EQUAL(am.GetArg("-bar", 11), 0); } BOOST_AUTO_TEST_CASE(doubledash) { ArgsManager am; const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY); const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY); SetupArgs(am, {foo, bar}); ResetArgs(am, "--foo"); BOOST_CHECK_EQUAL(am.GetBoolArg("-foo", false), true); ResetArgs(am, "--foo=verbose --bar=1"); BOOST_CHECK_EQUAL(am.GetArg("-foo", ""), "verbose"); BOOST_CHECK_EQUAL(am.GetArg("-bar", 0), 1); } BOOST_AUTO_TEST_CASE(boolargno) { ArgsManager am; const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_BOOL); SetupArgs(am, {foo, bar}); ResetArgs(am, "-nofoo"); BOOST_CHECK(!am.GetBoolArg("-foo", true)); BOOST_CHECK(!am.GetBoolArg("-foo", false)); ResetArgs(am, "-nofoo=1"); BOOST_CHECK(!am.GetBoolArg("-foo", true)); BOOST_CHECK(!am.GetBoolArg("-foo", false)); ResetArgs(am, "-nofoo=0"); BOOST_CHECK(am.GetBoolArg("-foo", true)); BOOST_CHECK(am.GetBoolArg("-foo", false)); // --nofoo should win ResetArgs(am, "-foo --nofoo"); BOOST_CHECK(!am.GetBoolArg("-foo", true)); BOOST_CHECK(!am.GetBoolArg("-foo", false)); // foo always wins: ResetArgs(am, "-nofoo -foo"); BOOST_CHECK(am.GetBoolArg("-foo", true)); BOOST_CHECK(am.GetBoolArg("-foo", false)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index b3fe2ed48..5df65718c 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -1,341 +1,341 @@ // Copyright (c) 2012-2019 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include class CAddrManSerializationMock : public CAddrMan { public: virtual void Serialize(CDataStream &s) const = 0; //! Ensure that bucket placement is always the same for testing purposes. void MakeDeterministic() { nKey.SetNull(); insecure_rand = FastRandomContext(true); } }; class CAddrManUncorrupted : public CAddrManSerializationMock { public: void Serialize(CDataStream &s) const override { CAddrMan::Serialize(s); } }; class CAddrManCorrupted : public CAddrManSerializationMock { public: void Serialize(CDataStream &s) const override { // Produces corrupt output that claims addrman has 20 addrs when it only // has one addr. uint8_t nVersion = 1; s << nVersion; s << uint8_t(32); s << nKey; s << 10; // nNew s << 10; // nTried int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30); s << nUBuckets; CService serv; Lookup("252.1.1.1", serv, 7777, false); CAddress addr = CAddress(serv, NODE_NONE); CNetAddr resolved; LookupHost("252.2.2.2", resolved, false); CAddrInfo info = CAddrInfo(addr, resolved); s << info; } }; class NetTestConfig : public DummyConfig { public: bool SetMaxBlockSize(uint64_t maxBlockSize) override { nMaxBlockSize = maxBlockSize; return true; } uint64_t GetMaxBlockSize() const override { return nMaxBlockSize; } private: uint64_t nMaxBlockSize; }; static CDataStream AddrmanToStream(CAddrManSerializationMock &_addrman) { CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION); ssPeersIn << Params().DiskMagic(); ssPeersIn << _addrman; std::string str = ssPeersIn.str(); std::vector vchData(str.begin(), str.end()); return CDataStream(vchData, SER_DISK, CLIENT_VERSION); } BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(cnode_listen_port) { // test default unsigned short port = GetListenPort(); BOOST_CHECK(port == Params().GetDefaultPort()); // test set port unsigned short altPort = 12345; gArgs.SoftSetArg("-port", std::to_string(altPort)); port = GetListenPort(); BOOST_CHECK(port == altPort); } BOOST_AUTO_TEST_CASE(caddrdb_read) { CAddrManUncorrupted addrmanUncorrupted; addrmanUncorrupted.MakeDeterministic(); CService addr1, addr2, addr3; Lookup("250.7.1.1", addr1, 8333, false); Lookup("250.7.2.2", addr2, 9999, false); Lookup("250.7.3.3", addr3, 9999, false); // Add three addresses to new table. CService source; Lookup("252.5.1.1", source, 8333, false); addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source); addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source); addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source); // Test that the de-serialization does not throw an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted); bool exceptionThrown = false; CAddrMan addrman1; BOOST_CHECK(addrman1.size() == 0); try { uint8_t pchMsgTmp[4]; ssPeers1 >> pchMsgTmp; ssPeers1 >> addrman1; } catch (const std::exception &) { exceptionThrown = true; } BOOST_CHECK(addrman1.size() == 3); BOOST_CHECK(exceptionThrown == false); // Test that CAddrDB::Read creates an addrman with the correct number of // addrs. CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted); CAddrMan addrman2; CAddrDB adb(Params()); BOOST_CHECK(addrman2.size() == 0); adb.Read(addrman2, ssPeers2); BOOST_CHECK(addrman2.size() == 3); } BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) { CAddrManCorrupted addrmanCorrupted; addrmanCorrupted.MakeDeterministic(); // Test that the de-serialization of corrupted addrman throws an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted); bool exceptionThrown = false; CAddrMan addrman1; BOOST_CHECK(addrman1.size() == 0); try { uint8_t pchMsgTmp[4]; ssPeers1 >> pchMsgTmp; ssPeers1 >> addrman1; } catch (const std::exception &) { exceptionThrown = true; } // Even through de-serialization failed addrman is not left in a clean // state. BOOST_CHECK(addrman1.size() == 1); BOOST_CHECK(exceptionThrown); // Test that CAddrDB::Read leaves addrman in a clean state if // de-serialization fails. CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted); CAddrMan addrman2; CAddrDB adb(Params()); BOOST_CHECK(addrman2.size() == 0); adb.Read(addrman2, ssPeers2); BOOST_CHECK(addrman2.size() == 0); } BOOST_AUTO_TEST_CASE(cnode_simple_test) { SOCKET hSocket = INVALID_SOCKET; NodeId id = 0; int height = 0; in_addr ipv4Addr; ipv4Addr.s_addr = 0xa0b0c001; CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK); std::string pszDest; bool fInboundIn = false; // Test that fFeeler is false by default. auto pnode1 = std::make_unique(id++, NODE_NETWORK, height, hSocket, addr, 0, 0, CAddress(), pszDest, fInboundIn); BOOST_CHECK(pnode1->fInbound == false); BOOST_CHECK(pnode1->fFeeler == false); fInboundIn = true; auto pnode2 = std::make_unique(id++, NODE_NETWORK, height, hSocket, addr, 1, 1, CAddress(), pszDest, fInboundIn); BOOST_CHECK(pnode2->fInbound == true); BOOST_CHECK(pnode2->fFeeler == false); } BOOST_AUTO_TEST_CASE(test_getSubVersionEB) { BOOST_CHECK_EQUAL(getSubVersionEB(13800000000), "13800.0"); BOOST_CHECK_EQUAL(getSubVersionEB(3800000000), "3800.0"); BOOST_CHECK_EQUAL(getSubVersionEB(14000000), "14.0"); BOOST_CHECK_EQUAL(getSubVersionEB(1540000), "1.5"); BOOST_CHECK_EQUAL(getSubVersionEB(1560000), "1.5"); BOOST_CHECK_EQUAL(getSubVersionEB(210000), "0.2"); BOOST_CHECK_EQUAL(getSubVersionEB(10000), "0.0"); BOOST_CHECK_EQUAL(getSubVersionEB(0), "0.0"); } BOOST_AUTO_TEST_CASE(test_userAgent) { NetTestConfig config; config.SetMaxBlockSize(8000000); const std::string uacomment = "A very nice comment"; - gArgs.ForceSetMultiArg("-uacomment", uacomment); + gArgs.ForceSetMultiArg("-uacomment", {uacomment}); const std::string versionMessage = "/Bitcoin ABC:" + std::to_string(CLIENT_VERSION_MAJOR) + "." + std::to_string(CLIENT_VERSION_MINOR) + "." + std::to_string(CLIENT_VERSION_REVISION) + "(EB8.0; " + uacomment + ")/"; BOOST_CHECK_EQUAL(userAgent(config), versionMessage); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) { BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true); BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true); BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true); SetReachable(NET_IPV4, false); SetReachable(NET_IPV6, false); SetReachable(NET_ONION, false); BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), false); BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), false); BOOST_CHECK_EQUAL(IsReachable(NET_ONION), false); SetReachable(NET_IPV4, true); SetReachable(NET_IPV6, true); SetReachable(NET_ONION, true); BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true); BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true); BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) { BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true); SetReachable(NET_UNROUTABLE, false); SetReachable(NET_INTERNAL, false); // Ignored for both networks BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true); } CNetAddr UtilBuildAddress(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4) { uint8_t ip[] = {p1, p2, p3, p4}; struct sockaddr_in sa; // initialize the memory block memset(&sa, 0, sizeof(sockaddr_in)); memcpy(&(sa.sin_addr), &ip, sizeof(ip)); return CNetAddr(sa.sin_addr); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_CNetAddr) { // 1.1.1.1 CNetAddr addr = UtilBuildAddress(0x001, 0x001, 0x001, 0x001); SetReachable(NET_IPV4, true); BOOST_CHECK_EQUAL(IsReachable(addr), true); SetReachable(NET_IPV4, false); BOOST_CHECK_EQUAL(IsReachable(addr), false); // have to reset this, because this is stateful. SetReachable(NET_IPV4, true); } BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) { // 2.1.1.1:1000 CService addr = CService(UtilBuildAddress(0x002, 0x001, 0x001, 0x001), 1000); SetReachable(NET_IPV4, true); BOOST_CHECK_EQUAL(IsLocal(addr), false); BOOST_CHECK_EQUAL(AddLocal(addr, 1000), true); BOOST_CHECK_EQUAL(IsLocal(addr), true); RemoveLocal(addr); BOOST_CHECK_EQUAL(IsLocal(addr), false); } // prior to PR #14728, this test triggers an undefined behavior BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) { // set up local addresses; all that's necessary to reproduce the bug is // that a normal IPv4 address is among the entries, but if this address is // !IsRoutable the undefined behavior is easier to trigger deterministically { LOCK(cs_mapLocalHost); in_addr ipv4AddrLocal; ipv4AddrLocal.s_addr = 0x0100007f; CNetAddr addr = CNetAddr(ipv4AddrLocal); LocalServiceInfo lsi; lsi.nScore = 23; lsi.nPort = 42; mapLocalHost[addr] = lsi; } // create a peer with an IPv4 address in_addr ipv4AddrPeer; ipv4AddrPeer.s_addr = 0xa0b0c001; CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK); std::unique_ptr pnode = std::make_unique(0, NODE_NETWORK, 0, INVALID_SOCKET, addr, 0, 0, CAddress{}, std::string{}, false); pnode->fSuccessfullyConnected.store(true); // the peer claims to be reaching us via IPv6 in6_addr ipv6AddrLocal; memset(ipv6AddrLocal.s6_addr, 0, 16); ipv6AddrLocal.s6_addr[0] = 0xcc; CAddress addrLocal = CAddress(CService(ipv6AddrLocal, 7777), NODE_NETWORK); pnode->SetAddrLocal(addrLocal); // before patch, this causes undefined behavior detectable with clang's // -fsanitize=memory AdvertiseLocal(&*pnode); // suppress no-checks-run warning; if this test fails, it's by triggering a // sanitizer BOOST_CHECK(1); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 692eca186..479d45c79 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1,1923 +1,1913 @@ // Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include +#include + #include #include #include #ifndef WIN32 #include #include #include #endif #include #include BOOST_FIXTURE_TEST_SUITE(util_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(util_criticalsection) { RecursiveMutex cs; do { LOCK(cs); break; BOOST_ERROR("break was swallowed!"); } while (0); do { TRY_LOCK(cs, lockTest); if (lockTest) { // Needed to suppress "Test case [...] did not check any assertions" BOOST_CHECK(true); break; } BOOST_ERROR("break was swallowed!"); } while (0); } static const uint8_t ParseHex_expected[65] = { 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f}; BOOST_AUTO_TEST_CASE(util_ParseHex) { std::vector result; std::vector expected( ParseHex_expected, ParseHex_expected + sizeof(ParseHex_expected)); // Basic test vector result = ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0" "ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d" "578a4c702b6bf11d5f"); BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); // Spaces between bytes must be supported result = ParseHex("12 34 56 78"); BOOST_CHECK(result.size() == 4 && result[0] == 0x12 && result[1] == 0x34 && result[2] == 0x56 && result[3] == 0x78); // Leading space must be supported (used in BerkeleyEnvironment::Salvage) result = ParseHex(" 89 34 56 78"); BOOST_CHECK(result.size() == 4 && result[0] == 0x89 && result[1] == 0x34 && result[2] == 0x56 && result[3] == 0x78); // Stop parsing at invalid value result = ParseHex("1234 invalid 1234"); BOOST_CHECK(result.size() == 2 && result[0] == 0x12 && result[1] == 0x34); } BOOST_AUTO_TEST_CASE(util_HexStr) { BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected + sizeof(ParseHex_expected)), "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0" "ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d" "578a4c702b6bf11d5f"); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected + 5, true), "04 67 8a fd b0"); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected + sizeof(ParseHex_expected), ParseHex_expected + sizeof(ParseHex_expected)), ""); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected + sizeof(ParseHex_expected), ParseHex_expected + sizeof(ParseHex_expected), true), ""); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected), ""); BOOST_CHECK_EQUAL(HexStr(ParseHex_expected, ParseHex_expected, true), ""); std::vector ParseHex_vec(ParseHex_expected, ParseHex_expected + 5); BOOST_CHECK_EQUAL(HexStr(ParseHex_vec, true), "04 67 8a fd b0"); BOOST_CHECK_EQUAL(HexStr(ParseHex_vec.rbegin(), ParseHex_vec.rend()), "b0fd8a6704"); BOOST_CHECK_EQUAL(HexStr(ParseHex_vec.rbegin(), ParseHex_vec.rend(), true), "b0 fd 8a 67 04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected), std::reverse_iterator(ParseHex_expected)), ""); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected), std::reverse_iterator(ParseHex_expected), true), ""); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 1), std::reverse_iterator(ParseHex_expected)), "04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 1), std::reverse_iterator(ParseHex_expected), true), "04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 5), std::reverse_iterator(ParseHex_expected)), "b0fd8a6704"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 5), std::reverse_iterator(ParseHex_expected), true), "b0 fd 8a 67 04"); BOOST_CHECK_EQUAL( HexStr(std::reverse_iterator(ParseHex_expected + 65), std::reverse_iterator(ParseHex_expected)), "5f1df16b2b704c8a578d0bbaf74d385cde12c11ee50455f3c438ef4c3fbcf649b6de61" "1feae06279a60939e028a8d65c10b73071a6f16719274855feb0fd8a6704"); } BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime) { BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); BOOST_CHECK_EQUAL(FormatISO8601DateTime(0), "1970-01-01T00:00:00Z"); BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0); BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0); BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777); auto time = GetSystemTimeInSeconds(); BOOST_CHECK_EQUAL(ParseISO8601DateTime(FormatISO8601DateTime(time)), time); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) { BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30"); } struct TestArgsManager : public ArgsManager { TestArgsManager() { m_network_only_args.clear(); } - std::map> &GetOverrideArgs() { - return m_override_args; - } - std::map> &GetConfigArgs() { - return m_config_args; - } void ReadConfigString(const std::string str_config) { std::istringstream streamConfig(str_config); { LOCK(cs_args); - m_config_args.clear(); + m_settings.ro_config.clear(); m_config_sections.clear(); } std::string error; BOOST_REQUIRE(ReadConfigStream(streamConfig, "", error)); } void SetNetworkOnlyArg(const std::string arg) { LOCK(cs_args); m_network_only_args.insert(arg); } void SetupArgs(const std::vector> &args) { for (const auto &arg : args) { AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS); } } using ArgsManager::cs_args; using ArgsManager::m_network; + using ArgsManager::m_settings; using ArgsManager::ReadConfigStream; }; BOOST_AUTO_TEST_CASE(util_ParseParameters) { TestArgsManager testArgs; const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY); const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY); const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY); const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY); const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; std::string error; + LOCK(testArgs.cs_args); testArgs.SetupArgs({a, b, ccc, d}); BOOST_CHECK(testArgs.ParseParameters(0, (char **)argv_test, error)); - BOOST_CHECK(testArgs.GetOverrideArgs().empty() && - testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && + testArgs.m_settings.ro_config.empty()); BOOST_CHECK(testArgs.ParseParameters(1, (char **)argv_test, error)); - BOOST_CHECK(testArgs.GetOverrideArgs().empty() && - testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && + testArgs.m_settings.ro_config.empty()); BOOST_CHECK(testArgs.ParseParameters(7, (char **)argv_test, error)); // expectation: -ignored is ignored (program name argument), // -a, -b and -ccc end up in map, -d ignored because it is after // a non-option argument (non-GNU option parsing) - BOOST_CHECK(testArgs.GetOverrideArgs().size() == 3 && - testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && + testArgs.m_settings.ro_config.empty()); BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc") && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d")); - BOOST_CHECK(testArgs.GetOverrideArgs().count("-a") && - testArgs.GetOverrideArgs().count("-b") && - testArgs.GetOverrideArgs().count("-ccc") && - !testArgs.GetOverrideArgs().count("f") && - !testArgs.GetOverrideArgs().count("-d")); - - BOOST_CHECK(testArgs.GetOverrideArgs()["-a"].size() == 1); - BOOST_CHECK(testArgs.GetOverrideArgs()["-a"].front() == ""); - BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].size() == 2); - BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].front() == "argument"); - BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].back() == "multiple"); + BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && + testArgs.m_settings.command_line_options.count("b") && + testArgs.m_settings.command_line_options.count("ccc") && + !testArgs.m_settings.command_line_options.count("f") && + !testArgs.m_settings.command_line_options.count("d")); + + BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1); + BOOST_CHECK( + testArgs.m_settings.command_line_options["a"].front().get_str() == ""); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2); + BOOST_CHECK( + testArgs.m_settings.command_line_options["ccc"].front().get_str() == + "argument"); + BOOST_CHECK( + testArgs.m_settings.command_line_options["ccc"].back().get_str() == + "multiple"); BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2); } BOOST_AUTO_TEST_CASE(util_ParseKeyValue) { { std::string key = "badarg"; std::string value; BOOST_CHECK(!ParseKeyValue(key, value)); } { std::string key = "badarg=v"; std::string value; BOOST_CHECK(!ParseKeyValue(key, value)); } { std::string key = "-a"; std::string value; BOOST_CHECK(ParseKeyValue(key, value)); BOOST_CHECK_EQUAL(key, "-a"); BOOST_CHECK_EQUAL(value, ""); } { std::string key = "-a=1"; std::string value; BOOST_CHECK(ParseKeyValue(key, value)); BOOST_CHECK_EQUAL(key, "-a"); BOOST_CHECK_EQUAL(value, "1"); } { std::string key = "--b"; std::string value; BOOST_CHECK(ParseKeyValue(key, value)); BOOST_CHECK_EQUAL(key, "-b"); BOOST_CHECK_EQUAL(value, ""); } { std::string key = "--b=abc"; std::string value; BOOST_CHECK(ParseKeyValue(key, value)); BOOST_CHECK_EQUAL(key, "-b"); BOOST_CHECK_EQUAL(value, "abc"); } } BOOST_AUTO_TEST_CASE(util_GetBoolArg) { TestArgsManager testArgs; const auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); const auto c = std::make_pair("-c", ArgsManager::ALLOW_BOOL); const auto d = std::make_pair("-d", ArgsManager::ALLOW_BOOL); const auto e = std::make_pair("-e", ArgsManager::ALLOW_BOOL); const auto f = std::make_pair("-f", ArgsManager::ALLOW_BOOL); const char *argv_test[] = {"ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; std::string error; + LOCK(testArgs.cs_args); testArgs.SetupArgs({a, b, c, d, e, f}); BOOST_CHECK(testArgs.ParseParameters(7, (char **)argv_test, error)); // Each letter should be set. for (const char opt : "abcdef") { BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt); } // Nothing else should be in the map - BOOST_CHECK(testArgs.GetOverrideArgs().size() == 6 && - testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 && + testArgs.m_settings.ro_config.empty()); // The -no prefix should get stripped on the way in. BOOST_CHECK(!testArgs.IsArgSet("-nob")); // The -b option is flagged as negated, and nothing else is BOOST_CHECK(testArgs.IsArgNegated("-b")); BOOST_CHECK(!testArgs.IsArgNegated("-a")); // Check expected values. BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true); BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false); BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false); BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true); BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false); BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false); } BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) { // Test some awful edge cases that hopefully no user will ever exercise. TestArgsManager testArgs; // Params test const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_BOOL); const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_BOOL); const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; testArgs.SetupArgs({foo, bar}); std::string error; BOOST_CHECK(testArgs.ParseParameters(4, (char **)argv_test, error)); // This was passed twice, second one overrides the negative setting. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == ""); // A double negative is a positive, and not marked as negated. BOOST_CHECK(!testArgs.IsArgNegated("-bar")); BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); // Config test const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n"; BOOST_CHECK(testArgs.ParseParameters(1, (char **)argv_test, error)); testArgs.ReadConfigString(conf_test); // This was passed twice, second one overrides the negative setting, // and the value. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1"); // A double negative is a positive, and does not count as negated. BOOST_CHECK(!testArgs.IsArgNegated("-bar")); BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1"); // Combined test const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"}; const char *combo_test_conf = "foo=1\nnobar=1\n"; BOOST_CHECK(testArgs.ParseParameters(3, (char **)combo_test_args, error)); testArgs.ReadConfigString(combo_test_conf); // Command line overrides, but doesn't erase old setting BOOST_CHECK(testArgs.IsArgNegated("-foo")); BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0"); BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0); // Command line overrides, but doesn't erase old setting BOOST_CHECK(!testArgs.IsArgNegated("-bar")); BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == ""); BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1 && testArgs.GetArgs("-bar").front() == ""); } BOOST_AUTO_TEST_CASE(util_ReadConfigStream) { const char *str_config = "a=\n" "b=1\n" "ccc=argument\n" "ccc=multiple\n" "d=e\n" "nofff=1\n" "noggg=0\n" "h=1\n" "noh=1\n" "noi=1\n" "i=1\n" "sec1.ccc=extend1\n" "\n" "[sec1]\n" "ccc=extend2\n" "d=eee\n" "h=1\n" "[sec2]\n" "ccc=extend3\n" "iii=2\n"; TestArgsManager test_args; + LOCK(test_args.cs_args); const auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_STRING); const auto d = std::make_pair("-d", ArgsManager::ALLOW_STRING); const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY); const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_BOOL); const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_BOOL); const auto h = std::make_pair("-h", ArgsManager::ALLOW_BOOL); const auto i = std::make_pair("-i", ArgsManager::ALLOW_BOOL); const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_INT); test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii}); test_args.ReadConfigString(str_config); // expectation: a, b, ccc, d, fff, ggg, h, i end up in map // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii - BOOST_CHECK(test_args.GetOverrideArgs().empty()); - BOOST_CHECK(test_args.GetConfigArgs().size() == 13); - - BOOST_CHECK(test_args.GetConfigArgs().count("-a") && - test_args.GetConfigArgs().count("-b") && - test_args.GetConfigArgs().count("-ccc") && - test_args.GetConfigArgs().count("-d") && - test_args.GetConfigArgs().count("-fff") && - test_args.GetConfigArgs().count("-ggg") && - test_args.GetConfigArgs().count("-h") && - test_args.GetConfigArgs().count("-i")); - BOOST_CHECK(test_args.GetConfigArgs().count("-sec1.ccc") && - test_args.GetConfigArgs().count("-sec1.h") && - test_args.GetConfigArgs().count("-sec2.ccc") && - test_args.GetConfigArgs().count("-sec2.iii")); + BOOST_CHECK(test_args.m_settings.command_line_options.empty()); + BOOST_CHECK(test_args.m_settings.ro_config.size() == 3); + BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8); + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3); + BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2); + + BOOST_CHECK(test_args.m_settings.ro_config[""].count("a") && + test_args.m_settings.ro_config[""].count("b") && + test_args.m_settings.ro_config[""].count("ccc") && + test_args.m_settings.ro_config[""].count("d") && + test_args.m_settings.ro_config[""].count("fff") && + test_args.m_settings.ro_config[""].count("ggg") && + test_args.m_settings.ro_config[""].count("h") && + test_args.m_settings.ro_config[""].count("i")); + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc") && + test_args.m_settings.ro_config["sec1"].count("h") && + test_args.m_settings.ro_config["sec2"].count("ccc") && + test_args.m_settings.ro_config["sec2"].count("iii")); BOOST_CHECK(test_args.IsArgSet("-a") && test_args.IsArgSet("-b") && test_args.IsArgSet("-ccc") && test_args.IsArgSet("-d") && test_args.IsArgSet("-fff") && test_args.IsArgSet("-ggg") && test_args.IsArgSet("-h") && test_args.IsArgSet("-i") && !test_args.IsArgSet("-zzz") && !test_args.IsArgSet("-iii")); BOOST_CHECK(test_args.GetArg("-a", "xxx") == "" && test_args.GetArg("-b", "xxx") == "1" && test_args.GetArg("-ccc", "xxx") == "argument" && test_args.GetArg("-d", "xxx") == "e" && test_args.GetArg("-fff", "xxx") == "0" && test_args.GetArg("-ggg", "xxx") == "1" && test_args.GetArg("-h", "xxx") == "0" && test_args.GetArg("-i", "xxx") == "1" && test_args.GetArg("-zzz", "xxx") == "xxx" && test_args.GetArg("-iii", "xxx") == "xxx"); for (const bool def : {false, true}) { BOOST_CHECK(test_args.GetBoolArg("-a", def) && test_args.GetBoolArg("-b", def) && !test_args.GetBoolArg("-ccc", def) && !test_args.GetBoolArg("-d", def) && !test_args.GetBoolArg("-fff", def) && test_args.GetBoolArg("-ggg", def) && !test_args.GetBoolArg("-h", def) && test_args.GetBoolArg("-i", def) && test_args.GetBoolArg("-zzz", def) == def && test_args.GetBoolArg("-iii", def) == def); } BOOST_CHECK(test_args.GetArgs("-a").size() == 1 && test_args.GetArgs("-a").front() == ""); BOOST_CHECK(test_args.GetArgs("-b").size() == 1 && test_args.GetArgs("-b").front() == "1"); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2 && test_args.GetArgs("-ccc").front() == "argument" && test_args.GetArgs("-ccc").back() == "multiple"); BOOST_CHECK(test_args.GetArgs("-fff").size() == 0); BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0); BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1 && test_args.GetArgs("-ggg").front() == "1"); BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0); BOOST_CHECK(test_args.GetArgs("-h").size() == 0); BOOST_CHECK(test_args.GetArgs("-noh").size() == 0); BOOST_CHECK(test_args.GetArgs("-i").size() == 1 && test_args.GetArgs("-i").front() == "1"); BOOST_CHECK(test_args.GetArgs("-noi").size() == 0); BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0); BOOST_CHECK(!test_args.IsArgNegated("-a")); BOOST_CHECK(!test_args.IsArgNegated("-b")); BOOST_CHECK(!test_args.IsArgNegated("-ccc")); BOOST_CHECK(!test_args.IsArgNegated("-d")); BOOST_CHECK(test_args.IsArgNegated("-fff")); BOOST_CHECK(!test_args.IsArgNegated("-ggg")); // last setting takes precedence BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence BOOST_CHECK(!test_args.IsArgNegated("-i")); BOOST_CHECK(!test_args.IsArgNegated("-zzz")); // Test sections work test_args.SelectConfigNetwork("sec1"); // same as original BOOST_CHECK(test_args.GetArg("-a", "xxx") == "" && test_args.GetArg("-b", "xxx") == "1" && test_args.GetArg("-fff", "xxx") == "0" && test_args.GetArg("-ggg", "xxx") == "1" && test_args.GetArg("-zzz", "xxx") == "xxx" && test_args.GetArg("-iii", "xxx") == "xxx"); // d is overridden BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); // section-specific setting BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); // section takes priority for multiple values BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1"); // check multiple values works const std::vector sec1_ccc_expected = {"extend1", "extend2", "argument", "multiple"}; const auto &sec1_ccc_res = test_args.GetArgs("-ccc"); BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end()); test_args.SelectConfigNetwork("sec2"); // same as original BOOST_CHECK(test_args.GetArg("-a", "xxx") == "" && test_args.GetArg("-b", "xxx") == "1" && test_args.GetArg("-d", "xxx") == "e" && test_args.GetArg("-fff", "xxx") == "0" && test_args.GetArg("-ggg", "xxx") == "1" && test_args.GetArg("-zzz", "xxx") == "xxx" && test_args.GetArg("-h", "xxx") == "0"); // section-specific setting BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2"); // section takes priority for multiple values BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3"); // check multiple values works const std::vector sec2_ccc_expected = {"extend3", "argument", "multiple"}; const auto &sec2_ccc_res = test_args.GetArgs("-ccc"); BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end()); // Test section only options test_args.SetNetworkOnlyArg("-d"); test_args.SetNetworkOnlyArg("-ccc"); test_args.SetNetworkOnlyArg("-h"); test_args.SelectConfigNetwork(CBaseChainParams::MAIN); BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e"); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); test_args.SelectConfigNetwork("sec1"); BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee"); BOOST_CHECK(test_args.GetArgs("-d").size() == 1); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2); BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1"); test_args.SelectConfigNetwork("sec2"); BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx"); BOOST_CHECK(test_args.GetArgs("-d").size() == 0); BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1); BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0"); } BOOST_AUTO_TEST_CASE(util_GetArg) { TestArgsManager testArgs; - testArgs.GetOverrideArgs().clear(); - testArgs.GetOverrideArgs()["strtest1"] = {"string..."}; + LOCK(testArgs.cs_args); + testArgs.m_settings.command_line_options.clear(); + testArgs.m_settings.command_line_options["strtest1"] = {"string..."}; // strtest2 undefined on purpose - testArgs.GetOverrideArgs()["inttest1"] = {"12345"}; - testArgs.GetOverrideArgs()["inttest2"] = {"81985529216486895"}; + testArgs.m_settings.command_line_options["inttest1"] = {"12345"}; + testArgs.m_settings.command_line_options["inttest2"] = { + "81985529216486895"}; // inttest3 undefined on purpose - testArgs.GetOverrideArgs()["booltest1"] = {""}; + testArgs.m_settings.command_line_options["booltest1"] = {""}; // booltest2 undefined on purpose - testArgs.GetOverrideArgs()["booltest3"] = {"0"}; - testArgs.GetOverrideArgs()["booltest4"] = {"1"}; + testArgs.m_settings.command_line_options["booltest3"] = {"0"}; + testArgs.m_settings.command_line_options["booltest4"] = {"1"}; // priorities - testArgs.GetOverrideArgs()["pritest1"] = {"a", "b"}; - testArgs.GetConfigArgs()["pritest2"] = {"a", "b"}; - testArgs.GetOverrideArgs()["pritest3"] = {"a"}; - testArgs.GetConfigArgs()["pritest3"] = {"b"}; - testArgs.GetOverrideArgs()["pritest4"] = {"a", "b"}; - testArgs.GetConfigArgs()["pritest4"] = {"c", "d"}; + testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"}; + testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"}; + testArgs.m_settings.command_line_options["pritest3"] = {"a"}; + testArgs.m_settings.ro_config[""]["pritest3"] = {"b"}; + testArgs.m_settings.command_line_options["pritest4"] = {"a", "b"}; + testArgs.m_settings.ro_config[""]["pritest4"] = {"c", "d"}; BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.GetArg("inttest1", -1), 12345); BOOST_CHECK_EQUAL(testArgs.GetArg("inttest2", -1), 81985529216486895LL); BOOST_CHECK_EQUAL(testArgs.GetArg("inttest3", -1), -1); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b"); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a"); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a"); BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b"); } BOOST_AUTO_TEST_CASE(util_ClearForcedArg) { TestArgsManager testArgs; - - // Clear single string arg - testArgs.GetOverrideArgs()["strtest1"] = {"string..."}; - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); - testArgs.ClearForcedArg("strtest1"); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "default"); - - // Clear boolean arg - testArgs.GetOverrideArgs()["booltest1"] = {"1"}; - BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); - testArgs.ClearForcedArg("booltest1"); - BOOST_CHECK_EQUAL(testArgs.GetArg("booltest1", false), false); - - // Clear multi arg. - testArgs.GetOverrideArgs()["strtest2"].push_back("string..."); - testArgs.GetOverrideArgs()["strtest2"].push_back("...gnirts"); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 2); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "...gnirts"); - testArgs.ClearForcedArg("strtest2"); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 0); - - // Clear exlusively overridden args - testArgs.GetOverrideArgs()["strtest3"].push_back("cli string..."); - testArgs.GetOverrideArgs()["strtest3"].push_back("...gnirts ilc"); - testArgs.GetConfigArgs()["strtest3"].push_back("string..."); - testArgs.GetConfigArgs()["strtest3"].push_back("...gnirts"); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest3", "default"), "...gnirts ilc"); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").size(), 4); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").front(), "cli string..."); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").back(), "...gnirts"); - testArgs.ClearForcedArg("strtest3"); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").size(), 2); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").front(), "string..."); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest3").back(), "...gnirts"); + LOCK(testArgs.cs_args); + + // Clear command line arg + testArgs.m_settings.command_line_options["cmdarg"] = {"cmdval"}; + BOOST_CHECK_EQUAL(testArgs.GetArg("cmdarg", "default"), "cmdval"); + testArgs.ClearForcedArg("cmdarg"); + BOOST_CHECK_EQUAL(testArgs.GetArg("cmdarg", "default"), "cmdval"); + + // Clear config arg + testArgs.m_settings.ro_config[""]["configarg"] = {"configval"}; + BOOST_CHECK_EQUAL(testArgs.GetArg("configarg", "default"), "configval"); + testArgs.ClearForcedArg("configarg"); + BOOST_CHECK_EQUAL(testArgs.GetArg("configarg", "default"), "configval"); + + // Clear forced arg + testArgs.m_settings.forced_settings["forcedarg"] = {"forcedval"}; + BOOST_CHECK_EQUAL(testArgs.GetArg("forcedarg", "default"), "forcedval"); + testArgs.ClearForcedArg("forcedarg"); + BOOST_CHECK_EQUAL(testArgs.GetArg("forcedarg", "default"), "default"); } BOOST_AUTO_TEST_CASE(util_SetArg) { TestArgsManager testArgs; // SoftSetArg BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.SoftSetArg("strtest1", "string..."), true); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest1").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest1").front(), "string..."); BOOST_CHECK_EQUAL(testArgs.SoftSetArg("strtest1", "...gnirts"), false); testArgs.ClearForcedArg("strtest1"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.SoftSetArg("strtest1", "...gnirts"), true); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "...gnirts"); // SoftSetBoolArg BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), false); BOOST_CHECK_EQUAL(testArgs.SoftSetBoolArg("booltest1", true), true); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true); BOOST_CHECK_EQUAL(testArgs.SoftSetBoolArg("booltest1", false), false); testArgs.ClearForcedArg("booltest1"); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", true), true); BOOST_CHECK_EQUAL(testArgs.SoftSetBoolArg("booltest1", false), true); BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", true), false); // ForceSetArg BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); testArgs.ForceSetArg("strtest2", "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); testArgs.ForceSetArg("strtest2", "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "...gnirts"); // ForceSetMultiArg - testArgs.ForceSetMultiArg("strtest2", "string..."); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "string..."); + testArgs.ForceSetMultiArg("strtest2", {"string...", "...gnirts"}); + BOOST_CHECK_THROW(testArgs.GetArg("strtest2", "default"), + std::runtime_error); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 2); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "...gnirts"); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "string..."); + BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); + BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "...gnirts"); testArgs.ClearForcedArg("strtest2"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 0); - testArgs.ForceSetMultiArg("strtest2", "string..."); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "string..."); + + // If there are multi args, ForceSetArg should erase them + testArgs.ForceSetMultiArg("strtest2", {"string..."}); + BOOST_CHECK_THROW(testArgs.GetArg("strtest2", "default"), + std::runtime_error); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); - testArgs.ForceSetMultiArg("strtest2", "one more thing..."); - BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), - "one more thing..."); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 2); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "string..."); - BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").back(), "one more thing..."); - // If there are multi args, ForceSetArg should erase them testArgs.ForceSetArg("strtest2", "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "...gnirts"); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").size(), 1); BOOST_CHECK_EQUAL(testArgs.GetArgs("strtest2").front(), "...gnirts"); } BOOST_AUTO_TEST_CASE(util_GetChainName) { TestArgsManager test_args; const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_BOOL); const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_BOOL); test_args.SetupArgs({testnet, regtest}); const char *argv_testnet[] = {"cmd", "-testnet"}; const char *argv_regtest[] = {"cmd", "-regtest"}; const char *argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"}; const char *argv_both[] = {"cmd", "-testnet", "-regtest"}; // equivalent to "-testnet" // regtest in testnet section is ignored const char *testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; std::string error; BOOST_CHECK(test_args.ParseParameters(0, (char **)argv_testnet, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_testnet, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_regtest, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); BOOST_CHECK(test_args.ParseParameters(3, (char **)argv_test_no_reg, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(3, (char **)argv_both, error)); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); BOOST_CHECK(test_args.ParseParameters(0, (char **)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_regtest, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); BOOST_CHECK(test_args.ParseParameters(3, (char **)argv_test_no_reg, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(3, (char **)argv_both, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); // check setting the network to test (and thus making // [test] regtest=1 potentially relevant) doesn't break things test_args.SelectConfigNetwork("test"); BOOST_CHECK(test_args.ParseParameters(0, (char **)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_regtest, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); BOOST_CHECK(test_args.ParseParameters(2, (char **)argv_test_no_reg, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); BOOST_CHECK(test_args.ParseParameters(3, (char **)argv_both, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); } // Test different ways settings can be merged, and verify results. This test can // be used to confirm that updates to settings code don't change behavior // unintentionally. // // The test covers: // // - Combining different setting actions. Possible actions are: configuring a // setting, negating a setting (adding "-no" prefix), and configuring/negating // settings in a network section (adding "main." or "test." prefixes). // // - Combining settings from command line arguments and a config file. // // - Combining SoftSet and ForceSet calls. // // - Testing "main" and "test" network values to make sure settings from network // sections are applied and to check for mainnet-specific behaviors like // inheriting settings from the default section. // // - Testing network-specific settings like "-wallet", that may be ignored // outside a network section, and non-network specific settings like "-server" // that aren't sensitive to the network. // struct ArgsMergeTestingSetup : public BasicTestingSetup { //! Max number of actions to sequence together. Can decrease this when //! debugging to make test results easier to understand. static constexpr int MAX_ACTIONS = 3; enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE }; using ActionList = Action[MAX_ACTIONS]; //! Enumerate all possible test configurations. template void ForEachMergeSetup(Fn &&fn) { ActionList arg_actions = {}; ForEachNoDup(arg_actions, SET, SECTION_NEGATE, [&] { ActionList conf_actions = {}; ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] { for (bool soft_set : {false, true}) { for (bool force_set : {false, true}) { for (const std::string §ion : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) { for (const std::string &network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) { for (bool net_specific : {false, true}) { fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific); } } } } } }); }); } //! Translate actions into a list of = setting strings. std::vector GetValues(const ActionList &actions, const std::string §ion, const std::string &name, const std::string &value_prefix) { std::vector values; int suffix = 0; for (Action action : actions) { if (action == NONE) { break; } std::string prefix; if (action == SECTION_SET || action == SECTION_NEGATE) { prefix = section + "."; } if (action == SET || action == SECTION_SET) { for (int i = 0; i < 2; ++i) { values.push_back(prefix + name + "=" + value_prefix + std::to_string(++suffix)); } } if (action == NEGATE || action == SECTION_NEGATE) { values.push_back(prefix + "no" + name + "=1"); } } return values; } }; // Regression test covering different ways config settings can be merged. The // test parses and merges settings, representing the results as strings that get // compared against an expected hash. To debug, the result strings can be dumped // to a file (see comments below). BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup) { CHash256 out_sha; FILE *out_file = nullptr; if (const char *out_path = getenv("ARGS_MERGE_TEST_OUT")) { out_file = fsbridge::fopen(out_path, "w"); if (!out_file) { throw std::system_error(errno, std::generic_category(), "fopen failed"); } } ForEachMergeSetup([&](const ActionList &arg_actions, const ActionList &conf_actions, bool soft_set, bool force_set, const std::string §ion, const std::string &network, bool net_specific) { TestArgsManager parser; LOCK(parser.cs_args); std::string desc = "net="; desc += network; parser.m_network = network; const std::string &name = net_specific ? "wallet" : "server"; const std::string key = "-" + name; parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); if (net_specific) { parser.SetNetworkOnlyArg(key); } auto args = GetValues(arg_actions, section, name, "a"); std::vector argv = {"ignored"}; for (auto &arg : args) { arg.insert(0, "-"); desc += " "; desc += arg; argv.push_back(arg.c_str()); } std::string error; BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error)); BOOST_CHECK_EQUAL(error, ""); std::string conf; for (auto &conf_val : GetValues(conf_actions, section, name, "c")) { desc += " "; desc += conf_val; conf += conf_val; conf += "\n"; } std::istringstream conf_stream(conf); BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); BOOST_CHECK_EQUAL(error, ""); if (soft_set) { desc += " soft"; parser.SoftSetArg(key, "soft1"); parser.SoftSetArg(key, "soft2"); } if (force_set) { desc += " force"; parser.ForceSetArg(key, "force1"); parser.ForceSetArg(key, "force2"); } desc += " || "; if (!parser.IsArgSet(key)) { desc += "unset"; BOOST_CHECK(!parser.IsArgNegated(key)); BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default"); BOOST_CHECK(parser.GetArgs(key).empty()); } else if (parser.IsArgNegated(key)) { desc += "negated"; BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0"); BOOST_CHECK(parser.GetArgs(key).empty()); } else { desc += parser.GetArg(key, "default"); desc += " |"; for (const auto &arg : parser.GetArgs(key)) { desc += " "; desc += arg; } } std::set ignored = parser.GetUnsuitableSectionOnlyArgs(); if (!ignored.empty()) { desc += " | ignored"; for (const auto &arg : ignored) { desc += " "; desc += arg; } } desc += "\n"; out_sha.Write((const uint8_t *)desc.data(), desc.size()); if (out_file) { BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); } }); if (out_file) { if (fclose(out_file)) { throw std::system_error(errno, std::generic_category(), "fclose failed"); } out_file = nullptr; } uint8_t out_sha_bytes[CSHA256::OUTPUT_SIZE]; out_sha.Finalize(out_sha_bytes); std::string out_sha_hex = HexStr(std::begin(out_sha_bytes), std::end(out_sha_bytes)); // If check below fails, should manually dump the results with: // // ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin // --run_test=util_tests/util_ArgsMerge // // And verify diff against previous results to make sure the changes are // expected. // // Results file is formatted like: // // || | | // BOOST_CHECK_EQUAL( out_sha_hex, "b835eef5977d69114eb039a976201f8c7121f34fe2b7ea2b73cafb516e5c9dc8"); } // Similar test as above, but for ArgsManager::GetChainName function. struct ChainMergeTestingSetup : public BasicTestingSetup { static constexpr int MAX_ACTIONS = 2; enum Action { NONE, ENABLE_TEST, DISABLE_TEST, NEGATE_TEST, ENABLE_REG, DISABLE_REG, NEGATE_REG }; using ActionList = Action[MAX_ACTIONS]; //! Enumerate all possible test configurations. template void ForEachMergeSetup(Fn &&fn) { ActionList arg_actions = {}; ForEachNoDup(arg_actions, ENABLE_TEST, NEGATE_REG, [&] { ActionList conf_actions = {}; ForEachNoDup(conf_actions, ENABLE_TEST, NEGATE_REG, [&] { fn(arg_actions, conf_actions); }); }); } }; BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) { CHash256 out_sha; FILE *out_file = nullptr; if (const char *out_path = getenv("CHAIN_MERGE_TEST_OUT")) { out_file = fsbridge::fopen(out_path, "w"); if (!out_file) { throw std::system_error(errno, std::generic_category(), "fopen failed"); } } ForEachMergeSetup([&](const ActionList &arg_actions, const ActionList &conf_actions) { TestArgsManager parser; LOCK(parser.cs_args); parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); auto arg = [](Action action) -> const char * { switch (action) { case ENABLE_TEST: return "-testnet=1"; case DISABLE_TEST: return "-testnet=0"; case NEGATE_TEST: return "-notestnet=1"; case ENABLE_REG: return "-regtest=1"; case DISABLE_REG: return "-regtest=0"; case NEGATE_REG: return "-noregtest=1"; default: return nullptr; } }; std::string desc; std::vector argv = {"ignored"}; for (Action action : arg_actions) { const char *argstr = arg(action); if (!argstr) { break; } argv.push_back(argstr); desc += " "; desc += argv.back(); } std::string error; BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error)); BOOST_CHECK_EQUAL(error, ""); std::string conf; for (Action action : conf_actions) { const char *argstr = arg(action); if (!argstr) { break; } desc += " "; desc += argstr + 1; conf += argstr + 1; } std::istringstream conf_stream(conf); BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); BOOST_CHECK_EQUAL(error, ""); desc += " || "; try { desc += parser.GetChainName(); } catch (const std::runtime_error &e) { desc += "error: "; desc += e.what(); } desc += "\n"; out_sha.Write((const uint8_t *)desc.data(), desc.size()); if (out_file) { BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); } }); if (out_file) { if (fclose(out_file)) { throw std::system_error(errno, std::generic_category(), "fclose failed"); } out_file = nullptr; } uint8_t out_sha_bytes[CSHA256::OUTPUT_SIZE]; out_sha.Finalize(out_sha_bytes); std::string out_sha_hex = HexStr(std::begin(out_sha_bytes), std::end(out_sha_bytes)); // If check below fails, should manually dump the results with: // // CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin // --run_test=util_tests/util_ChainMerge // // And verify diff against previous results to make sure the changes are // expected. // // Results file is formatted like: // // || BOOST_CHECK_EQUAL( out_sha_hex, "94b4ad55c8ac639a56b93e36f7e32e4c611fd7d7dd7b2be6a71707b1eadcaec7"); } BOOST_AUTO_TEST_CASE(util_FormatMoney) { BOOST_CHECK_EQUAL(FormatMoney(Amount::zero()), "0.00"); BOOST_CHECK_EQUAL(FormatMoney(123456789 * (COIN / 10000)), "12345.6789"); BOOST_CHECK_EQUAL(FormatMoney(-1 * COIN), "-1.00"); BOOST_CHECK_EQUAL(FormatMoney(100000000 * COIN), "100000000.00"); BOOST_CHECK_EQUAL(FormatMoney(10000000 * COIN), "10000000.00"); BOOST_CHECK_EQUAL(FormatMoney(1000000 * COIN), "1000000.00"); BOOST_CHECK_EQUAL(FormatMoney(100000 * COIN), "100000.00"); BOOST_CHECK_EQUAL(FormatMoney(10000 * COIN), "10000.00"); BOOST_CHECK_EQUAL(FormatMoney(1000 * COIN), "1000.00"); BOOST_CHECK_EQUAL(FormatMoney(100 * COIN), "100.00"); BOOST_CHECK_EQUAL(FormatMoney(10 * COIN), "10.00"); BOOST_CHECK_EQUAL(FormatMoney(COIN), "1.00"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 10), "0.10"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 100), "0.01"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 1000), "0.001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 10000), "0.0001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 100000), "0.00001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 1000000), "0.000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 10000000), "0.0000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN / 100000000), "0.00000001"); } BOOST_AUTO_TEST_CASE(util_ParseMoney) { Amount ret = Amount::zero(); BOOST_CHECK(ParseMoney("0.0", ret)); BOOST_CHECK_EQUAL(ret, Amount::zero()); BOOST_CHECK(ParseMoney("12345.6789", ret)); BOOST_CHECK_EQUAL(ret, 123456789 * (COIN / 10000)); BOOST_CHECK(ParseMoney("100000000.00", ret)); BOOST_CHECK_EQUAL(ret, 100000000 * COIN); BOOST_CHECK(ParseMoney("10000000.00", ret)); BOOST_CHECK_EQUAL(ret, 10000000 * COIN); BOOST_CHECK(ParseMoney("1000000.00", ret)); BOOST_CHECK_EQUAL(ret, 1000000 * COIN); BOOST_CHECK(ParseMoney("100000.00", ret)); BOOST_CHECK_EQUAL(ret, 100000 * COIN); BOOST_CHECK(ParseMoney("10000.00", ret)); BOOST_CHECK_EQUAL(ret, 10000 * COIN); BOOST_CHECK(ParseMoney("1000.00", ret)); BOOST_CHECK_EQUAL(ret, 1000 * COIN); BOOST_CHECK(ParseMoney("100.00", ret)); BOOST_CHECK_EQUAL(ret, 100 * COIN); BOOST_CHECK(ParseMoney("10.00", ret)); BOOST_CHECK_EQUAL(ret, 10 * COIN); BOOST_CHECK(ParseMoney("1.00", ret)); BOOST_CHECK_EQUAL(ret, COIN); BOOST_CHECK(ParseMoney("1", ret)); BOOST_CHECK_EQUAL(ret, COIN); BOOST_CHECK(ParseMoney("0.1", ret)); BOOST_CHECK_EQUAL(ret, COIN / 10); BOOST_CHECK(ParseMoney("0.01", ret)); BOOST_CHECK_EQUAL(ret, COIN / 100); BOOST_CHECK(ParseMoney("0.001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 1000); BOOST_CHECK(ParseMoney("0.0001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 10000); BOOST_CHECK(ParseMoney("0.00001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 100000); BOOST_CHECK(ParseMoney("0.000001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 1000000); BOOST_CHECK(ParseMoney("0.0000001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 10000000); BOOST_CHECK(ParseMoney("0.00000001", ret)); BOOST_CHECK_EQUAL(ret, COIN / 100000000); // Attempted 63 bit overflow should fail BOOST_CHECK(!ParseMoney("92233720368.54775808", ret)); // Parsing negative amounts must fail BOOST_CHECK(!ParseMoney("-1", ret)); } BOOST_AUTO_TEST_CASE(util_IsHex) { BOOST_CHECK(IsHex("00")); BOOST_CHECK(IsHex("00112233445566778899aabbccddeeffAABBCCDDEEFF")); BOOST_CHECK(IsHex("ff")); BOOST_CHECK(IsHex("FF")); BOOST_CHECK(!IsHex("")); BOOST_CHECK(!IsHex("0")); BOOST_CHECK(!IsHex("a")); BOOST_CHECK(!IsHex("eleven")); BOOST_CHECK(!IsHex("00xx00")); BOOST_CHECK(!IsHex("0x0000")); } BOOST_AUTO_TEST_CASE(util_IsHexNumber) { BOOST_CHECK(IsHexNumber("0x0")); BOOST_CHECK(IsHexNumber("0")); BOOST_CHECK(IsHexNumber("0x10")); BOOST_CHECK(IsHexNumber("10")); BOOST_CHECK(IsHexNumber("0xff")); BOOST_CHECK(IsHexNumber("ff")); BOOST_CHECK(IsHexNumber("0xFfa")); BOOST_CHECK(IsHexNumber("Ffa")); BOOST_CHECK(IsHexNumber("0x00112233445566778899aabbccddeeffAABBCCDDEEFF")); BOOST_CHECK(IsHexNumber("00112233445566778899aabbccddeeffAABBCCDDEEFF")); BOOST_CHECK(!IsHexNumber("")); // empty string not allowed BOOST_CHECK(!IsHexNumber("0x")); // empty string after prefix not allowed BOOST_CHECK(!IsHexNumber("0x0 ")); // no spaces at end, BOOST_CHECK(!IsHexNumber(" 0x0")); // or beginning, BOOST_CHECK(!IsHexNumber("0x 0")); // or middle, BOOST_CHECK(!IsHexNumber(" ")); // etc. BOOST_CHECK(!IsHexNumber("0x0ga")); // invalid character BOOST_CHECK(!IsHexNumber("x0")); // broken prefix BOOST_CHECK(!IsHexNumber("0x0x00")); // two prefixes not allowed } BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) { SeedInsecureRand(true); for (int mod = 2; mod < 11; mod++) { int mask = 1; // Really rough binomial confidence approximation. int err = 30 * 10000. / mod * sqrt((1. / mod * (1 - 1. / mod)) / 10000.); // mask is 2^ceil(log2(mod))-1 while (mask < mod - 1) { mask = (mask << 1) + 1; } int count = 0; // How often does it get a zero from the uniform range [0,mod)? for (int i = 0; i < 10000; i++) { uint32_t rval; do { rval = InsecureRand32() & mask; } while (rval >= uint32_t(mod)); count += rval == 0; } BOOST_CHECK(count <= 10000 / mod + err); BOOST_CHECK(count >= 10000 / mod - err); } } BOOST_AUTO_TEST_CASE(util_TimingResistantEqual) { BOOST_CHECK(TimingResistantEqual(std::string(""), std::string(""))); BOOST_CHECK(!TimingResistantEqual(std::string("abc"), std::string(""))); BOOST_CHECK(!TimingResistantEqual(std::string(""), std::string("abc"))); BOOST_CHECK(!TimingResistantEqual(std::string("a"), std::string("aa"))); BOOST_CHECK(!TimingResistantEqual(std::string("aa"), std::string("a"))); BOOST_CHECK(TimingResistantEqual(std::string("abc"), std::string("abc"))); BOOST_CHECK(!TimingResistantEqual(std::string("abc"), std::string("aba"))); } /* Test strprintf formatting directives. * Put a string before and after to ensure sanity of element sizes on stack. */ #define B "check_prefix" #define E "check_postfix" BOOST_AUTO_TEST_CASE(strprintf_numbers) { int64_t s64t = -9223372036854775807LL; /* signed 64 bit test value */ uint64_t u64t = 18446744073709551615ULL; /* unsigned 64 bit test value */ BOOST_CHECK(strprintf("%s %d %s", B, s64t, E) == B " -9223372036854775807 " E); BOOST_CHECK(strprintf("%s %u %s", B, u64t, E) == B " 18446744073709551615 " E); BOOST_CHECK(strprintf("%s %x %s", B, u64t, E) == B " ffffffffffffffff " E); size_t st = 12345678; /* unsigned size_t test value */ ssize_t sst = -12345678; /* signed size_t test value */ BOOST_CHECK(strprintf("%s %d %s", B, sst, E) == B " -12345678 " E); BOOST_CHECK(strprintf("%s %u %s", B, st, E) == B " 12345678 " E); BOOST_CHECK(strprintf("%s %x %s", B, st, E) == B " bc614e " E); ptrdiff_t pt = 87654321; /* positive ptrdiff_t test value */ ptrdiff_t spt = -87654321; /* negative ptrdiff_t test value */ BOOST_CHECK(strprintf("%s %d %s", B, spt, E) == B " -87654321 " E); BOOST_CHECK(strprintf("%s %u %s", B, pt, E) == B " 87654321 " E); BOOST_CHECK(strprintf("%s %x %s", B, pt, E) == B " 5397fb1 " E); } #undef B #undef E /* Check for mingw/wine issue #3494 * Remove this test before time.ctime(0xffffffff) == 'Sun Feb 7 07:28:15 2106' */ BOOST_AUTO_TEST_CASE(gettime) { BOOST_CHECK((GetTime() & ~0xFFFFFFFFLL) == 0); } BOOST_AUTO_TEST_CASE(util_time_GetTime) { SetMockTime(111); // Check that mock time does not change after a sleep for (const auto &num_sleep : {0, 1}) { MilliSleep(num_sleep); BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter BOOST_CHECK_EQUAL(111, GetTime().count()); BOOST_CHECK_EQUAL(111000, GetTime().count()); BOOST_CHECK_EQUAL(111000000, GetTime().count()); } SetMockTime(0); // Check that system time changes after a sleep const auto ms_0 = GetTime(); const auto us_0 = GetTime(); MilliSleep(1); BOOST_CHECK(ms_0 < GetTime()); BOOST_CHECK(us_0 < GetTime()); } BOOST_AUTO_TEST_CASE(test_IsDigit) { BOOST_CHECK_EQUAL(IsDigit('0'), true); BOOST_CHECK_EQUAL(IsDigit('1'), true); BOOST_CHECK_EQUAL(IsDigit('8'), true); BOOST_CHECK_EQUAL(IsDigit('9'), true); BOOST_CHECK_EQUAL(IsDigit('0' - 1), false); BOOST_CHECK_EQUAL(IsDigit('9' + 1), false); BOOST_CHECK_EQUAL(IsDigit(0), false); BOOST_CHECK_EQUAL(IsDigit(1), false); BOOST_CHECK_EQUAL(IsDigit(8), false); BOOST_CHECK_EQUAL(IsDigit(9), false); } BOOST_AUTO_TEST_CASE(test_ParseInt32) { int32_t n; // Valid values BOOST_CHECK(ParseInt32("1234", nullptr)); BOOST_CHECK(ParseInt32("0", &n) && n == 0); BOOST_CHECK(ParseInt32("1234", &n) && n == 1234); BOOST_CHECK(ParseInt32("01234", &n) && n == 1234); // no octal BOOST_CHECK(ParseInt32("2147483647", &n) && n == 2147483647); // (-2147483647 - 1) equals INT_MIN BOOST_CHECK(ParseInt32("-2147483648", &n) && n == (-2147483647 - 1)); BOOST_CHECK(ParseInt32("-1234", &n) && n == -1234); // Invalid values BOOST_CHECK(!ParseInt32("", &n)); BOOST_CHECK(!ParseInt32(" 1", &n)); // no padding inside BOOST_CHECK(!ParseInt32("1 ", &n)); BOOST_CHECK(!ParseInt32("1a", &n)); BOOST_CHECK(!ParseInt32("aap", &n)); BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseInt32(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseInt32("-2147483649", nullptr)); BOOST_CHECK(!ParseInt32("2147483648", nullptr)); BOOST_CHECK(!ParseInt32("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseInt32("32482348723847471234", nullptr)); } BOOST_AUTO_TEST_CASE(test_ParseInt64) { int64_t n; // Valid values BOOST_CHECK(ParseInt64("1234", nullptr)); BOOST_CHECK(ParseInt64("0", &n) && n == 0LL); BOOST_CHECK(ParseInt64("1234", &n) && n == 1234LL); BOOST_CHECK(ParseInt64("01234", &n) && n == 1234LL); // no octal BOOST_CHECK(ParseInt64("2147483647", &n) && n == 2147483647LL); BOOST_CHECK(ParseInt64("-2147483648", &n) && n == -2147483648LL); BOOST_CHECK(ParseInt64("9223372036854775807", &n) && n == (int64_t)9223372036854775807); BOOST_CHECK(ParseInt64("-9223372036854775808", &n) && n == (int64_t)-9223372036854775807 - 1); BOOST_CHECK(ParseInt64("-1234", &n) && n == -1234LL); // Invalid values BOOST_CHECK(!ParseInt64("", &n)); BOOST_CHECK(!ParseInt64(" 1", &n)); // no padding inside BOOST_CHECK(!ParseInt64("1 ", &n)); BOOST_CHECK(!ParseInt64("1a", &n)); BOOST_CHECK(!ParseInt64("aap", &n)); BOOST_CHECK(!ParseInt64("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseInt64(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseInt64("9223372036854775808", nullptr)); BOOST_CHECK(!ParseInt64("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseInt64("32482348723847471234", nullptr)); } BOOST_AUTO_TEST_CASE(test_ParseUInt32) { uint32_t n; // Valid values BOOST_CHECK(ParseUInt32("1234", nullptr)); BOOST_CHECK(ParseUInt32("0", &n) && n == 0); BOOST_CHECK(ParseUInt32("1234", &n) && n == 1234); BOOST_CHECK(ParseUInt32("01234", &n) && n == 1234); // no octal BOOST_CHECK(ParseUInt32("2147483647", &n) && n == 2147483647); BOOST_CHECK(ParseUInt32("2147483648", &n) && n == (uint32_t)2147483648); BOOST_CHECK(ParseUInt32("4294967295", &n) && n == (uint32_t)4294967295); // Invalid values BOOST_CHECK(!ParseUInt32("", &n)); BOOST_CHECK(!ParseUInt32(" 1", &n)); // no padding inside BOOST_CHECK(!ParseUInt32(" -1", &n)); BOOST_CHECK(!ParseUInt32("1 ", &n)); BOOST_CHECK(!ParseUInt32("1a", &n)); BOOST_CHECK(!ParseUInt32("aap", &n)); BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseUInt32(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseUInt32("-2147483648", &n)); BOOST_CHECK(!ParseUInt32("4294967296", &n)); BOOST_CHECK(!ParseUInt32("-1234", &n)); BOOST_CHECK(!ParseUInt32("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseUInt32("32482348723847471234", nullptr)); } BOOST_AUTO_TEST_CASE(test_ParseUInt64) { uint64_t n; // Valid values BOOST_CHECK(ParseUInt64("1234", nullptr)); BOOST_CHECK(ParseUInt64("0", &n) && n == 0LL); BOOST_CHECK(ParseUInt64("1234", &n) && n == 1234LL); BOOST_CHECK(ParseUInt64("01234", &n) && n == 1234LL); // no octal BOOST_CHECK(ParseUInt64("2147483647", &n) && n == 2147483647LL); BOOST_CHECK(ParseUInt64("9223372036854775807", &n) && n == 9223372036854775807ULL); BOOST_CHECK(ParseUInt64("9223372036854775808", &n) && n == 9223372036854775808ULL); BOOST_CHECK(ParseUInt64("18446744073709551615", &n) && n == 18446744073709551615ULL); // Invalid values BOOST_CHECK(!ParseUInt64("", &n)); BOOST_CHECK(!ParseUInt64(" 1", &n)); // no padding inside BOOST_CHECK(!ParseUInt64(" -1", &n)); BOOST_CHECK(!ParseUInt64("1 ", &n)); BOOST_CHECK(!ParseUInt64("1a", &n)); BOOST_CHECK(!ParseUInt64("aap", &n)); BOOST_CHECK(!ParseUInt64("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseUInt64(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseUInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseUInt64("18446744073709551616", nullptr)); BOOST_CHECK(!ParseUInt64("-32482348723847471234", nullptr)); BOOST_CHECK(!ParseUInt64("-2147483648", &n)); BOOST_CHECK(!ParseUInt64("-9223372036854775808", &n)); BOOST_CHECK(!ParseUInt64("-1234", &n)); } BOOST_AUTO_TEST_CASE(test_ParseDouble) { double n; // Valid values BOOST_CHECK(ParseDouble("1234", nullptr)); BOOST_CHECK(ParseDouble("0", &n) && n == 0.0); BOOST_CHECK(ParseDouble("1234", &n) && n == 1234.0); BOOST_CHECK(ParseDouble("01234", &n) && n == 1234.0); // no octal BOOST_CHECK(ParseDouble("2147483647", &n) && n == 2147483647.0); BOOST_CHECK(ParseDouble("-2147483648", &n) && n == -2147483648.0); BOOST_CHECK(ParseDouble("-1234", &n) && n == -1234.0); BOOST_CHECK(ParseDouble("1e6", &n) && n == 1e6); BOOST_CHECK(ParseDouble("-1e6", &n) && n == -1e6); // Invalid values BOOST_CHECK(!ParseDouble("", &n)); BOOST_CHECK(!ParseDouble(" 1", &n)); // no padding inside BOOST_CHECK(!ParseDouble("1 ", &n)); BOOST_CHECK(!ParseDouble("1a", &n)); BOOST_CHECK(!ParseDouble("aap", &n)); BOOST_CHECK(!ParseDouble("0x1", &n)); // no hex const char test_bytes[] = {'1', 0, '1'}; std::string teststr(test_bytes, sizeof(test_bytes)); BOOST_CHECK(!ParseDouble(teststr, &n)); // no embedded NULs // Overflow and underflow BOOST_CHECK(!ParseDouble("-1e10000", nullptr)); BOOST_CHECK(!ParseDouble("1e10000", nullptr)); } BOOST_AUTO_TEST_CASE(test_FormatParagraph) { BOOST_CHECK_EQUAL(FormatParagraph("", 79, 0), ""); BOOST_CHECK_EQUAL(FormatParagraph("test", 79, 0), "test"); BOOST_CHECK_EQUAL(FormatParagraph(" test", 79, 0), " test"); BOOST_CHECK_EQUAL(FormatParagraph("test test", 79, 0), "test test"); BOOST_CHECK_EQUAL(FormatParagraph("test test", 4, 0), "test\ntest"); BOOST_CHECK_EQUAL(FormatParagraph("testerde test", 4, 0), "testerde\ntest"); BOOST_CHECK_EQUAL(FormatParagraph("test test", 4, 4), "test\n test"); // Make sure we don't indent a fully-new line following a too-long line // ending BOOST_CHECK_EQUAL(FormatParagraph("test test\nabc", 4, 4), "test\n test\nabc"); BOOST_CHECK_EQUAL( FormatParagraph("This_is_a_very_long_test_string_without_any_spaces_so_" "it_should_just_get_returned_as_is_despite_the_length " "until it gets here", 79), "This_is_a_very_long_test_string_without_any_spaces_so_it_should_just_" "get_returned_as_is_despite_the_length\nuntil it gets here"); // Test wrap length is exact BOOST_CHECK_EQUAL( FormatParagraph("a b c d e f g h i j k l m n o p q r s t u v w x y z 1 " "2 3 4 5 6 7 8 9 a b c de f g h i j k l m n o p", 79), "a b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 8 9 " "a b c de\nf g h i j k l m n o p"); BOOST_CHECK_EQUAL( FormatParagraph("x\na b c d e f g h i j k l m n o p q r s t u v w x y " "z 1 2 3 4 5 6 7 8 9 a b c de f g h i j k l m n o p", 79), "x\na b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 " "8 9 a b c de\nf g h i j k l m n o p"); // Indent should be included in length of lines BOOST_CHECK_EQUAL( FormatParagraph("x\na b c d e f g h i j k l m n o p q r s t u v w x y " "z 1 2 3 4 5 6 7 8 9 a b c de f g h i j k l m n o p q " "r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 a b c d e fg h " "i j k", 79, 4), "x\na b c d e f g h i j k l m n o p q r s t u v w x y z 1 2 3 4 5 6 7 " "8 9 a b c de\n f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 " "5 6 7 8 9 a b c d e fg\n h i j k"); BOOST_CHECK_EQUAL( FormatParagraph("This is a very long test string. This is a second " "sentence in the very long test string.", 79), "This is a very long test string. This is a second sentence in the " "very long\ntest string."); BOOST_CHECK_EQUAL( FormatParagraph("This is a very long test string.\nThis is a second " "sentence in the very long test string. This is a " "third sentence in the very long test string.", 79), "This is a very long test string.\nThis is a second sentence in the " "very long test string. This is a third\nsentence in the very long " "test string."); BOOST_CHECK_EQUAL( FormatParagraph("This is a very long test string.\n\nThis is a second " "sentence in the very long test string. This is a " "third sentence in the very long test string.", 79), "This is a very long test string.\n\nThis is a second sentence in the " "very long test string. This is a third\nsentence in the very long " "test string."); BOOST_CHECK_EQUAL( FormatParagraph( "Testing that normal newlines do not get indented.\nLike here.", 79), "Testing that normal newlines do not get indented.\nLike here."); } BOOST_AUTO_TEST_CASE(test_FormatSubVersion) { std::vector comments; comments.push_back(std::string("comment1")); std::vector comments2; comments2.push_back(std::string("comment1")); // Semicolon is discouraged but not forbidden by BIP-0014 comments2.push_back(SanitizeString( std::string("Comment2; .,_?@-; !\"#$%&'()*+/<=>[]\\^`{|}~"), SAFE_CHARS_UA_COMMENT)); BOOST_CHECK_EQUAL( FormatSubVersion("Test", 99900, std::vector()), std::string("/Test:0.9.99/")); BOOST_CHECK_EQUAL(FormatSubVersion("Test", 99900, comments), std::string("/Test:0.9.99(comment1)/")); BOOST_CHECK_EQUAL( FormatSubVersion("Test", 99900, comments2), std::string("/Test:0.9.99(comment1; Comment2; .,_?@-; )/")); } BOOST_AUTO_TEST_CASE(test_ParseFixedPoint) { int64_t amount = 0; BOOST_CHECK(ParseFixedPoint("0", 8, &amount)); BOOST_CHECK_EQUAL(amount, 0LL); BOOST_CHECK(ParseFixedPoint("1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 100000000LL); BOOST_CHECK(ParseFixedPoint("0.0", 8, &amount)); BOOST_CHECK_EQUAL(amount, 0LL); BOOST_CHECK(ParseFixedPoint("-0.1", 8, &amount)); BOOST_CHECK_EQUAL(amount, -10000000LL); BOOST_CHECK(ParseFixedPoint("1.1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 110000000LL); BOOST_CHECK(ParseFixedPoint("1.10000000000000000", 8, &amount)); BOOST_CHECK_EQUAL(amount, 110000000LL); BOOST_CHECK(ParseFixedPoint("1.1e1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 1100000000LL); BOOST_CHECK(ParseFixedPoint("1.1e-1", 8, &amount)); BOOST_CHECK_EQUAL(amount, 11000000LL); BOOST_CHECK(ParseFixedPoint("1000", 8, &amount)); BOOST_CHECK_EQUAL(amount, 100000000000LL); BOOST_CHECK(ParseFixedPoint("-1000", 8, &amount)); BOOST_CHECK_EQUAL(amount, -100000000000LL); BOOST_CHECK(ParseFixedPoint("0.00000001", 8, &amount)); BOOST_CHECK_EQUAL(amount, 1LL); BOOST_CHECK(ParseFixedPoint("0.0000000100000000", 8, &amount)); BOOST_CHECK_EQUAL(amount, 1LL); BOOST_CHECK(ParseFixedPoint("-0.00000001", 8, &amount)); BOOST_CHECK_EQUAL(amount, -1LL); BOOST_CHECK(ParseFixedPoint("1000000000.00000001", 8, &amount)); BOOST_CHECK_EQUAL(amount, 100000000000000001LL); BOOST_CHECK(ParseFixedPoint("9999999999.99999999", 8, &amount)); BOOST_CHECK_EQUAL(amount, 999999999999999999LL); BOOST_CHECK(ParseFixedPoint("-9999999999.99999999", 8, &amount)); BOOST_CHECK_EQUAL(amount, -999999999999999999LL); BOOST_CHECK(!ParseFixedPoint("", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("a-1000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-a1000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-1000a", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-01000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("00.1", 8, &amount)); BOOST_CHECK(!ParseFixedPoint(".1", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("--0.1", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("0.000000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-0.000000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("0.00000001000000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-10000000000.00000000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("10000000000.00000000", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-10000000000.00000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("10000000000.00000001", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-10000000000.00000009", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("10000000000.00000009", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-99999999999.99999999", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("99999909999.09999999", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("92233720368.54775807", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("92233720368.54775808", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-92233720368.54775808", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("-92233720368.54775809", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("1.1e", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("1.1e-", 8, &amount)); BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount)); } static void TestOtherThread(fs::path dirname, std::string lockname, bool *result) { *result = LockDirectory(dirname, lockname); } #ifndef WIN32 // Cannot do this test on WIN32 due to lack of fork() static constexpr char LockCommand = 'L'; static constexpr char UnlockCommand = 'U'; static constexpr char ExitCommand = 'X'; static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) { char ch; while (true) { // Wait for command int rv = read(fd, &ch, 1); assert(rv == 1); switch (ch) { case LockCommand: ch = LockDirectory(dirname, lockname); rv = write(fd, &ch, 1); assert(rv == 1); break; case UnlockCommand: ReleaseDirectoryLocks(); ch = true; // Always succeeds rv = write(fd, &ch, 1); assert(rv == 1); break; case ExitCommand: close(fd); // As an alternative to exit() which runs the exit handlers // (which seem to be flakey with Boost test suite with JUNIT // logging in a forked process), just vanish this process as // fast as possible. `quick_exit()` would also work, but it is // not available on all non glibc platforms. // Using exec also stops valgrind from thinking it needs to // analyze the memory leaks in this forked process. execlp("true", "true", (char *)NULL); default: assert(0); } } } #endif BOOST_AUTO_TEST_CASE(test_LockDirectory) { fs::path dirname = GetDataDir() / "lock_dir"; const std::string lockname = ".lock"; #ifndef WIN32 // Revert SIGCHLD to default, otherwise boost.test will catch and fail on // it: there is BOOST_TEST_IGNORE_SIGCHLD but that only works when defined // at build-time of the boost library void (*old_handler)(int) = signal(SIGCHLD, SIG_DFL); // Fork another process for testing before creating the lock, so that we // won't fork while holding the lock (which might be undefined, and is not // relevant as test case as that is avoided with -daemonize). int fd[2]; BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0); pid_t pid = fork(); if (!pid) { BOOST_CHECK_EQUAL(close(fd[1]), 0); // Child: close parent end TestOtherProcess(dirname, lockname, fd[0]); } BOOST_CHECK_EQUAL(close(fd[0]), 0); // Parent: close child end #endif // Lock on non-existent directory should fail BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), false); fs::create_directories(dirname); // Probing lock on new directory should succeed BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Persistent lock on new directory should succeed BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true); // Another lock on the directory from the same thread should succeed BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true); // Another lock on the directory from a different thread within the same // process should succeed bool threadresult; std::thread thr(TestOtherThread, dirname, lockname, &threadresult); thr.join(); BOOST_CHECK_EQUAL(threadresult, true); #ifndef WIN32 // Try to acquire lock in child process while we're holding it, this should // fail. char ch; BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); BOOST_CHECK_EQUAL((bool)ch, false); // Give up our lock ReleaseDirectoryLocks(); // Probing lock from our side now should succeed, but not hold on to the // lock. BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Try to acquire the lock in the child process, this should be successful. BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); BOOST_CHECK_EQUAL((bool)ch, true); // When we try to probe the lock now, it should fail. BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), false); // Unlock the lock in the child process BOOST_CHECK_EQUAL(write(fd[1], &UnlockCommand, 1), 1); BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); BOOST_CHECK_EQUAL((bool)ch, true); // When we try to probe the lock now, it should succeed. BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Re-lock the lock in the child process, then wait for it to exit, check // successful return. After that, we check that exiting the process // has released the lock as we would expect by probing it. int processstatus; BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); BOOST_CHECK_EQUAL(write(fd[1], &ExitCommand, 1), 1); BOOST_CHECK_EQUAL(waitpid(pid, &processstatus, 0), pid); BOOST_CHECK_EQUAL(processstatus, 0); BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); // Restore SIGCHLD signal(SIGCHLD, old_handler); BOOST_CHECK_EQUAL(close(fd[1]), 0); // Close our side of the socketpair #endif // Clean up ReleaseDirectoryLocks(); fs::remove_all(dirname); } BOOST_AUTO_TEST_CASE(test_DirIsWritable) { // Should be able to write to the data dir. fs::path tmpdirname = GetDataDir(); BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), true); // Should not be able to write to a non-existent dir. tmpdirname = tmpdirname / fs::unique_path(); BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), false); fs::create_directory(tmpdirname); // Should be able to write to it now. BOOST_CHECK_EQUAL(DirIsWritable(tmpdirname), true); fs::remove(tmpdirname); } template static void CheckConvertBits(const std::vector &in, const std::vector &expected) { std::vector outpad; bool ret = ConvertBits([&](uint8_t c) { outpad.push_back(c); }, in.begin(), in.end()); BOOST_CHECK(ret); BOOST_CHECK(outpad == expected); const bool dopad = (in.size() * F) % T; std::vector outnopad; ret = ConvertBits([&](uint8_t c) { outnopad.push_back(c); }, in.begin(), in.end()); BOOST_CHECK(ret != (dopad && !outpad.empty() && outpad.back())); if (dopad) { // We should have skipped the last digit. outnopad.push_back(expected.back()); } BOOST_CHECK(outnopad == expected); // Check the other way around. // Check with padding. We may get an extra 0 in that case. std::vector origpad; ret = ConvertBits([&](uint8_t c) { origpad.push_back(c); }, expected.begin(), expected.end()); BOOST_CHECK(ret); std::vector orignopad; ret = ConvertBits([&](uint8_t c) { orignopad.push_back(c); }, expected.begin(), expected.end()); BOOST_CHECK(ret != ((expected.size() * T) % F && !origpad.empty() && origpad.back())); BOOST_CHECK(orignopad == in); if (dopad) { BOOST_CHECK_EQUAL(origpad.back(), 0); origpad.pop_back(); } BOOST_CHECK(origpad == in); } BOOST_AUTO_TEST_CASE(test_ConvertBits) { CheckConvertBits<8, 5>({}, {}); CheckConvertBits<8, 5>({0xff}, {0x1f, 0x1c}); CheckConvertBits<8, 5>({0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x10}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1e}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x18}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); CheckConvertBits<8, 5>({0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, {0x00, 0x04, 0x11, 0x14, 0x0a, 0x19, 0x1c, 0x09, 0x15, 0x0f, 0x06, 0x1e, 0x1e}); CheckConvertBits<8, 5>({0x00}, {0x00, 0x00}); CheckConvertBits<8, 5>({0xf8}, {0x1f, 0x00}); CheckConvertBits<8, 5>({0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}); } BOOST_AUTO_TEST_CASE(test_ToLower) { BOOST_CHECK_EQUAL(ToLower('@'), '@'); BOOST_CHECK_EQUAL(ToLower('A'), 'a'); BOOST_CHECK_EQUAL(ToLower('Z'), 'z'); BOOST_CHECK_EQUAL(ToLower('['), '['); BOOST_CHECK_EQUAL(ToLower(0), 0); BOOST_CHECK_EQUAL(ToLower(255), 255); std::string testVector; Downcase(testVector); BOOST_CHECK_EQUAL(testVector, ""); testVector = "#HODL"; Downcase(testVector); BOOST_CHECK_EQUAL(testVector, "#hodl"); testVector = "\x00\xfe\xff"; Downcase(testVector); BOOST_CHECK_EQUAL(testVector, "\x00\xfe\xff"); } BOOST_AUTO_TEST_CASE(test_ToUpper) { BOOST_CHECK_EQUAL(ToUpper('`'), '`'); BOOST_CHECK_EQUAL(ToUpper('a'), 'A'); BOOST_CHECK_EQUAL(ToUpper('z'), 'Z'); BOOST_CHECK_EQUAL(ToUpper('{'), '{'); BOOST_CHECK_EQUAL(ToUpper(0), 0); BOOST_CHECK_EQUAL(ToUpper(255), 255); } BOOST_AUTO_TEST_CASE(test_Capitalize) { BOOST_CHECK_EQUAL(Capitalize(""), ""); BOOST_CHECK_EQUAL(Capitalize("bitcoin"), "Bitcoin"); BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/system.cpp b/src/util/system.cpp index 40d2433aa..484a1aee5 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,1411 +1,1283 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include +#include + #include #include #include #include #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) #include #include #endif #ifndef WIN32 // for posix_fallocate #ifdef __linux__ #ifdef _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L #endif // __linux__ #include #include #include #include #include #else #ifdef _MSC_VER #pragma warning(disable : 4786) #pragma warning(disable : 4804) #pragma warning(disable : 4805) #pragma warning(disable : 4717) #endif #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include #include /* for _commit */ #include #include #endif #ifdef HAVE_MALLOPT_ARENA_MAX #include #endif // Application startup time (used for uptime calculation) const int64_t nStartupTime = GetTime(); const char *const BITCOIN_CONF_FILENAME = "bitcoin.conf"; ArgsManager gArgs; /** * A map that contains all the currently held directory locks. After successful * locking, these will be held here until the global destructor cleans them up * and thus automatically unlocks them, or ReleaseDirectoryLocks is called. */ static std::map> dir_locks; /** Mutex to protect dir_locks. */ static std::mutex cs_dir_locks; bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only) { std::lock_guard ulock(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; // If a lock for this directory already exists in the map, don't try to // re-lock it if (dir_locks.count(pathLockFile.string())) { return true; } // Create empty lock file if it doesn't exist. FILE *file = fsbridge::fopen(pathLockFile, "a"); if (file) { fclose(file); } auto lock = std::make_unique(pathLockFile); if (!lock->TryLock()) { return error("Error while attempting to lock directory %s: %s", directory.string(), lock->GetReason()); } if (!probe_only) { // Lock successful and we're not just probing, put it into the map dir_locks.emplace(pathLockFile.string(), std::move(lock)); } return true; } void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name) { std::lock_guard lock(cs_dir_locks); dir_locks.erase((directory / lockfile_name).string()); } void ReleaseDirectoryLocks() { std::lock_guard ulock(cs_dir_locks); dir_locks.clear(); } bool DirIsWritable(const fs::path &directory) { fs::path tmpFile = directory / fs::unique_path(); FILE *file = fsbridge::fopen(tmpFile, "a"); if (!file) { return false; } fclose(file); remove(tmpFile); return true; } bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes) { // 50 MiB constexpr uint64_t min_disk_space = 52428800; uint64_t free_bytes_available = fs::space(dir).available; return free_bytes_available >= min_disk_space + additional_bytes; } /** * Interpret a string argument as a boolean. * * The definition of atoi() requires that non-numeric string values like "foo", * return 0. This means that if a user unintentionally supplies a non-integer * argument here, the return value is always false. This means that -foo=false * does what the user probably expects, but -foo=true is well defined but does * not do what they probably expected. * * The return value of atoi() is undefined when given input not representable as * an int. On most systems this means string value between "-2147483648" and * "2147483647" are well defined (this method will return true). Setting * -txindex=2147483648 on most systems, however, is probably undefined. * * For a more extensive discussion of this topic (and a wide range of opinions * on the Right Way to change this code), see PR12713. */ static bool InterpretBool(const std::string &strValue) { if (strValue.empty()) { return true; } return (atoi(strValue) != 0); } +static std::string SettingName(const std::string &arg) { + return arg.size() > 0 && arg[0] == '-' ? arg.substr(1) : arg; +} + /** Internal helper functions for ArgsManager */ class ArgsManagerHelper { public: - typedef std::map> MapArgs; - /** Determine whether to use config settings in the default section, * See also comments around ArgsManager::ArgsManager() below. */ static inline bool UseDefaultSection(const ArgsManager &am, const std::string &arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) { return (am.m_network == CBaseChainParams::MAIN || am.m_network_only_args.count(arg) == 0); } - /** Convert regular argument into the network-specific setting */ - static inline std::string NetworkArg(const ArgsManager &am, - const std::string &arg) { - assert(arg.length() > 1 && arg[0] == '-'); - return "-" + am.m_network + "." + arg.substr(1); - } - - /** Find arguments in a map and add them to a vector */ - static inline void AddArgs(std::vector &res, - const MapArgs &map_args, - const std::string &arg) { - auto it = map_args.find(arg); - if (it != map_args.end()) { - res.insert(res.end(), it->second.begin(), it->second.end()); - } - } - - /** - * Return true/false if an argument is set in a map, and also - * return the first (or last) of the possibly multiple values it has - */ - static inline std::pair - GetArgHelper(const MapArgs &map_args, const std::string &arg, - bool getLast = false) { - auto it = map_args.find(arg); - - if (it == map_args.end() || it->second.empty()) { - return std::make_pair(false, std::string()); - } - - if (getLast) { - return std::make_pair(true, it->second.back()); - } else { - return std::make_pair(true, it->second.front()); - } - } - - /** - * Get the string value of an argument, returning a pair of a boolean - * indicating the argument was found, and the value for the argument - * if it was found (or the empty string if not found). - */ - static inline std::pair GetArg(const ArgsManager &am, - const std::string &arg) { + static util::SettingsValue Get(const ArgsManager &am, + const std::string &arg) { LOCK(am.cs_args); - std::pair found_result(false, std::string()); - - // We pass "true" to GetArgHelper in order to return the last - // argument value seen from the command line (so "bitcoind -foo=bar - // -foo=baz" gives GetArg(am,"foo")=={true,"baz"} - found_result = GetArgHelper(am.m_override_args, arg, true); - if (found_result.first) { - return found_result; - } - - // But in contrast we return the first argument seen in a config file, - // so "foo=bar \n foo=baz" in the config file gives - // GetArg(am,"foo")={true,"bar"} - if (!am.m_network.empty()) { - found_result = GetArgHelper(am.m_config_args, NetworkArg(am, arg)); - if (found_result.first) { - return found_result; - } - } - - if (UseDefaultSection(am, arg)) { - found_result = GetArgHelper(am.m_config_args, arg); - if (found_result.first) { - return found_result; - } - } - - return found_result; - } - - /* Special test for -testnet and -regtest args, because we don't want to be - * confused by craziness like "[regtest] testnet=1" - */ - static inline bool GetNetBoolArg(const ArgsManager &am, - const std::string &net_arg) - EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) { - std::pair found_result(false, std::string()); - found_result = GetArgHelper(am.m_override_args, net_arg, true); - if (!found_result.first) { - found_result = GetArgHelper(am.m_config_args, net_arg, true); - if (!found_result.first) { - // not set - return false; - } - } - // is set, so evaluate - return InterpretBool(found_result.second); + return GetSetting(am.m_settings, am.m_network, SettingName(arg), + !UseDefaultSection(am, arg), + /* get_chain_name= */ false); } }; /** * Interpret -nofoo as if the user supplied -foo=0. * * This method also tracks when the -no form was supplied, and if so, checks * whether there was a double-negative (-nofoo=0 -> -foo=1). * * If there was not a double negative, it removes the "no" from the key - * and clears the args vector to indicate a negated option. + * and returns false. * - * If there was a double negative, it removes "no" from the key, sets the - * value to "1" and pushes the key and the updated value to the args vector. + * If there was a double negative, it removes "no" from the key, and + * returns true. * - * If there was no "no", it leaves key and value untouched and pushes them - * to the args vector. + * If there was no "no", it returns the string value untouched. * * Where an option was negated can be later checked using the IsArgNegated() * method. One use case for this is to have a way to disable options that are * not normally boolean (e.g. using -nodebuglogfile to request that debug log * output is not sent to any file at all). */ -NODISCARD static bool -InterpretOption(std::string key, std::string val, unsigned int flags, - std::map> &args, - std::string &error) { - assert(key[0] == '-'); - +static util::SettingsValue InterpretOption(std::string §ion, + std::string &key, + const std::string &value) { + // Split section name from key name for keys like "testnet.foo" or + // "regtest.bar" size_t option_index = key.find('.'); - if (option_index == std::string::npos) { - option_index = 1; - } else { - ++option_index; - } - if (key.substr(option_index, 2) == "no") { - key.erase(option_index, 2); - if (flags & ArgsManager::ALLOW_BOOL) { - if (InterpretBool(val)) { - args[key].clear(); - return true; - } - // Double negatives like -nofoo=0 are supported (but discouraged) - LogPrintf( - "Warning: parsed potentially confusing double-negative %s=%s\n", - key, val); - val = "1"; - } else { - error = strprintf( - "Negating of %s is meaningless and therefore forbidden", - key.c_str()); - return false; + if (option_index != std::string::npos) { + section = key.substr(0, option_index); + key.erase(0, option_index + 1); + } + if (key.substr(0, 2) == "no") { + key.erase(0, 2); + // Double negatives like -nofoo=0 are supported (but discouraged) + if (!InterpretBool(value)) { + LogPrintf("Warning: parsed potentially confusing double-negative " + "-%s=%s\n", + key, value); + return true; } + return false; + } + return value; +} + +/** + * Check settings value validity according to flags. + * + * TODO: Add more meaningful error checks here in the future + * See "here's how the flags are meant to behave" in + * https://github.com/bitcoin/bitcoin/pull/16097#issuecomment-514627823 + */ +static bool CheckValid(const std::string &key, const util::SettingsValue &val, + unsigned int flags, std::string &error) { + if (val.isBool() && !(flags & ArgsManager::ALLOW_BOOL)) { + error = strprintf( + "Negating of -%s is meaningless and therefore forbidden", key); + return false; } - args[key].push_back(val); return true; } ArgsManager::ArgsManager() { // nothing to do } const std::set ArgsManager::GetUnsuitableSectionOnlyArgs() const { std::set unsuitables; LOCK(cs_args); // if there's no section selected, don't worry if (m_network.empty()) { return std::set{}; } // if it's okay to use the default section for this network, don't worry if (m_network == CBaseChainParams::MAIN) { return std::set{}; } for (const auto &arg : m_network_only_args) { - std::pair found_result; - - // if this option is overridden it's fine - found_result = ArgsManagerHelper::GetArgHelper(m_override_args, arg); - if (found_result.first) { - continue; - } - - // if there's a network-specific value for this option, it's fine - found_result = ArgsManagerHelper::GetArgHelper( - m_config_args, ArgsManagerHelper::NetworkArg(*this, arg)); - if (found_result.first) { - continue; - } - - // if there isn't a default value for this option, it's fine - found_result = ArgsManagerHelper::GetArgHelper(m_config_args, arg); - if (!found_result.first) { - continue; + if (OnlyHasDefaultSectionSetting(m_settings, m_network, + SettingName(arg))) { + unsuitables.insert(arg); } - - // otherwise, issue a warning - unsuitables.insert(arg); } return unsuitables; } const std::list ArgsManager::GetUnrecognizedSections() const { // Section names to be recognized in the config file. static const std::set available_sections{ CBaseChainParams::REGTEST, CBaseChainParams::TESTNET, CBaseChainParams::MAIN}; LOCK(cs_args); std::list unrecognized = m_config_sections; unrecognized.remove_if([](const SectionInfo &appeared) { return available_sections.find(appeared.m_name) != available_sections.end(); }); return unrecognized; } void ArgsManager::SelectConfigNetwork(const std::string &network) { LOCK(cs_args); m_network = network; } bool ParseKeyValue(std::string &key, std::string &val) { size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); key.erase(is_index); } #ifdef WIN32 std::transform(key.begin(), key.end(), key.begin(), ::tolower); if (key[0] == '/') { key[0] = '-'; } #endif if (key[0] != '-') { return false; } // Transform --foo to -foo if (key.length() > 1 && key[1] == '-') { key.erase(0, 1); } return true; } bool ArgsManager::ParseParameters(int argc, const char *const argv[], std::string &error) { LOCK(cs_args); - m_override_args.clear(); + m_settings.command_line_options.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); if (key == "-") { // bitcoin-tx using stdin break; } std::string val; if (!ParseKeyValue(key, val)) { break; } + // Transform -foo to foo + key.erase(0, 1); + std::string section; + util::SettingsValue value = InterpretOption(section, key, val); const unsigned int flags = FlagsOfKnownArg(key); if (flags) { - if (!InterpretOption(key, val, flags, m_override_args, error)) { + if (!CheckValid(key, value, flags, error)) { return false; } + // Weird behavior preserved for backwards compatibility: command + // line options with section prefixes are allowed but ignored. It + // would be better if these options triggered the Invalid parameter + // error below. + if (section.empty()) { + m_settings.command_line_options[key].push_back(value); + } } else { - error = strprintf("Invalid parameter %s", key.c_str()); + error = strprintf("Invalid parameter -%s", key); return false; } } // we do not allow -includeconf from command line, so we clear it here - auto it = m_override_args.find("-includeconf"); - if (it != m_override_args.end()) { - if (it->second.size() > 0) { - for (const auto &ic : it->second) { - error += "-includeconf cannot be used from commandline; " - "-includeconf=" + - ic + "\n"; - } - return false; + bool success = true; + if (auto *includes = + util::FindKey(m_settings.command_line_options, "includeconf")) { + for (const auto &include : util::SettingsSpan(*includes)) { + error += + "-includeconf cannot be used from commandline; -includeconf=" + + include.get_str() + "\n"; + success = false; } } - return true; + return success; } unsigned int ArgsManager::FlagsOfKnownArg(const std::string &key) const { - assert(key[0] == '-'); - - size_t option_index = key.find('.'); - if (option_index == std::string::npos) { - option_index = 1; - } else { - ++option_index; - } - if (key.substr(option_index, 2) == "no") { - option_index += 2; - } - - const std::string base_arg_name = '-' + key.substr(option_index); - LOCK(cs_args); for (const auto &arg_map : m_available_args) { - const auto search = arg_map.second.find(base_arg_name); + const auto search = arg_map.second.find('-' + key); if (search != arg_map.second.end()) { return search->second.m_flags; } } return ArgsManager::NONE; } std::vector ArgsManager::GetArgs(const std::string &strArg) const { - std::vector result = {}; - // special case - if (IsArgNegated(strArg)) { - return result; - } - LOCK(cs_args); - - ArgsManagerHelper::AddArgs(result, m_override_args, strArg); - if (!m_network.empty()) { - ArgsManagerHelper::AddArgs( - result, m_config_args, - ArgsManagerHelper::NetworkArg(*this, strArg)); - } - - if (ArgsManagerHelper::UseDefaultSection(*this, strArg)) { - ArgsManagerHelper::AddArgs(result, m_config_args, strArg); + bool ignore_default_section_config = + !ArgsManagerHelper::UseDefaultSection(*this, strArg); + std::vector result; + for (const util::SettingsValue &value : + util::GetSettingsList(m_settings, m_network, SettingName(strArg), + ignore_default_section_config)) { + result.push_back( + value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str()); } - return result; } bool ArgsManager::IsArgSet(const std::string &strArg) const { - // special case - if (IsArgNegated(strArg)) { - return true; - } - return ArgsManagerHelper::GetArg(*this, strArg).first; + return !ArgsManagerHelper::Get(*this, strArg).isNull(); } bool ArgsManager::IsArgNegated(const std::string &strArg) const { - LOCK(cs_args); - - const auto &ov = m_override_args.find(strArg); - if (ov != m_override_args.end()) { - return ov->second.empty(); - } - - if (!m_network.empty()) { - const auto &cfs = - m_config_args.find(ArgsManagerHelper::NetworkArg(*this, strArg)); - if (cfs != m_config_args.end()) { - return cfs->second.empty(); - } - } - - const auto &cf = m_config_args.find(strArg); - if (cf != m_config_args.end()) { - return cf->second.empty(); - } - - return false; + return ArgsManagerHelper::Get(*this, strArg).isFalse(); } std::string ArgsManager::GetArg(const std::string &strArg, const std::string &strDefault) const { - if (IsArgNegated(strArg)) { - return "0"; - } - std::pair found_res = - ArgsManagerHelper::GetArg(*this, strArg); - if (found_res.first) { - return found_res.second; - } - return strDefault; + const util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg); + return value.isNull() + ? strDefault + : value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str(); } int64_t ArgsManager::GetArg(const std::string &strArg, int64_t nDefault) const { - if (IsArgNegated(strArg)) { - return 0; - } - std::pair found_res = - ArgsManagerHelper::GetArg(*this, strArg); - if (found_res.first) { - return atoi64(found_res.second); - } - return nDefault; + const util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg); + return value.isNull() + ? nDefault + : value.isFalse() + ? 0 + : value.isTrue() ? 1 + : value.isNum() ? value.get_int64() + : atoi64(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string &strArg, bool fDefault) const { - if (IsArgNegated(strArg)) { - return false; - } - std::pair found_res = - ArgsManagerHelper::GetArg(*this, strArg); - if (found_res.first) { - return InterpretBool(found_res.second); - } - return fDefault; + const util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg); + return value.isNull() ? fDefault + : value.isBool() ? value.get_bool() + : InterpretBool(value.get_str()); } bool ArgsManager::SoftSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); if (IsArgSet(strArg)) { return false; } ForceSetArg(strArg, strValue); return true; } bool ArgsManager::SoftSetBoolArg(const std::string &strArg, bool fValue) { if (fValue) { return SoftSetArg(strArg, std::string("1")); } else { return SoftSetArg(strArg, std::string("0")); } } void ArgsManager::ForceSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); - m_override_args[strArg] = {strValue}; + m_settings.forced_settings[SettingName(strArg)] = strValue; } /** * This function is only used for testing purpose so * so we should not worry about element uniqueness and * integrity of mapMultiArgs data structure */ void ArgsManager::ForceSetMultiArg(const std::string &strArg, - const std::string &strValue) { + const std::vector &values) { LOCK(cs_args); - m_override_args[strArg].push_back(strValue); + util::SettingsValue value; + value.setArray(); + for (const std::string &s : values) { + value.push_back(s); + } + + m_settings.forced_settings[SettingName(strArg)] = value; } void ArgsManager::AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat) { // Split arg name from its help param size_t eq_index = name.find('='); if (eq_index == std::string::npos) { eq_index = name.size(); } std::string arg_name = name.substr(0, eq_index); LOCK(cs_args); std::map &arg_map = m_available_args[cat]; auto ret = arg_map.emplace( arg_name, Arg{name.substr(eq_index, name.size() - eq_index), help, flags}); // Make sure an insertion actually happened. assert(ret.second); if (flags & ArgsManager::NETWORK_ONLY) { m_network_only_args.emplace(arg_name); } } void ArgsManager::AddHiddenArgs(const std::vector &names) { for (const std::string &name : names) { AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN); } } void ArgsManager::ClearForcedArg(const std::string &strArg) { LOCK(cs_args); - m_override_args.erase(strArg); + m_settings.forced_settings.erase(SettingName(strArg)); } std::string ArgsManager::GetHelpMessage() const { const bool show_debug = gArgs.GetBoolArg("-help-debug", false); std::string usage = ""; LOCK(cs_args); for (const auto &arg_map : m_available_args) { switch (arg_map.first) { case OptionsCategory::OPTIONS: usage += HelpMessageGroup("Options:"); break; case OptionsCategory::CONNECTION: usage += HelpMessageGroup("Connection options:"); break; case OptionsCategory::ZMQ: usage += HelpMessageGroup("ZeroMQ notification options:"); break; case OptionsCategory::DEBUG_TEST: usage += HelpMessageGroup("Debugging/Testing options:"); break; case OptionsCategory::NODE_RELAY: usage += HelpMessageGroup("Node relay options:"); break; case OptionsCategory::BLOCK_CREATION: usage += HelpMessageGroup("Block creation options:"); break; case OptionsCategory::RPC: usage += HelpMessageGroup("RPC server options:"); break; case OptionsCategory::WALLET: usage += HelpMessageGroup("Wallet options:"); break; case OptionsCategory::WALLET_DEBUG_TEST: if (show_debug) { usage += HelpMessageGroup("Wallet debugging/testing options:"); } break; case OptionsCategory::CHAINPARAMS: usage += HelpMessageGroup("Chain selection options:"); break; case OptionsCategory::GUI: usage += HelpMessageGroup("UI Options:"); break; case OptionsCategory::COMMANDS: usage += HelpMessageGroup("Commands:"); break; case OptionsCategory::REGISTER_COMMANDS: usage += HelpMessageGroup("Register Commands:"); break; default: break; } // When we get to the hidden options, stop if (arg_map.first == OptionsCategory::HIDDEN) { break; } for (const auto &arg : arg_map.second) { if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) { std::string name; if (arg.second.m_help_param.empty()) { name = arg.first; } else { name = arg.first + arg.second.m_help_param; } usage += HelpMessageOpt(name, arg.second.m_help_text); } } } return usage; } bool HelpRequested(const ArgsManager &args) { return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help") || args.IsArgSet("-help-debug"); } static const int screenWidth = 79; static const int optIndent = 2; static const int msgIndent = 7; std::string HelpMessageGroup(const std::string &message) { return std::string(message) + std::string("\n\n"); } std::string HelpMessageOpt(const std::string &option, const std::string &message) { return std::string(optIndent, ' ') + std::string(option) + std::string("\n") + std::string(msgIndent, ' ') + FormatParagraph(message, screenWidth - msgIndent, msgIndent) + std::string("\n\n"); } static std::string FormatException(const std::exception *pex, const char *pszThread) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; GetModuleFileNameA(nullptr, pszModule, sizeof(pszModule)); #else const char *pszModule = "bitcoin"; #endif if (pex) { return strprintf("EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); } else { return strprintf("UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); } } void PrintExceptionContinue(const std::exception *pex, const char *pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); tfm::format(std::cerr, "\n\n************************\n%s\n", message.c_str()); } fs::path GetDefaultDataDir() { // Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin // Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin // Mac: ~/Library/Application Support/Bitcoin // Unix: ~/.bitcoin #ifdef WIN32 // Windows return GetSpecialFolderPath(CSIDL_APPDATA) / "Bitcoin"; #else fs::path pathRet; char *pszHome = getenv("HOME"); if (pszHome == nullptr || strlen(pszHome) == 0) { pathRet = fs::path("/"); } else { pathRet = fs::path(pszHome); } #ifdef MAC_OSX // Mac return pathRet / "Library/Application Support/Bitcoin"; #else // Unix return pathRet / ".bitcoin"; #endif #endif } static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; static RecursiveMutex csPathCached; const fs::path &GetBlocksDir() { LOCK(csPathCached); fs::path &path = g_blocks_path_cache_net_specific; // Cache the path to avoid calling fs::create_directories on every call of // this function if (!path.empty()) { return path; } if (gArgs.IsArgSet("-blocksdir")) { path = fs::system_complete(gArgs.GetArg("-blocksdir", "")); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDataDir(false); } path /= BaseParams().DataDir(); path /= "blocks"; fs::create_directories(path); return path; } const fs::path &GetDataDir(bool fNetSpecific) { LOCK(csPathCached); fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; // Cache the path to avoid calling fs::create_directories on every call of // this function if (!path.empty()) { return path; } std::string datadir = gArgs.GetArg("-datadir", ""); if (!datadir.empty()) { path = fs::system_complete(datadir); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDefaultDataDir(); } if (fNetSpecific) { path /= BaseParams().DataDir(); } if (fs::create_directories(path)) { // This is the first run, create wallets subdirectory too // // TODO: this is an ugly way to create the wallets/ directory and // really shouldn't be done here. Once this is fixed, please // also remove the corresponding line in bitcoind.cpp AppInit. // See more info at: // https://reviews.bitcoinabc.org/D3312 fs::create_directories(path / "wallets"); } return path; } bool CheckDataDirOption() { std::string datadir = gArgs.GetArg("-datadir", ""); return datadir.empty() || fs::is_directory(fs::system_complete(datadir)); } void ClearDatadirCache() { LOCK(csPathCached); pathCached = fs::path(); pathCachedNetSpecific = fs::path(); g_blocks_path_cache_net_specific = fs::path(); } fs::path GetConfigFile(const std::string &confPath) { return AbsPathForConfigVal(fs::path(confPath), false); } static std::string TrimString(const std::string &str, const std::string &pattern) { std::string::size_type front = str.find_first_not_of(pattern); if (front == std::string::npos) { return std::string(); } std::string::size_type end = str.find_last_not_of(pattern); return str.substr(front, end - front + 1); } static bool GetConfigOptions(std::istream &stream, const std::string &filepath, std::string &error, std::vector> &options, std::list §ions) { std::string str, prefix; std::string::size_type pos; int linenr = 1; while (std::getline(stream, str)) { bool used_hash = false; if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); used_hash = true; } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); if (!str.empty()) { if (*str.begin() == '[' && *str.rbegin() == ']') { const std::string section = str.substr(1, str.size() - 2); sections.emplace_back(SectionInfo{section, filepath, linenr}); prefix = section + '.'; } else if (*str.begin() == '-') { error = strprintf( "parse error on line %i: %s, options in configuration file " "must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); if (used_hash && name.find("rpcpassword") != std::string::npos) { error = strprintf( "parse error on line %i, using # in rpcpassword can be " "ambiguous and should be avoided", linenr); return false; } options.emplace_back(name, value); if ((pos = name.rfind('.')) != std::string::npos && prefix.length() <= pos) { sections.emplace_back( SectionInfo{name.substr(0, pos), filepath, linenr}); } } else { error = strprintf("parse error on line %i: %s", linenr, str); if (str.size() >= 2 && str.substr(0, 2) == "no") { error += strprintf(", if you intended to specify a negated " "option, use %s=1 instead", str); } return false; } } ++linenr; } return true; } bool ArgsManager::ReadConfigStream(std::istream &stream, const std::string &filepath, std::string &error, bool ignore_invalid_keys) { LOCK(cs_args); std::vector> options; if (!GetConfigOptions(stream, filepath, error, options, m_config_sections)) { return false; } for (const std::pair &option : options) { - const std::string strKey = std::string("-") + option.first; - const unsigned int flags = FlagsOfKnownArg(strKey); + std::string section; + std::string key = option.first; + util::SettingsValue value = + InterpretOption(section, key, option.second); + const unsigned int flags = FlagsOfKnownArg(key); if (flags) { - if (!InterpretOption(strKey, option.second, flags, m_config_args, - error)) { + if (!CheckValid(key, value, flags, error)) { return false; } + m_settings.ro_config[section][key].push_back(value); } else { if (ignore_invalid_keys) { LogPrintf("Ignoring unknown configuration value %s\n", option.first); } else { error = strprintf("Invalid configuration value %s", option.first.c_str()); return false; } } } return true; } bool ArgsManager::ReadConfigFiles(std::string &error, bool ignore_invalid_keys) { { LOCK(cs_args); - m_config_args.clear(); + m_settings.ro_config.clear(); m_config_sections.clear(); } const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); fsbridge::ifstream stream(GetConfigFile(confPath)); // ok to not have a config file if (stream.good()) { if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) { return false; } // `-includeconf` cannot be included in the command line arguments // except as `-noincludeconf` (which indicates that no conf file should // be used). bool use_conf_file{true}; { LOCK(cs_args); - auto it = m_override_args.find("-includeconf"); - if (it != m_override_args.end()) { + if (auto *includes = util::FindKey(m_settings.command_line_options, + "includeconf")) { // ParseParameters() fails if a non-negated -includeconf is // passed on the command-line - assert(it->second.empty()); + assert(util::SettingsSpan(*includes).last_negated()); use_conf_file = false; } } if (use_conf_file) { std::string chain_id = GetChainName(); - std::vector conf_file_names(GetArgs("-includeconf")); - { - // We haven't set m_network yet (that happens in - // SelectParams()), so manually check for network.includeconf - // args. - std::vector includeconf_net( - GetArgs(std::string("-") + chain_id + ".includeconf")); - conf_file_names.insert(conf_file_names.end(), - includeconf_net.begin(), - includeconf_net.end()); - } + std::vector conf_file_names; - // Remove -includeconf from configuration, so we can warn about - // recursion later - { + auto add_includes = [&](const std::string &network, + size_t skip = 0) { + size_t num_values = 0; LOCK(cs_args); - m_config_args.erase("-includeconf"); - m_config_args.erase(std::string("-") + chain_id + - ".includeconf"); - } + if (auto *section = + util::FindKey(m_settings.ro_config, network)) { + if (auto *values = util::FindKey(*section, "includeconf")) { + for (size_t i = std::max( + skip, util::SettingsSpan(*values).negated()); + i < values->size(); ++i) { + conf_file_names.push_back((*values)[i].get_str()); + } + num_values = values->size(); + } + } + return num_values; + }; + + // We haven't set m_network yet (that happens in SelectParams()), so + // manually check for network.includeconf args. + const size_t chain_includes = add_includes(chain_id); + const size_t default_includes = add_includes({}); for (const std::string &conf_file_name : conf_file_names) { fsbridge::ifstream conf_file_stream( GetConfigFile(conf_file_name)); if (conf_file_stream.good()) { if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) { return false; } LogPrintf("Included configuration file %s\n", conf_file_name); } else { error = "Failed to include configuration file " + conf_file_name; return false; } } // Warn about recursive -includeconf - conf_file_names = GetArgs("-includeconf"); - std::vector includeconf_net( - GetArgs(std::string("-") + chain_id + ".includeconf")); - conf_file_names.insert(conf_file_names.end(), - includeconf_net.begin(), - includeconf_net.end()); + conf_file_names.clear(); + add_includes(chain_id, /* skip= */ chain_includes); + add_includes({}, /* skip= */ default_includes); std::string chain_id_final = GetChainName(); if (chain_id_final != chain_id) { // Also warn about recursive includeconf for the chain that was // specified in one of the includeconfs - includeconf_net = - GetArgs(std::string("-") + chain_id_final + ".includeconf"); - conf_file_names.insert(conf_file_names.end(), - includeconf_net.begin(), - includeconf_net.end()); + add_includes(chain_id_final); } for (const std::string &conf_file_name : conf_file_names) { tfm::format(std::cerr, "warning: -includeconf cannot be used from " "included files; ignoring -includeconf=%s\n", conf_file_name); } } } // If datadir is changed in .conf file: ClearDatadirCache(); if (!CheckDataDirOption()) { error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); return false; } return true; } std::string ArgsManager::GetChainName() const { - LOCK(cs_args); - const bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); - const bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); + auto get_net = [&](const std::string &arg) { + LOCK(cs_args); + util::SettingsValue value = + GetSetting(m_settings, /* section= */ "", SettingName(arg), + /* ignore_default_section_config= */ false, + /* get_chain_name= */ true); + return value.isNull() ? false + : value.isBool() ? value.get_bool() + : InterpretBool(value.get_str()); + }; + + const bool fRegTest = get_net("-regtest"); + const bool fTestNet = get_net("-testnet"); const bool is_chain_arg_set = IsArgSet("-chain"); if (int(is_chain_arg_set) + int(fRegTest) + int(fTestNet) > 1) { throw std::runtime_error("Invalid combination of -regtest, -testnet " "and -chain. Can use at most one."); } if (fRegTest) { return CBaseChainParams::REGTEST; } if (fTestNet) { return CBaseChainParams::TESTNET; } return GetArg("-chain", CBaseChainParams::MAIN); } bool RenameOver(fs::path src, fs::path dest) { #ifdef WIN32 return MoveFileExA(src.string().c_str(), dest.string().c_str(), MOVEFILE_REPLACE_EXISTING) != 0; #else int rc = std::rename(src.string().c_str(), dest.string().c_str()); return (rc == 0); #endif /* WIN32 */ } /** * Ignores exceptions thrown by Boost's create_directories if the requested * directory exists. Specifically handles case where path p exists, but it * wasn't possible for the user to write to the parent directory. */ bool TryCreateDirectories(const fs::path &p) { try { return fs::create_directories(p); } catch (const fs::filesystem_error &) { if (!fs::exists(p) || !fs::is_directory(p)) { throw; } } // create_directory didn't create the directory, it had to have existed // already. return false; } bool FileCommit(FILE *file) { // harmless if redundantly called if (fflush(file) != 0) { LogPrintf("%s: fflush failed: %d\n", __func__, errno); return false; } #ifdef WIN32 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); if (FlushFileBuffers(hFile) == 0) { LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); return false; } #else #if defined(__linux__) || defined(__NetBSD__) // Ignore EINVAL for filesystems that don't support sync if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); return false; } #elif defined(MAC_OSX) && defined(F_FULLFSYNC) // Manpage says "value other than -1" is returned on success if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); return false; } #else if (fsync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fsync failed: %d\n", __func__, errno); return false; } #endif #endif return true; } bool TruncateFile(FILE *file, unsigned int length) { #if defined(WIN32) return _chsize(_fileno(file), length) == 0; #else return ftruncate(fileno(file), length) == 0; #endif } /** * This function tries to raise the file descriptor limit to the requested * number. It returns the actual file descriptor limit (which may be more or * less than nMinFD) */ int RaiseFileDescriptorLimit(int nMinFD) { #if defined(WIN32) return 2048; #else struct rlimit limitFD; if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { if (limitFD.rlim_cur < (rlim_t)nMinFD) { limitFD.rlim_cur = nMinFD; if (limitFD.rlim_cur > limitFD.rlim_max) { limitFD.rlim_cur = limitFD.rlim_max; } setrlimit(RLIMIT_NOFILE, &limitFD); getrlimit(RLIMIT_NOFILE, &limitFD); } return limitFD.rlim_cur; } // getrlimit failed, assume it's fine. return nMinFD; #endif } /** * This function tries to make a particular range of a file allocated * (corresponding to disk space) it is advisory, and the range specified in the * arguments will never contain live data. */ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { #if defined(WIN32) // Windows-specific version. HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); LARGE_INTEGER nFileSize; int64_t nEndPos = (int64_t)offset + length; nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; nFileSize.u.HighPart = nEndPos >> 32; SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); SetEndOfFile(hFile); #elif defined(MAC_OSX) // OSX specific version. fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; fst.fst_length = (off_t)offset + length; fst.fst_bytesalloc = 0; if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { fst.fst_flags = F_ALLOCATEALL; fcntl(fileno(file), F_PREALLOCATE, &fst); } ftruncate(fileno(file), fst.fst_length); #elif defined(__linux__) // Version using posix_fallocate. off_t nEndPos = (off_t)offset + length; posix_fallocate(fileno(file), 0, nEndPos); #else // Fallback version // TODO: just write one byte per block static const char buf[65536] = {}; if (fseek(file, offset, SEEK_SET)) { return; } while (length > 0) { unsigned int now = 65536; if (length < now) { now = length; } // Allowed to fail; this function is advisory anyway. fwrite(buf, 1, now, file); length -= now; } #endif } #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) { WCHAR pszPath[MAX_PATH] = L""; if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) { return fs::path(pszPath); } LogPrintf( "SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); return fs::path(""); } #endif void runCommand(const std::string &strCommand) { if (strCommand.empty()) { return; } #ifndef WIN32 int nErr = ::system(strCommand.c_str()); #else int nErr = ::_wsystem( std::wstring_convert, wchar_t>() .from_bytes(strCommand) .c_str()); #endif if (nErr) { LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); } } void SetupEnvironment() { #ifdef HAVE_MALLOPT_ARENA_MAX // glibc-specific: On 32-bit systems set the number of arenas to 1. By // default, since glibc 2.10, the C library will create up to two heap // arenas per core. This is known to cause excessive virtual address space // usage in our usage. Work around it by setting the maximum number of // arenas to 1. if (sizeof(void *) == 4) { mallopt(M_ARENA_MAX, 1); } #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale may // be invalid, in which case the "C" locale is used as fallback. #if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && \ !defined(__OpenBSD__) try { // Raises a runtime error if current locale is invalid. std::locale(""); } catch (const std::runtime_error &) { setenv("LC_ALL", "C", 1); } #elif defined(WIN32) // Set the default input/output charset is utf-8 SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); #endif // The path locale is lazy initialized and to avoid deinitialization errors // in multithreading environments, it is set explicitly by the main thread. // A dummy locale is used to extract the internal default locale, used by // fs::path, which is then used to explicitly imbue the path. std::locale loc = fs::path::imbue(std::locale::classic()); #ifndef WIN32 fs::path::imbue(loc); #else fs::path::imbue(std::locale(loc, new std::codecvt_utf8_utf16())); #endif } bool SetupNetworking() { #ifdef WIN32 // Initialize Windows Sockets. WSADATA wsadata; int ret = WSAStartup(MAKEWORD(2, 2), &wsadata); if (ret != NO_ERROR || LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { return false; } #endif return true; } int GetNumCores() { return std::thread::hardware_concurrency(); } std::string CopyrightHolders(const std::string &strPrefix) { return strPrefix + strprintf(_(COPYRIGHT_HOLDERS), COPYRIGHT_HOLDERS_SUBSTITUTION); } // Obtain the application startup time (used for uptime calculation) int64_t GetStartupTime() { return nStartupTime; } fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific) { if (path.is_absolute()) { return path; } return fs::absolute(path, GetDataDir(net_specific)); } void ScheduleBatchPriority() { #ifdef SCHED_BATCH const static sched_param param{}; if (pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m) != 0) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); } #endif } namespace util { #ifdef WIN32 WinCmdLineArgs::WinCmdLineArgs() { wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); std::wstring_convert, wchar_t> utf8_cvt; argv = new char *[argc]; args.resize(argc); for (int i = 0; i < argc; i++) { args[i] = utf8_cvt.to_bytes(wargv[i]); argv[i] = &*args[i].begin(); } LocalFree(wargv); } WinCmdLineArgs::~WinCmdLineArgs() { delete[] argv; } std::pair WinCmdLineArgs::get() { return std::make_pair(argc, argv); } #endif } // namespace util diff --git a/src/util/system.h b/src/util/system.h index 0d31c8a9e..c58802431 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -1,425 +1,423 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. /** * Server/client environment: argument handling, config file parsing, * thread wrappers, startup time */ #ifndef BITCOIN_UTIL_SYSTEM_H #define BITCOIN_UTIL_SYSTEM_H #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include +#include #include #include #include // for boost::thread_interrupted #include #include #include #include #include #include #include // Application startup time (used for uptime calculation) int64_t GetStartupTime(); extern const char *const BITCOIN_CONF_FILENAME; /** Translate a message to the native language of the user. */ const extern std::function G_TRANSLATION_FUN; /** * Translation function. * If no translation function is set, simply return the input. */ inline std::string _(const char *psz) { return G_TRANSLATION_FUN ? (G_TRANSLATION_FUN)(psz) : psz; } void SetupEnvironment(); bool SetupNetworking(); template bool error(const char *fmt, const Args &... args) { LogPrintf("ERROR: %s\n", tfm::format(fmt, args...)); return false; } void PrintExceptionContinue(const std::exception *pex, const char *pszThread); bool FileCommit(FILE *file); bool TruncateFile(FILE *file, unsigned int length); int RaiseFileDescriptorLimit(int nMinFD); void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); bool RenameOver(fs::path src, fs::path dest); bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only = false); void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name); bool DirIsWritable(const fs::path &directory); bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes = 0); /** * Release all directory locks. This is used for unit testing only, at runtime * the global destructor will take care of the locks. */ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path &p); fs::path GetDefaultDataDir(); // The blocks directory is always net specific. const fs::path &GetBlocksDir(); const fs::path &GetDataDir(bool fNetSpecific = true); // Return true if -datadir option points to a valid directory or is not // specified. bool CheckDataDirOption(); /** Tests only */ void ClearDatadirCache(); fs::path GetConfigFile(const std::string &confPath); #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif void runCommand(const std::string &strCommand); NODISCARD bool ParseKeyValue(std::string &key, std::string &val); /** * Most paths passed as configuration arguments are treated as relative to * the datadir if they are not absolute. * * @param path The path to be conditionally prefixed with datadir. * @param net_specific Forwarded to GetDataDir(). * @return The normalized path. */ fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific = true); inline bool IsSwitchChar(char c) { #ifdef WIN32 return c == '-' || c == '/'; #else return c == '-'; #endif } enum class OptionsCategory { OPTIONS, CONNECTION, WALLET, WALLET_DEBUG_TEST, ZMQ, DEBUG_TEST, CHAINPARAMS, NODE_RELAY, BLOCK_CREATION, RPC, GUI, COMMANDS, REGISTER_COMMANDS, // Always the last option to avoid printing these in the help HIDDEN, // Avalanche is still experimental, so we keep it hidden for now. AVALANCHE, }; struct SectionInfo { std::string m_name; std::string m_file; int m_line; }; class ArgsManager { public: enum Flags { NONE = 0x00, // Boolean options can accept negation syntax -noOPTION or -noOPTION=1 ALLOW_BOOL = 0x01, ALLOW_INT = 0x02, ALLOW_STRING = 0x04, ALLOW_ANY = ALLOW_BOOL | ALLOW_INT | ALLOW_STRING, DEBUG_ONLY = 0x100, /* Some options would cause cross-contamination if values for * mainnet were used while running on regtest/testnet (or vice-versa). * Setting them as NETWORK_ONLY ensures that sharing a config file * between mainnet and regtest/testnet won't cause problems due to these * parameters by accident. */ NETWORK_ONLY = 0x200, }; protected: friend class ArgsManagerHelper; struct Arg { std::string m_help_param; std::string m_help_text; unsigned int m_flags; }; mutable RecursiveMutex cs_args; - std::map> - m_override_args GUARDED_BY(cs_args); - std::map> - m_config_args GUARDED_BY(cs_args); + util::Settings m_settings GUARDED_BY(cs_args); std::string m_network GUARDED_BY(cs_args); std::set m_network_only_args GUARDED_BY(cs_args); std::map> m_available_args GUARDED_BY(cs_args); std::list m_config_sections GUARDED_BY(cs_args); NODISCARD bool ReadConfigStream(std::istream &stream, const std::string &filepath, std::string &error, bool ignore_invalid_keys = false); public: ArgsManager(); /** * Select the network in use */ void SelectConfigNetwork(const std::string &network); NODISCARD bool ParseParameters(int argc, const char *const argv[], std::string &error); bool ReadConfigFiles(std::string &error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when they are specified * in the default section but not overridden on the command line or in a * network-specific section in the config file. */ const std::set GetUnsuitableSectionOnlyArgs() const; /** * Log warnings for unrecognized section names in the config file. */ const std::list GetUnrecognizedSections() const; /** * Return a vector of strings of the given argument * * @param strArg Argument to get (e.g. "-foo") * @return command-line arguments */ std::vector GetArgs(const std::string &strArg) const; /** * Return true if the given argument has been manually set. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument has been set */ bool IsArgSet(const std::string &strArg) const; /** * Return true if the argument was originally passed as a negated option, * i.e. -nofoo. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument was passed negated */ bool IsArgNegated(const std::string &strArg) const; /** * Return string argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param default (e.g. "1") * @return command-line argument or default value */ std::string GetArg(const std::string &strArg, const std::string &strDefault) const; /** * Return integer argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param default (e.g. 1) * @return command-line argument (0 if invalid number) or default value */ int64_t GetArg(const std::string &strArg, int64_t nDefault) const; /** * Return boolean argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param default (true or false) * @return command-line argument or default value */ bool GetBoolArg(const std::string &strArg, bool fDefault) const; /** * Set an argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param strValue Value (e.g. "1") * @return true if argument gets set, false if it already had a value */ bool SoftSetArg(const std::string &strArg, const std::string &strValue); /** * Set a boolean argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param fValue Value (e.g. false) * @return true if argument gets set, false if it already had a value */ bool SoftSetBoolArg(const std::string &strArg, bool fValue); // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already // been set. Also called directly in testing. void ForceSetArg(const std::string &strArg, const std::string &strValue); // Forces a multi arg setting, used only in testing void ForceSetMultiArg(const std::string &strArg, - const std::string &strValue); + const std::vector &values); /** * Looks for -regtest, -testnet and returns the appropriate BIP70 chain * name. * @return CBaseChainParams::MAIN by default; raises runtime error if an * invalid combination is given. */ std::string GetChainName() const; /** * Add argument */ void AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat); /** * Remove a forced arg setting, used only in testing. */ void ClearForcedArg(const std::string &strArg); /** * Add many hidden arguments */ void AddHiddenArgs(const std::vector &args); /** * Clear available arguments */ void ClearArgs() { LOCK(cs_args); m_available_args.clear(); m_network_only_args.clear(); } /** * Get the help string */ std::string GetHelpMessage() const; /** * Return Flags for known arg. * Return ArgsManager::NONE for unknown arg. */ unsigned int FlagsOfKnownArg(const std::string &key) const; }; extern ArgsManager gArgs; /** * @return true if help has been requested via a command-line arg */ bool HelpRequested(const ArgsManager &args); /** * Format a string to be used as group of options in help messages. * * @param message Group name (e.g. "RPC server options:") * @return the formatted string */ std::string HelpMessageGroup(const std::string &message); /** * Format a string to be used as option description in help messages. * * @param option Option message (e.g. "-rpcuser=") * @param message Option description (e.g. "Username for JSON-RPC connections") * @return the formatted string */ std::string HelpMessageOpt(const std::string &option, const std::string &message); /** * Return the number of cores available on the current system. * @note This does count virtual cores, such as those provided by * HyperThreading. */ int GetNumCores(); /** * .. and a wrapper that just calls func once */ template void TraceThread(const char *name, Callable func) { util::ThreadRename(name); try { LogPrintf("%s thread start\n", name); func(); LogPrintf("%s thread exit\n", name); } catch (const boost::thread_interrupted &) { LogPrintf("%s thread interrupt\n", name); throw; } catch (const std::exception &e) { PrintExceptionContinue(&e, name); throw; } catch (...) { PrintExceptionContinue(nullptr, name); throw; } } std::string CopyrightHolders(const std::string &strPrefix); /** * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. * */ void ScheduleBatchPriority(); namespace util { //! Simplification of std insertion template inline void insert(Tdst &dst, const Tsrc &src) { dst.insert(dst.begin(), src.begin(), src.end()); } template inline void insert(std::set &dst, const Tsrc &src) { dst.insert(src.begin(), src.end()); } #ifdef WIN32 class WinCmdLineArgs { public: WinCmdLineArgs(); ~WinCmdLineArgs(); std::pair get(); private: int argc; char **argv; std::vector args; }; #endif } // namespace util #endif // BITCOIN_UTIL_SYSTEM_H diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index 831071f3a..1bfcd6cd9 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -1,86 +1,87 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' This checks if all command line args are documented. Return value is 0 to indicate no error. Author: @MarcoFalke ''' from subprocess import check_output from pprint import PrettyPrinter import glob import re TOP_LEVEL = 'git rev-parse --show-toplevel' FOLDER_SRC = '/src/**/' FOLDER_TEST = '/src/**/test/' EXTENSIONS = ["*.c", "*.h", "*.cpp", "*.cc", "*.hpp"] REGEX_ARG = r'(?:ForceSet|SoftSet|Get|Is)(?:Bool)?Args?(?:Set)?\(\s*"(-[^"]+)"' REGEX_DOC = r'AddArg\(\s*"(-[^"=]+?)(?:=|")' # list false positive unknows arguments SET_FALSE_POSITIVE_UNKNOWNS = set([ + '-includeconf', '-regtest', '-testnet', '-zmqpubhashblock', '-zmqpubhashtx', '-zmqpubrawblock', '-zmqpubrawtx', ]) # list false positive undocumented arguments SET_FALSE_POSITIVE_UNDOCUMENTED = set(['-dbcrashratio', '-enableminerfund', '-forcecompactdb', '-parkdeepreorg', '-automaticunparking', # Remove after May 2020 upgrade '-phononactivationtime', '-replayprotectionactivationtime', ]) def main(): top_level = check_output(TOP_LEVEL, shell=True, universal_newlines=True).strip() source_files = [] test_files = [] for extension in EXTENSIONS: source_files += glob.glob(top_level + FOLDER_SRC + extension, recursive=True) test_files += glob.glob(top_level + FOLDER_TEST + extension, recursive=True) files = set(source_files) - set(test_files) args_used = set() args_docd = set() for file in files: with open(file, 'r', encoding='utf-8') as f: content = f.read() args_used |= set(re.findall(re.compile(REGEX_ARG), content)) args_docd |= set(re.findall(re.compile(REGEX_DOC), content)) args_used |= SET_FALSE_POSITIVE_UNKNOWNS args_docd |= SET_FALSE_POSITIVE_UNDOCUMENTED args_need_doc = args_used - args_docd args_unknown = args_docd - args_used pp = PrettyPrinter() print("Args used : {}".format(len(args_used))) print("Args documented : {}".format(len(args_docd))) print("Args undocumented: {}".format(len(args_need_doc))) pp.pprint(args_need_doc) print("Args unknown : {}".format(len(args_unknown))) pp.pprint(args_unknown) if __name__ == "__main__": main()