LCOV - code coverage report
Current view: top level - src/test - settings_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 91.7 % 132 121
Test Date: 2024-11-04 05:10:19 Functions: 94.1 % 17 16
Branches: 50.1 % 670 336

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

Generated by: LCOV version 2.0-1