Skip to content

Commit 083c954

Browse files
ryanofskyjamesob
andcommitted
Add settings_tests
Co-authored-by: James O'Beirne <[email protected]>
1 parent 7f40528 commit 083c954

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ BITCOIN_TESTS =\
152152
test/script_standard_tests.cpp \
153153
test/scriptnum_tests.cpp \
154154
test/serialize_tests.cpp \
155+
test/settings_tests.cpp \
155156
test/sighash_tests.cpp \
156157
test/sigopcount_tests.cpp \
157158
test/skiplist_tests.cpp \

src/test/settings_tests.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright (c) 2011-2019 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <util/settings.h>
6+
7+
#include <test/util.h>
8+
#include <test/util/setup_common.h>
9+
10+
#include <boost/test/unit_test.hpp>
11+
#include <univalue.h>
12+
#include <util/strencodings.h>
13+
#include <vector>
14+
15+
BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup)
16+
17+
//! Check settings struct contents against expected json strings.
18+
static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val)
19+
{
20+
util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false);
21+
util::SettingsValue list_value(util::SettingsValue::VARR);
22+
for (const auto& item : GetSettingsList(settings, "section", "name", false)) {
23+
list_value.push_back(item);
24+
}
25+
BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val);
26+
BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val);
27+
};
28+
29+
// Simple settings merge test case.
30+
BOOST_AUTO_TEST_CASE(Simple)
31+
{
32+
util::Settings settings;
33+
settings.command_line_options["name"].push_back("val1");
34+
settings.command_line_options["name"].push_back("val2");
35+
settings.ro_config["section"]["name"].push_back(2);
36+
37+
// The last given arg takes precedence when specified via commandline.
38+
CheckValues(settings, R"("val2")", R"(["val1","val2",2])");
39+
40+
util::Settings settings2;
41+
settings2.ro_config["section"]["name"].push_back("val2");
42+
settings2.ro_config["section"]["name"].push_back("val3");
43+
44+
// The first given arg takes precedence when specified via config file.
45+
CheckValues(settings2, R"("val2")", R"(["val2","val3"])");
46+
}
47+
48+
// Test different ways settings can be merged, and verify results. This test can
49+
// be used to confirm that updates to settings code don't change behavior
50+
// unintentionally.
51+
struct MergeTestingSetup : public BasicTestingSetup {
52+
//! Max number of actions to sequence together. Can decrease this when
53+
//! debugging to make test results easier to understand.
54+
static constexpr int MAX_ACTIONS = 3;
55+
56+
enum Action { END, SET, NEGATE, SECTION_SET, SECTION_NEGATE };
57+
using ActionList = Action[MAX_ACTIONS];
58+
59+
//! Enumerate all possible test configurations.
60+
template <typename Fn>
61+
void ForEachMergeSetup(Fn&& fn)
62+
{
63+
ActionList arg_actions = {};
64+
// command_line_options do not have sections. Only iterate over SET and NEGATE
65+
ForEachNoDup(arg_actions, SET, NEGATE, [&]{
66+
ActionList conf_actions = {};
67+
ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&]{
68+
for (bool force_set : {false, true}) {
69+
for (bool ignore_default_section_config : {false, true}) {
70+
fn(arg_actions, conf_actions, force_set, ignore_default_section_config);
71+
}
72+
}
73+
});
74+
});
75+
}
76+
};
77+
78+
// Regression test covering different ways config settings can be merged. The
79+
// test parses and merges settings, representing the results as strings that get
80+
// compared against an expected hash. To debug, the result strings can be dumped
81+
// to a file (see comments below).
82+
BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup)
83+
{
84+
CHash256 out_sha;
85+
FILE* out_file = nullptr;
86+
if (const char* out_path = getenv("SETTINGS_MERGE_TEST_OUT")) {
87+
out_file = fsbridge::fopen(out_path, "w");
88+
if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
89+
}
90+
91+
const std::string& network = CBaseChainParams::MAIN;
92+
ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool force_set,
93+
bool ignore_default_section_config) {
94+
std::string desc;
95+
int value_suffix = 0;
96+
util::Settings settings;
97+
98+
const std::string& name = ignore_default_section_config ? "wallet" : "server";
99+
auto push_values = [&](Action action, const char* value_prefix, const std::string& name_prefix,
100+
std::vector<util::SettingsValue>& dest) {
101+
if (action == SET || action == SECTION_SET) {
102+
for (int i = 0; i < 2; ++i) {
103+
dest.push_back(value_prefix + std::to_string(++value_suffix));
104+
desc += " " + name_prefix + name + "=" + dest.back().get_str();
105+
}
106+
} else if (action == NEGATE || action == SECTION_NEGATE) {
107+
dest.push_back(false);
108+
desc += " " + name_prefix + "no" + name;
109+
}
110+
};
111+
112+
if (force_set) {
113+
settings.forced_settings[name] = "forced";
114+
desc += " " + name + "=forced";
115+
}
116+
for (Action arg_action : arg_actions) {
117+
push_values(arg_action, "a", "-", settings.command_line_options[name]);
118+
}
119+
for (Action conf_action : conf_actions) {
120+
bool use_section = conf_action == SECTION_SET || conf_action == SECTION_NEGATE;
121+
push_values(conf_action, "c", use_section ? network + "." : "",
122+
settings.ro_config[use_section ? network : ""][name]);
123+
}
124+
125+
desc += " || ";
126+
desc += GetSetting(settings, network, name, ignore_default_section_config, /* get_chain_name= */ false).write();
127+
desc += " |";
128+
for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) {
129+
desc += " ";
130+
desc += s.write();
131+
}
132+
desc += " |";
133+
if (OnlyHasDefaultSectionSetting(settings, network, name)) desc += " ignored";
134+
desc += "\n";
135+
136+
out_sha.Write((const unsigned char*)desc.data(), desc.size());
137+
if (out_file) {
138+
BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
139+
}
140+
});
141+
142+
if (out_file) {
143+
if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
144+
out_file = nullptr;
145+
}
146+
147+
unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
148+
out_sha.Finalize(out_sha_bytes);
149+
std::string out_sha_hex = HexStr(std::begin(out_sha_bytes), std::end(out_sha_bytes));
150+
151+
// If check below fails, should manually dump the results with:
152+
//
153+
// SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=settings_tests/Merge
154+
//
155+
// And verify diff against previous results to make sure the changes are expected.
156+
//
157+
// Results file is formatted like:
158+
//
159+
// <input> || GetSetting() | GetSettingsList() | OnlyHasDefaultSectionSetting()
160+
BOOST_CHECK_EQUAL(out_sha_hex, "79db02d74e3e193196541b67c068b40ebd0c124a24b3ecbe9cbf7e85b1c4ba7a");
161+
}
162+
163+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)