LCOV - code coverage report
Current view: top level - src/test - settings_tests.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 0 141 0.0 %
Date: 2023-10-05 12:38:51 Functions: 0 35 0.0 %
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : // Copyright (c) 2011-2021 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 <common/settings.h>
       6                 :            : 
       7                 :            : #include <test/util/setup_common.h>
       8                 :            : #include <test/util/str.h>
       9                 :            : 
      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>
      17                 :            : 
      18                 :            : #include <fstream>
      19                 :            : #include <map>
      20                 :            : #include <string>
      21                 :            : #include <system_error>
      22                 :            : #include <vector>
      23                 :            : 
      24                 :          0 : inline bool operator==(const common::SettingsValue& a, const common::SettingsValue& b)
      25                 :            : {
      26                 :          0 :     return a.write() == b.write();
      27                 :          0 : }
      28                 :            : 
      29                 :            : inline std::ostream& operator<<(std::ostream& os, const common::SettingsValue& value)
      30                 :            : {
      31                 :            :     os << value.write();
      32                 :            :     return os;
      33                 :            : }
      34                 :            : 
      35                 :          0 : inline std::ostream& operator<<(std::ostream& os, const std::pair<std::string, common::SettingsValue>& kv)
      36                 :            : {
      37                 :          0 :     common::SettingsValue out(common::SettingsValue::VOBJ);
      38                 :          0 :     out.pushKVEnd(kv.first, kv.second);
      39                 :          0 :     os << out.write();
      40                 :          0 :     return os;
      41                 :          0 : }
      42                 :            : 
      43                 :          0 : inline void WriteText(const fs::path& path, const std::string& text)
      44                 :            : {
      45                 :          0 :     std::ofstream file;
      46                 :          0 :     file.open(path);
      47                 :          0 :     file << text;
      48                 :          0 : }
      49                 :            : 
      50                 :          0 : BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup)
      51                 :            : 
      52                 :          0 : BOOST_AUTO_TEST_CASE(ReadWrite)
      53                 :            : {
      54                 :          0 :     fs::path path = m_args.GetDataDirBase() / "settings.json";
      55                 :            : 
      56                 :          0 :     WriteText(path, R"({
      57                 :            :         "string": "string",
      58                 :            :         "num": 5,
      59                 :            :         "bool": true,
      60                 :            :         "null": null
      61                 :            :     })");
      62                 :            : 
      63                 :          0 :     std::map<std::string, common::SettingsValue> expected{
      64                 :          0 :         {"string", "string"},
      65                 :          0 :         {"num", 5},
      66                 :          0 :         {"bool", true},
      67                 :          0 :         {"null", {}},
      68                 :            :     };
      69                 :            : 
      70                 :            :     // Check file read.
      71                 :          0 :     std::map<std::string, common::SettingsValue> values;
      72                 :          0 :     std::vector<std::string> errors;
      73                 :          0 :     BOOST_CHECK(common::ReadSettings(path, values, errors));
      74                 :          0 :     BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end());
      75                 :          0 :     BOOST_CHECK(errors.empty());
      76                 :            : 
      77                 :            :     // Check no errors if file doesn't exist.
      78                 :          0 :     fs::remove(path);
      79                 :          0 :     BOOST_CHECK(common::ReadSettings(path, values, errors));
      80                 :          0 :     BOOST_CHECK(values.empty());
      81                 :          0 :     BOOST_CHECK(errors.empty());
      82                 :            : 
      83                 :            :     // Check duplicate keys not allowed and that values returns empty if a duplicate is found.
      84                 :          0 :     WriteText(path, R"({
      85                 :            :         "dupe": "string",
      86                 :            :         "dupe": "dupe"
      87                 :            :     })");
      88                 :          0 :     BOOST_CHECK(!common::ReadSettings(path, values, errors));
      89                 :          0 :     std::vector<std::string> dup_keys = {strprintf("Found duplicate key dupe in settings file %s", fs::PathToString(path))};
      90                 :          0 :     BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), dup_keys.begin(), dup_keys.end());
      91                 :          0 :     BOOST_CHECK(values.empty());
      92                 :            : 
      93                 :            :     // Check non-kv json files not allowed
      94                 :          0 :     WriteText(path, R"("non-kv")");
      95                 :          0 :     BOOST_CHECK(!common::ReadSettings(path, values, errors));
      96                 :          0 :     std::vector<std::string> non_kv = {strprintf("Found non-object value \"non-kv\" in settings file %s", fs::PathToString(path))};
      97                 :          0 :     BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), non_kv.begin(), non_kv.end());
      98                 :            : 
      99                 :            :     // Check invalid json not allowed
     100                 :          0 :     WriteText(path, R"(invalid json)");
     101                 :          0 :     BOOST_CHECK(!common::ReadSettings(path, values, errors));
     102                 :          0 :     std::vector<std::string> fail_parse = {strprintf("Unable to parse settings file %s", fs::PathToString(path))};
     103                 :          0 :     BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end());
     104                 :          0 : }
     105                 :            : 
     106                 :            : //! Check settings struct contents against expected json strings.
     107                 :          0 : static void CheckValues(const common::Settings& settings, const std::string& single_val, const std::string& list_val)
     108                 :            : {
     109                 :          0 :     common::SettingsValue single_value = GetSetting(settings, "section", "name", false, false, false);
     110                 :          0 :     common::SettingsValue list_value(common::SettingsValue::VARR);
     111                 :          0 :     for (const auto& item : GetSettingsList(settings, "section", "name", false)) {
     112                 :          0 :         list_value.push_back(item);
     113                 :            :     }
     114                 :          0 :     BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val);
     115                 :          0 :     BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val);
     116                 :          0 : };
     117                 :            : 
     118                 :            : // Simple settings merge test case.
     119                 :          0 : BOOST_AUTO_TEST_CASE(Simple)
     120                 :            : {
     121                 :          0 :     common::Settings settings;
     122                 :          0 :     settings.command_line_options["name"].push_back("val1");
     123                 :          0 :     settings.command_line_options["name"].push_back("val2");
     124                 :          0 :     settings.ro_config["section"]["name"].push_back(2);
     125                 :            : 
     126                 :            :     // The last given arg takes precedence when specified via commandline.
     127                 :          0 :     CheckValues(settings, R"("val2")", R"(["val1","val2",2])");
     128                 :            : 
     129                 :          0 :     common::Settings settings2;
     130                 :          0 :     settings2.ro_config["section"]["name"].push_back("val2");
     131                 :          0 :     settings2.ro_config["section"]["name"].push_back("val3");
     132                 :            : 
     133                 :            :     // The first given arg takes precedence when specified via config file.
     134                 :          0 :     CheckValues(settings2, R"("val2")", R"(["val2","val3"])");
     135                 :          0 : }
     136                 :            : 
     137                 :            : // Confirm that a high priority setting overrides a lower priority setting even
     138                 :            : // if the high priority setting is null. This behavior is useful for a high
     139                 :            : // priority setting source to be able to effectively reset any setting back to
     140                 :            : // its default value.
     141                 :          0 : BOOST_AUTO_TEST_CASE(NullOverride)
     142                 :            : {
     143                 :          0 :     common::Settings settings;
     144                 :          0 :     settings.command_line_options["name"].push_back("value");
     145                 :          0 :     BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false, false).write().c_str());
     146                 :          0 :     settings.forced_settings["name"] = {};
     147                 :          0 :     BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false, false).write().c_str());
     148                 :          0 : }
     149                 :            : 
     150                 :            : // Test different ways settings can be merged, and verify results. This test can
     151                 :            : // be used to confirm that updates to settings code don't change behavior
     152                 :            : // unintentionally.
     153                 :          0 : struct MergeTestingSetup : public BasicTestingSetup {
     154                 :            :     //! Max number of actions to sequence together. Can decrease this when
     155                 :            :     //! debugging to make test results easier to understand.
     156                 :            :     static constexpr int MAX_ACTIONS = 3;
     157                 :            : 
     158                 :            :     enum Action { END, SET, NEGATE, SECTION_SET, SECTION_NEGATE };
     159                 :            :     using ActionList = Action[MAX_ACTIONS];
     160                 :            : 
     161                 :            :     //! Enumerate all possible test configurations.
     162                 :            :     template <typename Fn>
     163                 :          0 :     void ForEachMergeSetup(Fn&& fn)
     164                 :            :     {
     165                 :          0 :         ActionList arg_actions = {};
     166                 :            :         // command_line_options do not have sections. Only iterate over SET and NEGATE
     167                 :          0 :         ForEachNoDup(arg_actions, SET, NEGATE, [&]{
     168                 :          0 :             ActionList conf_actions = {};
     169                 :          0 :             ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&]{
     170                 :          0 :                 for (bool force_set : {false, true}) {
     171                 :          0 :                     for (bool ignore_default_section_config : {false, true}) {
     172                 :          0 :                         fn(arg_actions, conf_actions, force_set, ignore_default_section_config);
     173                 :            :                     }
     174                 :            :                 }
     175                 :          0 :             });
     176                 :          0 :         });
     177                 :          0 :     }
     178                 :            : };
     179                 :            : 
     180                 :            : // Regression test covering different ways config settings can be merged. The
     181                 :            : // test parses and merges settings, representing the results as strings that get
     182                 :            : // compared against an expected hash. To debug, the result strings can be dumped
     183                 :            : // to a file (see comments below).
     184                 :          0 : BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup)
     185                 :            : {
     186                 :          0 :     CHash256 out_sha;
     187                 :          0 :     FILE* out_file = nullptr;
     188                 :          0 :     if (const char* out_path = getenv("SETTINGS_MERGE_TEST_OUT")) {
     189                 :          0 :         out_file = fsbridge::fopen(out_path, "w");
     190                 :          0 :         if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
     191                 :          0 :     }
     192                 :            : 
     193                 :          0 :     const std::string& network = ChainTypeToString(ChainType::MAIN);
     194                 :          0 :     ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool force_set,
     195                 :            :                           bool ignore_default_section_config) {
     196                 :          0 :         std::string desc;
     197                 :          0 :         int value_suffix = 0;
     198                 :          0 :         common::Settings settings;
     199                 :            : 
     200                 :          0 :         const std::string& name = ignore_default_section_config ? "wallet" : "server";
     201                 :          0 :         auto push_values = [&](Action action, const char* value_prefix, const std::string& name_prefix,
     202                 :            :                                std::vector<common::SettingsValue>& dest) {
     203                 :          0 :             if (action == SET || action == SECTION_SET) {
     204                 :          0 :                 for (int i = 0; i < 2; ++i) {
     205                 :          0 :                     dest.push_back(value_prefix + ToString(++value_suffix));
     206                 :          0 :                     desc += " " + name_prefix + name + "=" + dest.back().get_str();
     207                 :          0 :                 }
     208                 :          0 :             } else if (action == NEGATE || action == SECTION_NEGATE) {
     209                 :          0 :                 dest.push_back(false);
     210                 :          0 :                 desc += " " + name_prefix + "no" + name;
     211                 :          0 :             }
     212                 :          0 :         };
     213                 :            : 
     214                 :          0 :         if (force_set) {
     215                 :          0 :             settings.forced_settings[name] = "forced";
     216                 :          0 :             desc += " " + name + "=forced";
     217                 :          0 :         }
     218                 :          0 :         for (Action arg_action : arg_actions) {
     219                 :          0 :             push_values(arg_action, "a", "-", settings.command_line_options[name]);
     220                 :            :         }
     221                 :          0 :         for (Action conf_action : conf_actions) {
     222                 :          0 :             bool use_section = conf_action == SECTION_SET || conf_action == SECTION_NEGATE;
     223                 :          0 :             push_values(conf_action, "c", use_section ? network + "." : "",
     224                 :          0 :                 settings.ro_config[use_section ? network : ""][name]);
     225                 :            :         }
     226                 :            : 
     227                 :          0 :         desc += " || ";
     228                 :          0 :         desc += GetSetting(settings, network, name, ignore_default_section_config, /*ignore_nonpersistent=*/false, /*get_chain_type=*/false).write();
     229                 :          0 :         desc += " |";
     230                 :          0 :         for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) {
     231                 :          0 :             desc += " ";
     232                 :          0 :             desc += s.write();
     233                 :            :         }
     234                 :          0 :         desc += " |";
     235                 :          0 :         if (OnlyHasDefaultSectionSetting(settings, network, name)) desc += " ignored";
     236                 :          0 :         desc += "\n";
     237                 :            : 
     238                 :          0 :         out_sha.Write(MakeUCharSpan(desc));
     239                 :          0 :         if (out_file) {
     240                 :          0 :             BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
     241                 :          0 :         }
     242                 :          0 :     });
     243                 :            : 
     244                 :          0 :     if (out_file) {
     245                 :          0 :         if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
     246                 :          0 :         out_file = nullptr;
     247                 :          0 :     }
     248                 :            : 
     249                 :            :     unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
     250                 :          0 :     out_sha.Finalize(out_sha_bytes);
     251                 :          0 :     std::string out_sha_hex = HexStr(out_sha_bytes);
     252                 :            : 
     253                 :            :     // If check below fails, should manually dump the results with:
     254                 :            :     //
     255                 :            :     //   SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=settings_tests/Merge
     256                 :            :     //
     257                 :            :     // And verify diff against previous results to make sure the changes are expected.
     258                 :            :     //
     259                 :            :     // Results file is formatted like:
     260                 :            :     //
     261                 :            :     //   <input> || GetSetting() | GetSettingsList() | OnlyHasDefaultSectionSetting()
     262                 :          0 :     BOOST_CHECK_EQUAL(out_sha_hex, "79db02d74e3e193196541b67c068b40ebd0c124a24b3ecbe9cbf7e85b1c4ba7a");
     263                 :          0 : }
     264                 :            : 
     265                 :          0 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14