Skip to content

Commit 10ed4df

Browse files
author
MarcoFalke
committed
Merge #15869: Add settings merge test to prevent regresssions
151f3e9 Add settings merge test to prevent regresssions (Russell Yanofsky) Pull request description: Test-only change. Motivation: I'm trying to clean up settings code and add support for read/write settings without changing existing behavior, but current tests are very scattershot and don't actually cover a lot of current behavior. ACKs for commit 151f3e: jonasschnelli: utACK 151f3e9. MarcoFalke: utACK 151f3e9 Tree-SHA512: f9062f078da02855cdbdcae37d0cea5684e82adbe5c701a8eb042ee4a57d899f0ffb6a9db3bcf58b639dff22b2b2d8a75f9a7917402df58904036753d65a1e3e
2 parents 45d8b71 + 151f3e9 commit 10ed4df

File tree

1 file changed

+229
-0
lines changed

1 file changed

+229
-0
lines changed

src/test/util_tests.cpp

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ struct TestArgsManager : public ArgsManager
158158
AddArg(args[i], "", false, OptionsCategory::OPTIONS);
159159
}
160160
}
161+
using ArgsManager::ReadConfigStream;
162+
using ArgsManager::cs_args;
163+
using ArgsManager::m_network;
161164
};
162165

163166
BOOST_AUTO_TEST_CASE(util_ParseParameters)
@@ -575,6 +578,232 @@ BOOST_AUTO_TEST_CASE(util_GetChainName)
575578
BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
576579
}
577580

