Bitcoin Core  22.99.0
P2P Digital Currency
settings_tests.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2020 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 
8 #include <test/util/str.h>
9 
10 
11 #include <boost/test/unit_test.hpp>
12 #include <univalue.h>
13 #include <util/strencodings.h>
14 #include <util/string.h>
15 #include <util/system.h>
16 #include <vector>
17 
18 inline bool operator==(const util::SettingsValue& a, const util::SettingsValue& b)
19 {
20  return a.write() == b.write();
21 }
22 
23 inline std::ostream& operator<<(std::ostream& os, const util::SettingsValue& value)
24 {
25  os << value.write();
26  return os;
27 }
28 
29 inline std::ostream& operator<<(std::ostream& os, const std::pair<std::string, util::SettingsValue>& kv)
30 {
32  out.__pushKV(kv.first, kv.second);
33  os << out.write();
34  return os;
35 }
36 
37 inline void WriteText(const fs::path& path, const std::string& text)
38 {
39  fsbridge::ofstream file;
40  file.open(path);
41  file << text;
42 }
43 
45 
47 {
48  fs::path path = m_args.GetDataDirBase() / "settings.json";
49 
50  WriteText(path, R"({
51  "string": "string",
52  "num": 5,
53  "bool": true,
54  "null": null
55  })");
56 
57  std::map<std::string, util::SettingsValue> expected{
58  {"string", "string"},
59  {"num", 5},
60  {"bool", true},
61  {"null", {}},
62  };
63 
64  // Check file read.
65  std::map<std::string, util::SettingsValue> values;
66  std::vector<std::string> errors;
67  BOOST_CHECK(util::ReadSettings(path, values, errors));
68  BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end());
69  BOOST_CHECK(errors.empty());
70 
71  // Check no errors if file doesn't exist.
72  fs::remove(path);
73  BOOST_CHECK(util::ReadSettings(path, values, errors));
74  BOOST_CHECK(values.empty());
75  BOOST_CHECK(errors.empty());
76 
77  // Check duplicate keys not allowed
78  WriteText(path, R"({
79  "dupe": "string",
80  "dupe": "dupe"
81  })");
82  BOOST_CHECK(!util::ReadSettings(path, values, errors));
83  std::vector<std::string> dup_keys = {strprintf("Found duplicate key dupe in settings file %s", path.string())};
84  BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), dup_keys.begin(), dup_keys.end());
85 
86  // Check non-kv json files not allowed
87  WriteText(path, R"("non-kv")");
88  BOOST_CHECK(!util::ReadSettings(path, values, errors));
89  std::vector<std::string> non_kv = {strprintf("Found non-object value \"non-kv\" in settings file %s", path.string())};
90  BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), non_kv.begin(), non_kv.end());
91 
92  // Check invalid json not allowed
93  WriteText(path, R"(invalid json)");
94  BOOST_CHECK(!util::ReadSettings(path, values, errors));
95  std::vector<std::string> fail_parse = {strprintf("Unable to parse settings file %s", path.string())};
96  BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());
97 }
98 
100 static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val)
101 {
102  util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false);
104  for (const auto& item : GetSettingsList(settings, "section", "name", false)) {
105  list_value.push_back(item);
106  }
107  BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val);
108  BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val);
109 };
110 
111 // Simple settings merge test case.
113 {
114  util::Settings settings;
115  settings.command_line_options["name"].push_back("val1");
116  settings.command_line_options["name"].push_back("val2");
117  settings.ro_config["section"]["name"].push_back(2);
118 
119  // The last given arg takes precedence when specified via commandline.
120  CheckValues(settings, R"("val2")", R"(["val1","val2",2])");
121 
122  util::Settings settings2;
123  settings2.ro_config["section"]["name"].push_back("val2");
124  settings2.ro_config["section"]["name"].push_back("val3");
125 
126  // The first given arg takes precedence when specified via config file.
127  CheckValues(settings2, R"("val2")", R"(["val2","val3"])");
128 }
129 
130 // Confirm that a high priority setting overrides a lower priority setting even
131 // if the high priority setting is null. This behavior is useful for a high
132 // priority setting source to be able to effectively reset any setting back to
133 // its default value.
134 BOOST_AUTO_TEST_CASE(NullOverride)
135 {
136  util::Settings settings;
137  settings.command_line_options["name"].push_back("value");
138  BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false).write().c_str());
139  settings.forced_settings["name"] = {};
140  BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false).write().c_str());
141 }
142 
143 // Test different ways settings can be merged, and verify results. This test can
144 // be used to confirm that updates to settings code don't change behavior
145 // unintentionally.
149  static constexpr int MAX_ACTIONS = 3;
150 
153 
155  template <typename Fn>
156  void ForEachMergeSetup(Fn&& fn)
157  {
158  ActionList arg_actions = {};
159  // command_line_options do not have sections. Only iterate over SET and NEGATE
160  ForEachNoDup(arg_actions, SET, NEGATE, [&]{
161  ActionList conf_actions = {};
162  ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&]{
163  for (bool force_set : {false, true}) {
164  for (bool ignore_default_section_config : {false, true}) {
165  fn(arg_actions, conf_actions, force_set, ignore_default_section_config);
166  }
167  }
168  });
169  });
170  }
171 };
172 
173 // Regression test covering different ways config settings can be merged. The
174 // test parses and merges settings, representing the results as strings that get
175 // compared against an expected hash. To debug, the result strings can be dumped
176 // to a file (see comments below).
178 {
179  CHash256 out_sha;
180  FILE* out_file = nullptr;
181  if (const char* out_path = getenv("SETTINGS_MERGE_TEST_OUT")) {
182  out_file = fsbridge::fopen(out_path, "w");
183  if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
184  }
185 
186  const std::string& network = CBaseChainParams::MAIN;
187  ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool force_set,
188  bool ignore_default_section_config) {
189  std::string desc;
190  int value_suffix = 0;
191  util::Settings settings;
192 
193  const std::string& name = ignore_default_section_config ? "wallet" : "server";
194  auto push_values = [&](Action action, const char* value_prefix, const std::string& name_prefix,
195  std::vector<util::SettingsValue>& dest) {
196  if (action == SET || action == SECTION_SET) {
197  for (int i = 0; i < 2; ++i) {
198  dest.push_back(value_prefix + ToString(++value_suffix));
199  desc += " " + name_prefix + name + "=" + dest.back().get_str();
200  }
201  } else if (action == NEGATE || action == SECTION_NEGATE) {
202  dest.push_back(false);
203  desc += " " + name_prefix + "no" + name;
204  }
205  };
206 
207  if (force_set) {
208  settings.forced_settings[name] = "forced";
209  desc += " " + name + "=forced";
210  }
211  for (Action arg_action : arg_actions) {
212  push_values(arg_action, "a", "-", settings.command_line_options[name]);
213  }
214  for (Action conf_action : conf_actions) {
215  bool use_section = conf_action == SECTION_SET || conf_action == SECTION_NEGATE;
216  push_values(conf_action, "c", use_section ? network + "." : "",
217  settings.ro_config[use_section ? network : ""][name]);
218  }
219 
220  desc += " || ";
221  desc += GetSetting(settings, network, name, ignore_default_section_config, /* get_chain_name= */ false).write();
222  desc += " |";
223  for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) {
224  desc += " ";
225  desc += s.write();
226  }
227  desc += " |";
228  if (OnlyHasDefaultSectionSetting(settings, network, name)) desc += " ignored";
229  desc += "\n";
230 
231  out_sha.Write(MakeUCharSpan(desc));
232  if (out_file) {
233  BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
234  }
235  });
236 
237  if (out_file) {
238  if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
239  out_file = nullptr;
240  }
241 
242  unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
243  out_sha.Finalize(out_sha_bytes);
244  std::string out_sha_hex = HexStr(out_sha_bytes);
245 
246  // If check below fails, should manually dump the results with:
247  //
248  // SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=settings_tests/Merge
249  //
250  // And verify diff against previous results to make sure the changes are expected.
251  //
252  // Results file is formatted like:
253  //
254  // <input> || GetSetting() | GetSettingsList() | OnlyHasDefaultSectionSetting()
255  BOOST_CHECK_EQUAL(out_sha_hex, "79db02d74e3e193196541b67c068b40ebd0c124a24b3ecbe9cbf7e85b1c4ba7a");
256 }
257 
ForEachNoDup
void ForEachNoDup(CharType(&string)[StringLength], CharType min_char, CharType max_char, Fn &&fn)
Iterate over string values and call function for each string without successive duplicate characters.
Definition: str.h:32
MergeTestingSetup::SET
@ SET
Definition: settings_tests.cpp:151
str.h
ToString
std::string ToString(const T &t)
Locale-independent version of std::to_string.
Definition: string.h:87
UniValue::VOBJ
@ VOBJ
Definition: univalue.h:21
util::Settings
Stored settings.
Definition: settings.h:31
CHash256::Write
CHash256 & Write(Span< const unsigned char > input)
Definition: hash.h:37
fsbridge::fopen
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:24
setup_common.h
CheckValues
static void CheckValues(const util::Settings &settings, const std::string &single_val, const std::string &list_val)
Check settings struct contents against expected json strings.
Definition: settings_tests.cpp:100
MakeUCharSpan
constexpr auto MakeUCharSpan(V &&v) -> decltype(UCharSpanCast(MakeSpan(std::forward< V >(v))))
Like MakeSpan, but for (const) unsigned char member types only.
Definition: span.h:249
util::Settings::forced_settings
std::map< std::string, SettingsValue > forced_settings
Map of setting name to forced setting value.
Definition: settings.h:33
string.h
operator==
bool operator==(const util::SettingsValue &a, const util::SettingsValue &b)
Definition: settings_tests.cpp:18
operator<<
std::ostream & operator<<(std::ostream &os, const util::SettingsValue &value)
Definition: settings_tests.cpp:23
Merge
FlatSigningProvider Merge(const FlatSigningProvider &a, const FlatSigningProvider &b)
Definition: signingprovider.cpp:67
util::Settings::ro_config
std::map< std::string, std::map< std::string, std::vector< SettingsValue > > > ro_config
Map of config section name and setting name to list of config file values.
Definition: settings.h:39
UniValue::write
std::string write(unsigned int prettyIndent=0, unsigned int indentLevel=0) const
Definition: univalue_write.cpp:29
BOOST_FIXTURE_TEST_SUITE
#define BOOST_FIXTURE_TEST_SUITE(a, b)
Definition: object.cpp:14
BOOST_AUTO_TEST_SUITE_END
BOOST_AUTO_TEST_SUITE_END()
MergeTestingSetup
Definition: settings_tests.cpp:146
UniValue
Definition: univalue.h:19
fsbridge::ofstream
fs::ofstream ofstream
Definition: fs.h:102
util::ReadSettings
bool ReadSettings(const fs::path &path, std::map< std::string, SettingsValue > &values, std::vector< std::string > &errors)
Read settings file.
Definition: settings.cpp:58
strencodings.h
settings.h
MergeTestingSetup::SECTION_SET
@ SECTION_SET
Definition: settings_tests.cpp:151
values
static const int64_t values[]
A selection of numbers that do not trigger int64_t overflow when added/subtracted.
Definition: scriptnum_tests.cpp:17
MergeTestingSetup::ForEachMergeSetup
void ForEachMergeSetup(Fn &&fn)
Enumerate all possible test configurations.
Definition: settings_tests.cpp:156
univalue.h
MergeTestingSetup::NEGATE
@ NEGATE
Definition: settings_tests.cpp:151
MergeTestingSetup::END
@ END
Definition: settings_tests.cpp:151
UniValue::__pushKV
void __pushKV(const std::string &key, const UniValue &val)
Definition: univalue.cpp:127
CHash256::Finalize
void Finalize(Span< unsigned char > output)
Definition: hash.h:30
BasicTestingSetup
Basic testing setup.
Definition: setup_common.h:76
MergeTestingSetup::MAX_ACTIONS
static constexpr int MAX_ACTIONS
Max number of actions to sequence together.
Definition: settings_tests.cpp:149
MergeTestingSetup::ActionList
Action[MAX_ACTIONS] ActionList
Definition: settings_tests.cpp:152
CBaseChainParams::MAIN
static const std::string MAIN
Chain name strings.
Definition: chainparamsbase.h:22
BOOST_FIXTURE_TEST_CASE
BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup)
Definition: settings_tests.cpp:177
name
const char * name
Definition: rest.cpp:43
CSHA256::OUTPUT_SIZE
static const size_t OUTPUT_SIZE
Definition: sha256.h:21
system.h
strprintf
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1164
util::OnlyHasDefaultSectionSetting
bool OnlyHasDefaultSectionSetting(const Settings &settings, const std::string &section, const std::string &name)
Return true if a setting is set in the default config file section, and not overridden by a higher pr...
Definition: settings.cpp:218
util::GetSetting
SettingsValue GetSetting(const Settings &settings, const std::string &section, const std::string &name, bool ignore_default_section_config, bool get_chain_name)
Get settings value from combined sources: forced settings, command line arguments,...
Definition: settings.cpp:120
UniValue::push_back
bool push_back(const UniValue &val)
Definition: univalue.cpp:108
CHash256
A hasher class for Bitcoin's 256-bit hash (double SHA-256).
Definition: hash.h:24
MergeTestingSetup::Action
Action
Definition: settings_tests.cpp:151
util::Settings::command_line_options
std::map< std::string, std::vector< SettingsValue > > command_line_options
Map of setting name to list of command line values.
Definition: settings.h:35
util::GetSettingsList
std::vector< SettingsValue > GetSettingsList(const Settings &settings, const std::string &section, const std::string &name, bool ignore_default_section_config)
Get combined setting value similar to GetSetting(), except if setting was specified multiple times,...
Definition: settings.cpp:173
UniValue::VARR
@ VARR
Definition: univalue.h:21
HexStr
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
Definition: strencodings.cpp:594
WriteText
void WriteText(const fs::path &path, const std::string &text)
Definition: settings_tests.cpp:37
MergeTestingSetup::SECTION_NEGATE
@ SECTION_NEGATE
Definition: settings_tests.cpp:151
BOOST_CHECK
#define BOOST_CHECK(expr)
Definition: object.cpp:17
BOOST_CHECK_EQUAL
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
BOOST_AUTO_TEST_CASE
BOOST_AUTO_TEST_CASE(ReadWrite)
Definition: settings_tests.cpp:46