diff --git a/src/test/util.h b/src/test/util.h --- a/src/test/util.h +++ b/src/test/util.h @@ -21,4 +21,48 @@ std::shared_ptr PrepareBlock(const Config &config, const CScript &coinbase_scriptPubKey); +/** + * Increment a string. Useful to enumerate all fixed length strings with + * characters in [min_char, max_char]. + */ +template +bool NextString(CharType (&string)[StringLength], CharType min_char, + CharType max_char) { + for (CharType &elem : string) { + bool has_next = elem != max_char; + elem = + elem < min_char || elem >= max_char ? min_char : CharType(elem + 1); + if (has_next) { + return true; + } + } + return false; +} + +/** + * Iterate over string values and call function for each string without + * successive duplicate characters. + */ +template +void ForEachNoDup(CharType (&string)[StringLength], CharType min_char, + CharType max_char, Fn &&fn) { + for (bool has_next = true; has_next; + has_next = NextString(string, min_char, max_char)) { + int prev = -1; + bool skip_string = false; + for (CharType c : string) { + if (c == prev) { + skip_string = true; + } + if (skip_string || c < min_char || c > max_char) { + break; + } + prev = c; + } + if (!skip_string) { + fn(); + } + } +} + #endif // BITCOIN_TEST_UTIL_H diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -800,18 +801,20 @@ // outside a network section, and non-network specific settings like "-server" // that aren't sensitive to the network. // -struct SettingsMergeTestingSetup : public BasicTestingSetup { +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 { SET = 0, NEGATE, SECTION_SET, SECTION_NEGATE, END }; + enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE }; using ActionList = Action[MAX_ACTIONS]; //! Enumerate all possible test configurations. template void ForEachMergeSetup(Fn &&fn) { - ForEachActionList([&](const ActionList &arg_actions) { - ForEachActionList([&](const ActionList &conf_actions) { + 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 : @@ -833,37 +836,6 @@ }); } - //! Enumerate interesting combinations of actions. - template void ForEachActionList(Fn &&fn) { - ActionList actions = {SET}; - for (bool done = false; !done;) { - int prev_action = -1; - bool skip_actions = false; - for (Action action : actions) { - if ((prev_action == END && action != END) || - (prev_action != END && action == prev_action)) { - // To cut down list of enumerated settings, skip enumerating - // settings with ignored actions after an END, and settings - // that repeat the same action twice in a row. - skip_actions = true; - break; - } - prev_action = action; - } - if (!skip_actions) { - fn(actions); - } - done = true; - for (Action &action : actions) { - action = Action(action < END ? action + 1 : 0); - if (action) { - done = false; - break; - } - } - } - } - //! Translate actions into a list of = setting strings. std::vector GetValues(const ActionList &actions, const std::string §ion, @@ -872,7 +844,7 @@ std::vector values; int suffix = 0; for (Action action : actions) { - if (action == END) { + if (action == NONE) { break; } std::string prefix; @@ -896,11 +868,11 @@ // 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 below). -BOOST_FIXTURE_TEST_CASE(util_SettingsMerge, SettingsMergeTestingSetup) { +// 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("SETTINGS_MERGE_TEST_OUT")) { + 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(), @@ -919,7 +891,7 @@ desc += network; parser.m_network = network; - const std::string &name = net_specific ? "server" : "wallet"; + const std::string &name = net_specific ? "wallet" : "server"; const std::string key = "-" + name; parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -1015,8 +987,8 @@ // If check below fails, should manually dump the results with: // - // SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin - // --run_test=util_tests/util_SettingsMerge + // 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. @@ -1027,7 +999,146 @@ // BOOST_CHECK_EQUAL( out_sha_hex, - "80964e17fbd3c5569d3c824d032e28e2d319ef57494735b0e76eb7aad9957f2c"); + "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, + "b284f4b4a15dd6bf8c06213a69a004b1960388e1d9917173927db52ac220927f"); } BOOST_AUTO_TEST_CASE(util_FormatMoney) {