581+
// Test different ways settings can be merged, and verify results. This test can
582+
// be used to confirm that updates to settings code don't change behavior
583+
// unintentially.
584+
//
585+
// The test covers:
586+
//
587+
// - Combining different setting actions. Possible actions are: configuring a
588+
// setting, negating a setting (adding "-no" prefix), and configuring/negating
589+
// settings in a network section (adding "main." or "test." prefixes).
590+
//
591+
// - Combining settings from command line arguments and a config file.
592+
//
593+
// - Combining SoftSet and ForceSet calls.
594+
//
595+
// - Testing "main" and "test" network values to make sure settings from network
596+
// sections are applied and to check for mainnet-specific behaviors like
597+
// inheriting settings from the default section.
598+
//
599+
// - Testing network-specific settings like "-wallet", that may be ignored
600+
// outside a network section, and non-network specific settings like "-server"
601+
// that aren't sensitive to the network.
602+
//
603+
struct SettingsMergeTestingSetup : public BasicTestingSetup {
604+
//! Max number of actions to sequence together. Can decrease this when
605+
//! debugging to make test results easier to understand.
606+
static constexpr int MAX_ACTIONS = 3;
607+
608+
enum Action { SET = 0, NEGATE, SECTION_SET, SECTION_NEGATE, END };
609+
using ActionList = Action[MAX_ACTIONS];
610+
611+
//! Enumerate all possible test configurations.
612+
template <typename Fn>
613+
void ForEachMergeSetup(Fn&& fn)
614+
{
615+
ForEachActionList([&](const ActionList& arg_actions) {
616+
ForEachActionList([&](const ActionList& conf_actions) {
617+
for (bool soft_set : {false, true}) {
618+
for (bool force_set : {false, true}) {
619+
for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) {
620+
for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) {
621+
for (bool net_specific : {false, true}) {
622+
fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
623+
}
624+
}
625+
}
626+
}
627+
}
628+
});
629+
});
630+
}
631+
632+
//! Enumerate interesting combinations of actions.
633+
template <typename Fn>
634+
void ForEachActionList(Fn&& fn)
635+
{
636+
ActionList actions = {SET};
637+
for (bool done = false; !done;) {
638+
int prev_action = -1;
639+
bool skip_actions = false;
640+
for (Action action : actions) {
641+
if ((prev_action == END && action != END) || (prev_action != END && action == prev_action)) {
642+
// To cut down list of enumerated settings, skip enumerating
643+
// settings with ignored actions after an END, and settings that
644+
// repeat the same action twice in a row.
645+
skip_actions = true;
646+
break;
647+
}
648+
prev_action = action;
649+
}
650+
if (!skip_actions) fn(actions);
651+
done = true;
652+
for (Action& action : actions) {
653+
action = Action(action < END ? action + 1 : 0);
654+
if (action) {
655+
done = false;
656+
break;
657+
}
658+
}
659+
}
660+
}
661+
662+
//! Translate actions into a list of <key>=<value> setting strings.
663+
std::vector<std::string> GetValues(const ActionList& actions,
664+
const std::string& section,
665+
const std::string& name,
666+
const std::string& value_prefix)
667+
{
668+
std::vector<std::string> values;
669+
int suffix = 0;
670+
for (Action action : actions) {
671+
if (action == END) break;
672+
std::string prefix;
673+
if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + ".";
674+
if (action == SET || action == SECTION_SET) {
675+
for (int i = 0; i < 2; ++i) {
676+
values.push_back(prefix + name + "=" + value_prefix + std::to_string(++suffix));
677+
}
678+
}
679+
if (action == NEGATE || action == SECTION_NEGATE) {
680+
values.push_back(prefix + "no" + name + "=1");
681+
}
682+
}
683+
return values;
684+
}
685+
};
686+
687+
// Regression test covering different ways config settings can be merged. The
688+
// test parses and merges settings, representing the results as strings that get
689+
// compared against an expected hash. To debug, the result strings can be dumped
690+
// to a file (see below).
691+
BOOST_FIXTURE_TEST_CASE(util_SettingsMerge, SettingsMergeTestingSetup)
692+
{
693+
CHash256 out_sha;
694+
FILE* out_file = nullptr;
695+
if (const char* out_path = getenv("SETTINGS_MERGE_TEST_OUT")) {
696+
out_file = fsbridge::fopen(out_path, "w");
697+
if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
698+
}
699+
700+
ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set,
701+
const std::string& section, const std::string& network, bool net_specific) {
702+
TestArgsManager parser;
703+
LOCK(parser.cs_args);
704+
705+
std::string desc = "net=";
706+
desc += network;
707+
parser.m_network = network;
708+
709+
const std::string& name = net_specific ? "server" : "wallet";
710+
const std::string key = "-" + name;
711+
parser.AddArg(key, name, false, OptionsCategory::OPTIONS);
712+
if (net_specific) parser.SetNetworkOnlyArg(key);
713+
714+
auto args = GetValues(arg_actions, section, name, "a");
715+
std::vector<const char*> argv = {"ignored"};
716+
for (auto& arg : args) {
717+
arg.insert(0, "-");
718+
desc += " ";
719+
desc += arg;
720+
argv.push_back(arg.c_str());
721+
}
722+
std::string error;
723+
BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
724+
BOOST_CHECK_EQUAL(error, "");
725+
726+
std::string conf;
727+
for (auto& conf_val : GetValues(conf_actions, section, name, "c")) {
728+
desc += " ";
729+
desc += conf_val;
730+
conf += conf_val;
731+
conf += "\n";
732+
}
733+
std::istringstream conf_stream(conf);
734+
BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
735+
BOOST_CHECK_EQUAL(error, "");
736+
737+
if (soft_set) {
738+
desc += " soft";
739+
parser.SoftSetArg(key, "soft1");
740+
parser.SoftSetArg(key, "soft2");
741+
}
742+
743+
if (force_set) {
744+
desc += " force";
745+
parser.ForceSetArg(key, "force1");
746+
parser.ForceSetArg(key, "force2");
747+
}
748+
749+
desc += " || ";
750+
751+
if (!parser.IsArgSet(key)) {
752+
desc += "unset";
753+
BOOST_CHECK(!parser.IsArgNegated(key));
754+
BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default");
755+
BOOST_CHECK(parser.GetArgs(key).empty());
756+
} else if (parser.IsArgNegated(key)) {
757+
desc += "negated";
758+
BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0");
759+
BOOST_CHECK(parser.GetArgs(key).empty());
760+
} else {
761+
desc += parser.GetArg(key, "default");
762+
desc += " |";
763+
for (const auto& arg : parser.GetArgs(key)) {
764+
desc += " ";
765+
desc += arg;
766+
}
767+
}
768+
769+
std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs();
770+
if (!ignored.empty()) {
771+
desc += " | ignored";
772+
for (const auto& arg : ignored) {
773+
desc += " ";
774+
desc += arg;
775+
}
776+
}
777+
778+
desc += "\n";
779+
780+
out_sha.Write((const unsigned char*)desc.data(), desc.size());
781+
if (out_file) {
782+
BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
783+
}
784+
});
785+
786+
if (out_file) {
787+
if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
788+
out_file = nullptr;
789+
}
790+
791+
unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
792+
out_sha.Finalize(out_sha_bytes);
793+
std::string out_sha_hex = HexStr(std::begin(out_sha_bytes), std::end(out_sha_bytes));
794+
795+
// If check below fails, should manually dump the results with:
796+
//
797+
// SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_SettingsMerge
798+
//
799+
// And verify diff against previous results to make sure the changes are expected.
800+
//
801+
// Results file is formatted like:
802+
//
803+
// <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
804+
BOOST_CHECK_EQUAL(out_sha_hex, "80964e17fbd3c5569d3c824d032e28e2d319ef57494735b0e76eb7aad9957f2c");
805+
}
806+
578807
BOOST_AUTO_TEST_CASE(util_FormatMoney)
579808
{
580809
BOOST_CHECK_EQUAL(FormatMoney(0), "0.00");

0 commit comments

Comments
 (0)