Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2011-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <common/settings.h>7#include <test/util/setup_common.h>8#include <test/util/str.h>10#include <boost/test/unit_test.hpp>11#include <common/args.h>12#include <univalue.h>13#include <util/chaintype.h>14#include <util/fs.h>15#include <util/strencodings.h>16#include <util/string.h>18#include <fstream>19#include <map>20#include <string>21#include <system_error>22#include <vector>24using util::ToString;26inline bool operator==(const common::SettingsValue& a, const common::SettingsValue& b)27{28return a.write() == b.write();29}31inline std::ostream& operator<<(std::ostream& os, const common::SettingsValue& value)32{33os << value.write();34return os;35}37inline std::ostream& operator<<(std::ostream& os, const std::pair<std::string, common::SettingsValue>& kv)38{39common::SettingsValue out(common::SettingsValue::VOBJ);40out.pushKVEnd(kv.first, kv.second);41os << out.write();42return os;43}45inline void WriteText(const fs::path& path, const std::string& text)46{47std::ofstream file;48file.open(path.std_path());49file << text;50}52BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup)54BOOST_AUTO_TEST_CASE(ReadWrite)55{56fs::path path = m_args.GetDataDirBase() / "settings.json";58WriteText(path, R"({59"string": "string",60"num": 5,61"bool": true,62"null": null63})");65std::map<std::string, common::SettingsValue> expected{66{"string", "string"},67{"num", 5},68{"bool", true},69{"null", {}},70};72// Check file read.73std::map<std::string, common::SettingsValue> values;74std::vector<std::string> errors;75BOOST_CHECK(common::ReadSettings(path, values, errors));76BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end());77BOOST_CHECK(errors.empty());79// Check no errors if file doesn't exist.80fs::remove(path);81BOOST_CHECK(common::ReadSettings(path, values, errors));82BOOST_CHECK(values.empty());83BOOST_CHECK(errors.empty());85// Check duplicate keys not allowed and that values returns empty if a duplicate is found.86WriteText(path, R"({87"dupe": "string",88"dupe": "dupe"89})");90BOOST_CHECK(!common::ReadSettings(path, values, errors));91std::vector<std::string> dup_keys = {strprintf("Found duplicate key dupe in settings file %s", fs::PathToString(path))};92BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), dup_keys.begin(), dup_keys.end());93BOOST_CHECK(values.empty());95// Check non-kv json files not allowed96WriteText(path, R"("non-kv")");97BOOST_CHECK(!common::ReadSettings(path, values, errors));98std::vector<std::string> non_kv = {strprintf("Found non-object value \"non-kv\" in settings file %s", fs::PathToString(path))};99BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), non_kv.begin(), non_kv.end());101// Check invalid json not allowed102WriteText(path, R"(invalid json)");103BOOST_CHECK(!common::ReadSettings(path, values, errors));104std::vector<std::string> fail_parse = {strprintf("Settings file %s does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error, "105"and can be fixed by removing the file, which will reset settings to default values.",106fs::PathToString(path))};107BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());108}110//! Check settings struct contents against expected json strings.111static void CheckValues(const common::Settings& settings, const std::string& single_val, const std::string& list_val)112{113common::SettingsValue single_value = GetSetting(settings, "section", "name", false, false, false);114common::SettingsValue list_value(common::SettingsValue::VARR);115for (const auto& item : GetSettingsList(settings, "section", "name", false)) {116list_value.push_back(item);117}118BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val);119BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val);120};122// Simple settings merge test case.123BOOST_AUTO_TEST_CASE(Simple)124{125common::Settings settings;126settings.command_line_options["name"].emplace_back("val1");127settings.command_line_options["name"].emplace_back("val2");128settings.ro_config["section"]["name"].emplace_back(2);130// The last given arg takes precedence when specified via commandline.131CheckValues(settings, R"("val2")", R"(["val1","val2",2])");133common::Settings settings2;134settings2.ro_config["section"]["name"].emplace_back("val2");135settings2.ro_config["section"]["name"].emplace_back("val3");137// The first given arg takes precedence when specified via config file.138CheckValues(settings2, R"("val2")", R"(["val2","val3"])");139}141// Confirm that a high priority setting overrides a lower priority setting even142// if the high priority setting is null. This behavior is useful for a high143// priority setting source to be able to effectively reset any setting back to144// its default value.145BOOST_AUTO_TEST_CASE(NullOverride)146{147common::Settings settings;148settings.command_line_options["name"].emplace_back("value");149BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false, false).write().c_str());150settings.forced_settings["name"] = {};151BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false, false).write().c_str());152}154// Test different ways settings can be merged, and verify results. This test can155// be used to confirm that updates to settings code don't change behavior156// unintentionally.157struct MergeTestingSetup : public BasicTestingSetup {158//! Max number of actions to sequence together. Can decrease this when159//! debugging to make test results easier to understand.160static constexpr int MAX_ACTIONS = 3;162enum Action { END, SET, NEGATE, SECTION_SET, SECTION_NEGATE };163using ActionList = Action[MAX_ACTIONS];165//! Enumerate all possible test configurations.166template <typename Fn>167void ForEachMergeSetup(Fn&& fn)168{169ActionList arg_actions = {};170// command_line_options do not have sections. Only iterate over SET and NEGATE171ForEachNoDup(arg_actions, SET, NEGATE, [&]{172ActionList conf_actions = {};173ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&]{174for (bool force_set : {false, true}) {175for (bool ignore_default_section_config : {false, true}) {176fn(arg_actions, conf_actions, force_set, ignore_default_section_config);177}178}179});180});181}182};184// Regression test covering different ways config settings can be merged. The185// test parses and merges settings, representing the results as strings that get186// compared against an expected hash. To debug, the result strings can be dumped187// to a file (see comments below).188BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup)189{190CHash256 out_sha;191FILE* out_file = nullptr;192if (const char* out_path = getenv("SETTINGS_MERGE_TEST_OUT")) {193out_file = fsbridge::fopen(out_path, "w");194if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");195}197const std::string& network = ChainTypeToString(ChainType::MAIN);198ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool force_set,199bool ignore_default_section_config) {200std::string desc;201int value_suffix = 0;202common::Settings settings;204const std::string& name = ignore_default_section_config ? "wallet" : "server";205auto push_values = [&](Action action, const char* value_prefix, const std::string& name_prefix,206std::vector<common::SettingsValue>& dest) {207if (action == SET || action == SECTION_SET) {208for (int i = 0; i < 2; ++i) {209dest.emplace_back(value_prefix + ToString(++value_suffix));210desc += " " + name_prefix + name + "=" + dest.back().get_str();211}212} else if (action == NEGATE || action == SECTION_NEGATE) {213dest.emplace_back(false);214desc += " " + name_prefix + "no" + name;215}216};218if (force_set) {219settings.forced_settings[name] = "forced";220desc += " " + name + "=forced";221}222for (Action arg_action : arg_actions) {223push_values(arg_action, "a", "-", settings.command_line_options[name]);224}225for (Action conf_action : conf_actions) {226bool use_section = conf_action == SECTION_SET || conf_action == SECTION_NEGATE;227push_values(conf_action, "c", use_section ? network + "." : "",228settings.ro_config[use_section ? network : ""][name]);229}231desc += " || ";232desc += GetSetting(settings, network, name, ignore_default_section_config, /*ignore_nonpersistent=*/false, /*get_chain_type=*/false).write();233desc += " |";234for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) {235desc += " ";236desc += s.write();237}238desc += " |";239if (OnlyHasDefaultSectionSetting(settings, network, name)) desc += " ignored";240desc += "\n";242out_sha.Write(MakeUCharSpan(desc));243if (out_file) {244BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());245}246});248if (out_file) {249if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");250out_file = nullptr;
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.