Skip to content

Commit 2dfeff1

Browse files
ryanofskyjamesob
andcommitted
Add settings_tests
Co-authored-by: James O'Beirne <[email protected]>
1 parent 63e2861 commit 2dfeff1

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ BITCOIN_TESTS =\
133133
test/script_standard_tests.cpp \
134134
test/scriptnum_tests.cpp \
135135
test/serialize_tests.cpp \
136+
test/settings_tests.cpp \
136137
test/sighash_tests.cpp \
137138
test/sigopcount_tests.cpp \
138139
test/skiplist_tests.cpp \

src/test/settings_tests.cpp

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

src/test/util.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,37 @@ std::string getnewaddress(CWallet& w);
3434
/** Returns the generated coin */
3535
CTxIn generatetoaddress(const std::string& address);
3636

37+
/**
38+
* Increment a string. Useful to enumerate all fixed length strings with
39+
* characters in [min_char, max_char].
40+
*/
41+
template <typename CharType, size_t StringLength>
42+
bool NextString(CharType (&string)[StringLength], CharType min_char, CharType max_char)
43+
{
44+
for (CharType& elem : string) {
45+
bool has_next = elem != max_char;
46+
elem = elem < min_char || elem >= max_char ? min_char : CharType(elem + 1);
47+
if (has_next) return true;
48+
}
49+
return false;
50+
}
51+
52+
/**
53+
* Iterate over string values and call function for each string without
54+
* successive duplicate characters.
55+
*/
56+
template <typename CharType, size_t StringLength, typename Fn>
57+
void ForEachNoDup(CharType (&string)[StringLength], CharType min_char, CharType max_char, Fn&& fn) {
58+
for (bool has_next = true; has_next; has_next = NextString(string, min_char, max_char)) {
59+
int prev = -1;
60+
bool skip_string = false;
61+
for (CharType c : string) {
62+
if (c == prev) skip_string = true;
63+
if (skip_string || c < min_char || c > max_char) break;
64+
prev = c;
65+
}
66+
if (!skip_string) fn();
67+
}
68+
}
3769

3870
#endif // BITCOIN_TEST_UTIL_H

0 commit comments

Comments
 (0)