Skip to content

Commit 1d543ad

Browse files
committed
Add settings_tests
1 parent 63ce023 commit 1d543ad

File tree

3 files changed

+177
-0
lines changed

3 files changed

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