/ src / test / argsman_tests.cpp
argsman_tests.cpp
   1  // Copyright (c) 2011-2022 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/args.h>
   6  #include <sync.h>
   7  #include <test/util/logging.h>
   8  #include <test/util/setup_common.h>
   9  #include <test/util/str.h>
  10  #include <univalue.h>
  11  #include <util/chaintype.h>
  12  #include <util/fs.h>
  13  #include <util/strencodings.h>
  14  
  15  #include <array>
  16  #include <optional>
  17  #include <cstdint>
  18  #include <cstring>
  19  #include <vector>
  20  
  21  #include <boost/test/unit_test.hpp>
  22  
  23  BOOST_FIXTURE_TEST_SUITE(argsman_tests, BasicTestingSetup)
  24  
  25  BOOST_AUTO_TEST_CASE(util_datadir)
  26  {
  27      // Use local args variable instead of m_args to avoid making assumptions about test setup
  28      ArgsManager args;
  29      args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
  30  
  31      const fs::path dd_norm = args.GetDataDirBase();
  32  
  33      args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/");
  34      args.ClearPathCache();
  35      BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
  36  
  37      args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.");
  38      args.ClearPathCache();
  39      BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
  40  
  41      args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/./");
  42      args.ClearPathCache();
  43      BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
  44  
  45      args.ForceSetArg("-datadir", fs::PathToString(dd_norm) + "/.//");
  46      args.ClearPathCache();
  47      BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase());
  48  }
  49  
  50  struct TestArgsManager : public ArgsManager
  51  {
  52      TestArgsManager() { m_network_only_args.clear(); }
  53      void ReadConfigString(const std::string str_config)
  54      {
  55          std::istringstream streamConfig(str_config);
  56          {
  57              LOCK(cs_args);
  58              m_settings.ro_config.clear();
  59              m_config_sections.clear();
  60          }
  61          std::string error;
  62          BOOST_REQUIRE(ReadConfigStream(streamConfig, "", error));
  63      }
  64      void SetNetworkOnlyArg(const std::string arg)
  65      {
  66          LOCK(cs_args);
  67          m_network_only_args.insert(arg);
  68      }
  69      void SetupArgs(const std::vector<std::pair<std::string, unsigned int>>& args)
  70      {
  71          for (const auto& arg : args) {
  72              AddArg(arg.first, "", arg.second, OptionsCategory::OPTIONS);
  73          }
  74      }
  75      using ArgsManager::GetSetting;
  76      using ArgsManager::GetSettingsList;
  77      using ArgsManager::ReadConfigStream;
  78      using ArgsManager::cs_args;
  79      using ArgsManager::m_network;
  80      using ArgsManager::m_settings;
  81  };
  82  
  83  //! Test GetSetting and GetArg type coercion, negation, and default value handling.
  84  class CheckValueTest : public TestChain100Setup
  85  {
  86  public:
  87      struct Expect {
  88          common::SettingsValue setting;
  89          bool default_string = false;
  90          bool default_int = false;
  91          bool default_bool = false;
  92          const char* string_value = nullptr;
  93          std::optional<int64_t> int_value;
  94          std::optional<bool> bool_value;
  95          std::optional<std::vector<std::string>> list_value;
  96          const char* error = nullptr;
  97  
  98          explicit Expect(common::SettingsValue s) : setting(std::move(s)) {}
  99          Expect& DefaultString() { default_string = true; return *this; }
 100          Expect& DefaultInt() { default_int = true; return *this; }
 101          Expect& DefaultBool() { default_bool = true; return *this; }
 102          Expect& String(const char* s) { string_value = s; return *this; }
 103          Expect& Int(int64_t i) { int_value = i; return *this; }
 104          Expect& Bool(bool b) { bool_value = b; return *this; }
 105          Expect& List(std::vector<std::string> m) { list_value = std::move(m); return *this; }
 106          Expect& Error(const char* e) { error = e; return *this; }
 107      };
 108  
 109      void CheckValue(unsigned int flags, const char* arg, const Expect& expect)
 110      {
 111          TestArgsManager test;
 112          test.SetupArgs({{"-value", flags}});
 113          const char* argv[] = {"ignored", arg};
 114          std::string error;
 115          bool success = test.ParseParameters(arg ? 2 : 1, argv, error);
 116  
 117          BOOST_CHECK_EQUAL(test.GetSetting("-value").write(), expect.setting.write());
 118          auto settings_list = test.GetSettingsList("-value");
 119          if (expect.setting.isNull() || expect.setting.isFalse()) {
 120              BOOST_CHECK_EQUAL(settings_list.size(), 0U);
 121          } else {
 122              BOOST_CHECK_EQUAL(settings_list.size(), 1U);
 123              BOOST_CHECK_EQUAL(settings_list[0].write(), expect.setting.write());
 124          }
 125  
 126          if (expect.error) {
 127              BOOST_CHECK(!success);
 128              BOOST_CHECK_NE(error.find(expect.error), std::string::npos);
 129          } else {
 130              BOOST_CHECK(success);
 131              BOOST_CHECK_EQUAL(error, "");
 132          }
 133  
 134          if (expect.default_string) {
 135              BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), "zzzzz");
 136          } else if (expect.string_value) {
 137              BOOST_CHECK_EQUAL(test.GetArg("-value", "zzzzz"), expect.string_value);
 138          } else {
 139              BOOST_CHECK(!success);
 140          }
 141  
 142          if (expect.default_int) {
 143              BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), 99999);
 144          } else if (expect.int_value) {
 145              BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), *expect.int_value);
 146          } else {
 147              BOOST_CHECK(!success);
 148          }
 149  
 150          if (expect.default_bool) {
 151              BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), false);
 152              BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), true);
 153          } else if (expect.bool_value) {
 154              BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), *expect.bool_value);
 155              BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), *expect.bool_value);
 156          } else {
 157              BOOST_CHECK(!success);
 158          }
 159  
 160          if (expect.list_value) {
 161              auto l = test.GetArgs("-value");
 162              BOOST_CHECK_EQUAL_COLLECTIONS(l.begin(), l.end(), expect.list_value->begin(), expect.list_value->end());
 163          } else {
 164              BOOST_CHECK(!success);
 165          }
 166      }
 167  };
 168  
 169  BOOST_FIXTURE_TEST_CASE(util_CheckValue, CheckValueTest)
 170  {
 171      using M = ArgsManager;
 172  
 173      CheckValue(M::ALLOW_ANY, nullptr, Expect{{}}.DefaultString().DefaultInt().DefaultBool().List({}));
 174      CheckValue(M::ALLOW_ANY, "-novalue", Expect{false}.String("0").Int(0).Bool(false).List({}));
 175      CheckValue(M::ALLOW_ANY, "-novalue=", Expect{false}.String("0").Int(0).Bool(false).List({}));
 176      CheckValue(M::ALLOW_ANY, "-novalue=0", Expect{true}.String("1").Int(1).Bool(true).List({"1"}));
 177      CheckValue(M::ALLOW_ANY, "-novalue=1", Expect{false}.String("0").Int(0).Bool(false).List({}));
 178      CheckValue(M::ALLOW_ANY, "-novalue=2", Expect{false}.String("0").Int(0).Bool(false).List({}));
 179      CheckValue(M::ALLOW_ANY, "-novalue=abc", Expect{true}.String("1").Int(1).Bool(true).List({"1"}));
 180      CheckValue(M::ALLOW_ANY, "-value", Expect{""}.String("").Int(0).Bool(true).List({""}));
 181      CheckValue(M::ALLOW_ANY, "-value=", Expect{""}.String("").Int(0).Bool(true).List({""}));
 182      CheckValue(M::ALLOW_ANY, "-value=0", Expect{"0"}.String("0").Int(0).Bool(false).List({"0"}));
 183      CheckValue(M::ALLOW_ANY, "-value=1", Expect{"1"}.String("1").Int(1).Bool(true).List({"1"}));
 184      CheckValue(M::ALLOW_ANY, "-value=2", Expect{"2"}.String("2").Int(2).Bool(true).List({"2"}));
 185      CheckValue(M::ALLOW_ANY, "-value=abc", Expect{"abc"}.String("abc").Int(0).Bool(false).List({"abc"}));
 186  }
 187  
 188  struct NoIncludeConfTest {
 189      std::string Parse(const char* arg)
 190      {
 191          TestArgsManager test;
 192          test.SetupArgs({{"-includeconf", ArgsManager::ALLOW_ANY}});
 193          std::array argv{"ignored", arg};
 194          std::string error;
 195          (void)test.ParseParameters(argv.size(), argv.data(), error);
 196          return error;
 197      }
 198  };
 199  
 200  BOOST_FIXTURE_TEST_CASE(util_NoIncludeConf, NoIncludeConfTest)
 201  {
 202      BOOST_CHECK_EQUAL(Parse("-noincludeconf"), "");
 203      BOOST_CHECK_EQUAL(Parse("-includeconf"), "-includeconf cannot be used from commandline; -includeconf=\"\"");
 204      BOOST_CHECK_EQUAL(Parse("-includeconf=file"), "-includeconf cannot be used from commandline; -includeconf=\"file\"");
 205  }
 206  
 207  BOOST_AUTO_TEST_CASE(util_ParseParameters)
 208  {
 209      TestArgsManager testArgs;
 210      const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
 211      const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
 212      const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY);
 213      const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
 214  
 215      const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"};
 216  
 217      std::string error;
 218      LOCK(testArgs.cs_args);
 219      testArgs.SetupArgs({a, b, ccc, d});
 220      BOOST_CHECK(testArgs.ParseParameters(0, argv_test, error));
 221      BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty());
 222  
 223      BOOST_CHECK(testArgs.ParseParameters(1, argv_test, error));
 224      BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty());
 225  
 226      BOOST_CHECK(testArgs.ParseParameters(7, argv_test, error));
 227      // expectation: -ignored is ignored (program name argument),
 228      // -a, -b and -ccc end up in map, -d ignored because it is after
 229      // a non-option argument (non-GNU option parsing)
 230      BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty());
 231      BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc")
 232                  && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d"));
 233      BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc")
 234                  && !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d"));
 235  
 236      BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1);
 237      BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == "");
 238      BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2);
 239      BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].front().get_str() == "argument");
 240      BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].back().get_str() == "multiple");
 241      BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2);
 242  }
 243  
 244  BOOST_AUTO_TEST_CASE(util_ParseInvalidParameters)
 245  {
 246      TestArgsManager test;
 247      test.SetupArgs({{"-registered", ArgsManager::ALLOW_ANY}});
 248  
 249      const char* argv[] = {"ignored", "-registered"};
 250      std::string error;
 251      BOOST_CHECK(test.ParseParameters(2, argv, error));
 252      BOOST_CHECK_EQUAL(error, "");
 253  
 254      argv[1] = "-unregistered";
 255      BOOST_CHECK(!test.ParseParameters(2, argv, error));
 256      BOOST_CHECK_EQUAL(error, "Invalid parameter -unregistered");
 257  
 258      // Make sure registered parameters prefixed with a chain type trigger errors.
 259      // (Previously, they were accepted and ignored.)
 260      argv[1] = "-test.registered";
 261      BOOST_CHECK(!test.ParseParameters(2, argv, error));
 262      BOOST_CHECK_EQUAL(error, "Invalid parameter -test.registered");
 263  }
 264  
 265  static void TestParse(const std::string& str, bool expected_bool, int64_t expected_int)
 266  {
 267      TestArgsManager test;
 268      test.SetupArgs({{"-value", ArgsManager::ALLOW_ANY}});
 269      std::string arg = "-value=" + str;
 270      const char* argv[] = {"ignored", arg.c_str()};
 271      std::string error;
 272      BOOST_CHECK(test.ParseParameters(2, argv, error));
 273      BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), expected_bool);
 274      BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), expected_bool);
 275      BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99998), expected_int);
 276      BOOST_CHECK_EQUAL(test.GetIntArg("-value", 99999), expected_int);
 277  }
 278  
 279  // Test bool and int parsing.
 280  BOOST_AUTO_TEST_CASE(util_ArgParsing)
 281  {
 282      // Some of these cases could be ambiguous or surprising to users, and might
 283      // be worth triggering errors or warnings in the future. But for now basic
 284      // test coverage is useful to avoid breaking backwards compatibility
 285      // unintentionally.
 286      TestParse("", true, 0);
 287      TestParse(" ", false, 0);
 288      TestParse("0", false, 0);
 289      TestParse("0 ", false, 0);
 290      TestParse(" 0", false, 0);
 291      TestParse("+0", false, 0);
 292      TestParse("-0", false, 0);
 293      TestParse("5", true, 5);
 294      TestParse("5 ", true, 5);
 295      TestParse(" 5", true, 5);
 296      TestParse("+5", true, 5);
 297      TestParse("-5", true, -5);
 298      TestParse("0 5", false, 0);
 299      TestParse("5 0", true, 5);
 300      TestParse("050", true, 50);
 301      TestParse("0.", false, 0);
 302      TestParse("5.", true, 5);
 303      TestParse("0.0", false, 0);
 304      TestParse("0.5", false, 0);
 305      TestParse("5.0", true, 5);
 306      TestParse("5.5", true, 5);
 307      TestParse("x", false, 0);
 308      TestParse("x0", false, 0);
 309      TestParse("x5", false, 0);
 310      TestParse("0x", false, 0);
 311      TestParse("5x", true, 5);
 312      TestParse("0x5", false, 0);
 313      TestParse("false", false, 0);
 314      TestParse("true", false, 0);
 315      TestParse("yes", false, 0);
 316      TestParse("no", false, 0);
 317  }
 318  
 319  BOOST_AUTO_TEST_CASE(util_GetBoolArg)
 320  {
 321      TestArgsManager testArgs;
 322      const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
 323      const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
 324      const auto c = std::make_pair("-c", ArgsManager::ALLOW_ANY);
 325      const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
 326      const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY);
 327      const auto f = std::make_pair("-f", ArgsManager::ALLOW_ANY);
 328  
 329      const char *argv_test[] = {
 330          "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"};
 331      std::string error;
 332      LOCK(testArgs.cs_args);
 333      testArgs.SetupArgs({a, b, c, d, e, f});
 334      BOOST_CHECK(testArgs.ParseParameters(7, argv_test, error));
 335  
 336      // Each letter should be set.
 337      for (const char opt : "abcdef")
 338          BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt);
 339  
 340      // Nothing else should be in the map
 341      BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 &&
 342                  testArgs.m_settings.ro_config.empty());
 343  
 344      // The -no prefix should get stripped on the way in.
 345      BOOST_CHECK(!testArgs.IsArgSet("-nob"));
 346  
 347      // The -b option is flagged as negated, and nothing else is
 348      BOOST_CHECK(testArgs.IsArgNegated("-b"));
 349      BOOST_CHECK(!testArgs.IsArgNegated("-a"));
 350  
 351      // Check expected values.
 352      BOOST_CHECK(testArgs.GetBoolArg("-a", false) == true);
 353      BOOST_CHECK(testArgs.GetBoolArg("-b", true) == false);
 354      BOOST_CHECK(testArgs.GetBoolArg("-c", true) == false);
 355      BOOST_CHECK(testArgs.GetBoolArg("-d", false) == true);
 356      BOOST_CHECK(testArgs.GetBoolArg("-e", true) == false);
 357      BOOST_CHECK(testArgs.GetBoolArg("-f", true) == false);
 358  }
 359  
 360  BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases)
 361  {
 362      // Test some awful edge cases that hopefully no user will ever exercise.
 363      TestArgsManager testArgs;
 364  
 365      // Params test
 366      const auto foo = std::make_pair("-foo", ArgsManager::ALLOW_ANY);
 367      const auto bar = std::make_pair("-bar", ArgsManager::ALLOW_ANY);
 368      const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"};
 369      testArgs.SetupArgs({foo, bar});
 370      std::string error;
 371      BOOST_CHECK(testArgs.ParseParameters(4, argv_test, error));
 372  
 373      // This was passed twice, second one overrides the negative setting.
 374      BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
 375      BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "");
 376  
 377      // A double negative is a positive, and not marked as negated.
 378      BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
 379      BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1");
 380  
 381      // Config test
 382      const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n";
 383      BOOST_CHECK(testArgs.ParseParameters(1, argv_test, error));
 384      testArgs.ReadConfigString(conf_test);
 385  
 386      // This was passed twice, second one overrides the negative setting,
 387      // and the value.
 388      BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
 389      BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "1");
 390  
 391      // A double negative is a positive, and does not count as negated.
 392      BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
 393      BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "1");
 394  
 395      // Combined test
 396      const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"};
 397      const char *combo_test_conf = "foo=1\nnobar=1\n";
 398      BOOST_CHECK(testArgs.ParseParameters(3, combo_test_args, error));
 399      testArgs.ReadConfigString(combo_test_conf);
 400  
 401      // Command line overrides, but doesn't erase old setting
 402      BOOST_CHECK(testArgs.IsArgNegated("-foo"));
 403      BOOST_CHECK(testArgs.GetArg("-foo", "xxx") == "0");
 404      BOOST_CHECK(testArgs.GetArgs("-foo").size() == 0);
 405  
 406      // Command line overrides, but doesn't erase old setting
 407      BOOST_CHECK(!testArgs.IsArgNegated("-bar"));
 408      BOOST_CHECK(testArgs.GetArg("-bar", "xxx") == "");
 409      BOOST_CHECK(testArgs.GetArgs("-bar").size() == 1
 410                  && testArgs.GetArgs("-bar").front() == "");
 411  }
 412  
 413  BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
 414  {
 415      const char *str_config =
 416         "a=\n"
 417         "b=1\n"
 418         "ccc=argument\n"
 419         "ccc=multiple\n"
 420         "d=e\n"
 421         "nofff=1\n"
 422         "noggg=0\n"
 423         "h=1\n"
 424         "noh=1\n"
 425         "noi=1\n"
 426         "i=1\n"
 427         "sec1.ccc=extend1\n"
 428         "\n"
 429         "[sec1]\n"
 430         "ccc=extend2\n"
 431         "d=eee\n"
 432         "h=1\n"
 433         "[sec2]\n"
 434         "ccc=extend3\n"
 435         "iii=2\n";
 436  
 437      TestArgsManager test_args;
 438      LOCK(test_args.cs_args);
 439      const auto a = std::make_pair("-a", ArgsManager::ALLOW_ANY);
 440      const auto b = std::make_pair("-b", ArgsManager::ALLOW_ANY);
 441      const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_ANY);
 442      const auto d = std::make_pair("-d", ArgsManager::ALLOW_ANY);
 443      const auto e = std::make_pair("-e", ArgsManager::ALLOW_ANY);
 444      const auto fff = std::make_pair("-fff", ArgsManager::ALLOW_ANY);
 445      const auto ggg = std::make_pair("-ggg", ArgsManager::ALLOW_ANY);
 446      const auto h = std::make_pair("-h", ArgsManager::ALLOW_ANY);
 447      const auto i = std::make_pair("-i", ArgsManager::ALLOW_ANY);
 448      const auto iii = std::make_pair("-iii", ArgsManager::ALLOW_ANY);
 449      test_args.SetupArgs({a, b, ccc, d, e, fff, ggg, h, i, iii});
 450  
 451      test_args.ReadConfigString(str_config);
 452      // expectation: a, b, ccc, d, fff, ggg, h, i end up in map
 453      // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii
 454  
 455      BOOST_CHECK(test_args.m_settings.command_line_options.empty());
 456      BOOST_CHECK(test_args.m_settings.ro_config.size() == 3);
 457      BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8);
 458      BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3);
 459      BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2);
 460  
 461      BOOST_CHECK(test_args.m_settings.ro_config[""].count("a"));
 462      BOOST_CHECK(test_args.m_settings.ro_config[""].count("b"));
 463      BOOST_CHECK(test_args.m_settings.ro_config[""].count("ccc"));
 464      BOOST_CHECK(test_args.m_settings.ro_config[""].count("d"));
 465      BOOST_CHECK(test_args.m_settings.ro_config[""].count("fff"));
 466      BOOST_CHECK(test_args.m_settings.ro_config[""].count("ggg"));
 467      BOOST_CHECK(test_args.m_settings.ro_config[""].count("h"));
 468      BOOST_CHECK(test_args.m_settings.ro_config[""].count("i"));
 469      BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc"));
 470      BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("h"));
 471      BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("ccc"));
 472      BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("iii"));
 473  
 474      BOOST_CHECK(test_args.IsArgSet("-a"));
 475      BOOST_CHECK(test_args.IsArgSet("-b"));
 476      BOOST_CHECK(test_args.IsArgSet("-ccc"));
 477      BOOST_CHECK(test_args.IsArgSet("-d"));
 478      BOOST_CHECK(test_args.IsArgSet("-fff"));
 479      BOOST_CHECK(test_args.IsArgSet("-ggg"));
 480      BOOST_CHECK(test_args.IsArgSet("-h"));
 481      BOOST_CHECK(test_args.IsArgSet("-i"));
 482      BOOST_CHECK(!test_args.IsArgSet("-zzz"));
 483      BOOST_CHECK(!test_args.IsArgSet("-iii"));
 484  
 485      BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
 486      BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
 487      BOOST_CHECK_EQUAL(test_args.GetArg("-ccc", "xxx"), "argument");
 488      BOOST_CHECK_EQUAL(test_args.GetArg("-d", "xxx"), "e");
 489      BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
 490      BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
 491      BOOST_CHECK_EQUAL(test_args.GetArg("-h", "xxx"), "0");
 492      BOOST_CHECK_EQUAL(test_args.GetArg("-i", "xxx"), "1");
 493      BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
 494      BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
 495  
 496      for (const bool def : {false, true}) {
 497          BOOST_CHECK(test_args.GetBoolArg("-a", def));
 498          BOOST_CHECK(test_args.GetBoolArg("-b", def));
 499          BOOST_CHECK(!test_args.GetBoolArg("-ccc", def));
 500          BOOST_CHECK(!test_args.GetBoolArg("-d", def));
 501          BOOST_CHECK(!test_args.GetBoolArg("-fff", def));
 502          BOOST_CHECK(test_args.GetBoolArg("-ggg", def));
 503          BOOST_CHECK(!test_args.GetBoolArg("-h", def));
 504          BOOST_CHECK(test_args.GetBoolArg("-i", def));
 505          BOOST_CHECK(test_args.GetBoolArg("-zzz", def) == def);
 506          BOOST_CHECK(test_args.GetBoolArg("-iii", def) == def);
 507      }
 508  
 509      BOOST_CHECK(test_args.GetArgs("-a").size() == 1
 510                  && test_args.GetArgs("-a").front() == "");
 511      BOOST_CHECK(test_args.GetArgs("-b").size() == 1
 512                  && test_args.GetArgs("-b").front() == "1");
 513      BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2
 514                  && test_args.GetArgs("-ccc").front() == "argument"
 515                  && test_args.GetArgs("-ccc").back() == "multiple");
 516      BOOST_CHECK(test_args.GetArgs("-fff").size() == 0);
 517      BOOST_CHECK(test_args.GetArgs("-nofff").size() == 0);
 518      BOOST_CHECK(test_args.GetArgs("-ggg").size() == 1
 519                  && test_args.GetArgs("-ggg").front() == "1");
 520      BOOST_CHECK(test_args.GetArgs("-noggg").size() == 0);
 521      BOOST_CHECK(test_args.GetArgs("-h").size() == 0);
 522      BOOST_CHECK(test_args.GetArgs("-noh").size() == 0);
 523      BOOST_CHECK(test_args.GetArgs("-i").size() == 1
 524                  && test_args.GetArgs("-i").front() == "1");
 525      BOOST_CHECK(test_args.GetArgs("-noi").size() == 0);
 526      BOOST_CHECK(test_args.GetArgs("-zzz").size() == 0);
 527  
 528      BOOST_CHECK(!test_args.IsArgNegated("-a"));
 529      BOOST_CHECK(!test_args.IsArgNegated("-b"));
 530      BOOST_CHECK(!test_args.IsArgNegated("-ccc"));
 531      BOOST_CHECK(!test_args.IsArgNegated("-d"));
 532      BOOST_CHECK(test_args.IsArgNegated("-fff"));
 533      BOOST_CHECK(!test_args.IsArgNegated("-ggg"));
 534      BOOST_CHECK(test_args.IsArgNegated("-h")); // last setting takes precedence
 535      BOOST_CHECK(!test_args.IsArgNegated("-i")); // last setting takes precedence
 536      BOOST_CHECK(!test_args.IsArgNegated("-zzz"));
 537  
 538      // Test sections work
 539      test_args.SelectConfigNetwork("sec1");
 540  
 541      // same as original
 542      BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
 543      BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
 544      BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
 545      BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
 546      BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
 547      BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
 548      // d is overridden
 549      BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
 550      // section-specific setting
 551      BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1");
 552      // section takes priority for multiple values
 553      BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend1");
 554      // check multiple values works
 555      const std::vector<std::string> sec1_ccc_expected = {"extend1","extend2","argument","multiple"};
 556      const auto& sec1_ccc_res = test_args.GetArgs("-ccc");
 557      BOOST_CHECK_EQUAL_COLLECTIONS(sec1_ccc_res.begin(), sec1_ccc_res.end(), sec1_ccc_expected.begin(), sec1_ccc_expected.end());
 558  
 559      test_args.SelectConfigNetwork("sec2");
 560  
 561      // same as original
 562      BOOST_CHECK(test_args.GetArg("-a", "xxx") == "");
 563      BOOST_CHECK(test_args.GetArg("-b", "xxx") == "1");
 564      BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
 565      BOOST_CHECK(test_args.GetArg("-fff", "xxx") == "0");
 566      BOOST_CHECK(test_args.GetArg("-ggg", "xxx") == "1");
 567      BOOST_CHECK(test_args.GetArg("-zzz", "xxx") == "xxx");
 568      BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
 569      // section-specific setting
 570      BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2");
 571      // section takes priority for multiple values
 572      BOOST_CHECK(test_args.GetArg("-ccc", "xxx") == "extend3");
 573      // check multiple values works
 574      const std::vector<std::string> sec2_ccc_expected = {"extend3","argument","multiple"};
 575      const auto& sec2_ccc_res = test_args.GetArgs("-ccc");
 576      BOOST_CHECK_EQUAL_COLLECTIONS(sec2_ccc_res.begin(), sec2_ccc_res.end(), sec2_ccc_expected.begin(), sec2_ccc_expected.end());
 577  
 578      // Test section only options
 579  
 580      test_args.SetNetworkOnlyArg("-d");
 581      test_args.SetNetworkOnlyArg("-ccc");
 582      test_args.SetNetworkOnlyArg("-h");
 583  
 584      test_args.SelectConfigNetwork(ChainTypeToString(ChainType::MAIN));
 585      BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
 586      BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2);
 587      BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
 588  
 589      test_args.SelectConfigNetwork("sec1");
 590      BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
 591      BOOST_CHECK(test_args.GetArgs("-d").size() == 1);
 592      BOOST_CHECK(test_args.GetArgs("-ccc").size() == 2);
 593      BOOST_CHECK(test_args.GetArg("-h", "xxx") == "1");
 594  
 595      test_args.SelectConfigNetwork("sec2");
 596      BOOST_CHECK(test_args.GetArg("-d", "xxx") == "xxx");
 597      BOOST_CHECK(test_args.GetArgs("-d").size() == 0);
 598      BOOST_CHECK(test_args.GetArgs("-ccc").size() == 1);
 599      BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
 600  }
 601  
 602  BOOST_AUTO_TEST_CASE(util_GetArg)
 603  {
 604      TestArgsManager testArgs;
 605      LOCK(testArgs.cs_args);
 606      testArgs.m_settings.command_line_options.clear();
 607      testArgs.m_settings.command_line_options["strtest1"] = {"string..."};
 608      // strtest2 undefined on purpose
 609      testArgs.m_settings.command_line_options["inttest1"] = {"12345"};
 610      testArgs.m_settings.command_line_options["inttest2"] = {"81985529216486895"};
 611      // inttest3 undefined on purpose
 612      testArgs.m_settings.command_line_options["booltest1"] = {""};
 613      // booltest2 undefined on purpose
 614      testArgs.m_settings.command_line_options["booltest3"] = {"0"};
 615      testArgs.m_settings.command_line_options["booltest4"] = {"1"};
 616  
 617      // priorities
 618      testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"};
 619      testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"};
 620      testArgs.m_settings.command_line_options["pritest3"] = {"a"};
 621      testArgs.m_settings.ro_config[""]["pritest3"] = {"b"};
 622      testArgs.m_settings.command_line_options["pritest4"] = {"a","b"};
 623      testArgs.m_settings.ro_config[""]["pritest4"] = {"c","d"};
 624  
 625      BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string...");
 626      BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default");
 627      BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest1", -1), 12345);
 628      BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest2", -1), 81985529216486895LL);
 629      BOOST_CHECK_EQUAL(testArgs.GetIntArg("inttest3", -1), -1);
 630      BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest1", false), true);
 631      BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest2", false), false);
 632      BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest3", false), false);
 633      BOOST_CHECK_EQUAL(testArgs.GetBoolArg("booltest4", false), true);
 634  
 635      BOOST_CHECK_EQUAL(testArgs.GetArg("pritest1", "default"), "b");
 636      BOOST_CHECK_EQUAL(testArgs.GetArg("pritest2", "default"), "a");
 637      BOOST_CHECK_EQUAL(testArgs.GetArg("pritest3", "default"), "a");
 638      BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b");
 639  }
 640  
 641  BOOST_AUTO_TEST_CASE(util_GetChainTypeString)
 642  {
 643      TestArgsManager test_args;
 644      const auto testnet = std::make_pair("-testnet", ArgsManager::ALLOW_ANY);
 645      const auto regtest = std::make_pair("-regtest", ArgsManager::ALLOW_ANY);
 646      test_args.SetupArgs({testnet, regtest});
 647  
 648      const char* argv_testnet[] = {"cmd", "-testnet"};
 649      const char* argv_regtest[] = {"cmd", "-regtest"};
 650      const char* argv_test_no_reg[] = {"cmd", "-testnet", "-noregtest"};
 651      const char* argv_both[] = {"cmd", "-testnet", "-regtest"};
 652  
 653      // equivalent to "-testnet"
 654      // regtest in testnet section is ignored
 655      const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1";
 656      std::string error;
 657  
 658      BOOST_CHECK(test_args.ParseParameters(0, argv_testnet, error));
 659      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "main");
 660  
 661      BOOST_CHECK(test_args.ParseParameters(2, argv_testnet, error));
 662      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 663  
 664      BOOST_CHECK(test_args.ParseParameters(2, argv_regtest, error));
 665      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "regtest");
 666  
 667      BOOST_CHECK(test_args.ParseParameters(3, argv_test_no_reg, error));
 668      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 669  
 670      BOOST_CHECK(test_args.ParseParameters(3, argv_both, error));
 671      BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error);
 672  
 673      BOOST_CHECK(test_args.ParseParameters(0, argv_testnet, error));
 674      test_args.ReadConfigString(testnetconf);
 675      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 676  
 677      BOOST_CHECK(test_args.ParseParameters(2, argv_testnet, error));
 678      test_args.ReadConfigString(testnetconf);
 679      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 680  
 681      BOOST_CHECK(test_args.ParseParameters(2, argv_regtest, error));
 682      test_args.ReadConfigString(testnetconf);
 683      BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error);
 684  
 685      BOOST_CHECK(test_args.ParseParameters(3, argv_test_no_reg, error));
 686      test_args.ReadConfigString(testnetconf);
 687      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 688  
 689      BOOST_CHECK(test_args.ParseParameters(3, argv_both, error));
 690      test_args.ReadConfigString(testnetconf);
 691      BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error);
 692  
 693      // check setting the network to test (and thus making
 694      // [test] regtest=1 potentially relevant) doesn't break things
 695      test_args.SelectConfigNetwork("test");
 696  
 697      BOOST_CHECK(test_args.ParseParameters(0, argv_testnet, error));
 698      test_args.ReadConfigString(testnetconf);
 699      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 700  
 701      BOOST_CHECK(test_args.ParseParameters(2, argv_testnet, error));
 702      test_args.ReadConfigString(testnetconf);
 703      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 704  
 705      BOOST_CHECK(test_args.ParseParameters(2, argv_regtest, error));
 706      test_args.ReadConfigString(testnetconf);
 707      BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error);
 708  
 709      BOOST_CHECK(test_args.ParseParameters(2, argv_test_no_reg, error));
 710      test_args.ReadConfigString(testnetconf);
 711      BOOST_CHECK_EQUAL(test_args.GetChainTypeString(), "test");
 712  
 713      BOOST_CHECK(test_args.ParseParameters(3, argv_both, error));
 714      test_args.ReadConfigString(testnetconf);
 715      BOOST_CHECK_THROW(test_args.GetChainTypeString(), std::runtime_error);
 716  }
 717  
 718  // Test different ways settings can be merged, and verify results. This test can
 719  // be used to confirm that updates to settings code don't change behavior
 720  // unintentionally.
 721  //
 722  // The test covers:
 723  //
 724  // - Combining different setting actions. Possible actions are: configuring a
 725  //   setting, negating a setting (adding "-no" prefix), and configuring/negating
 726  //   settings in a network section (adding "main." or "test." prefixes).
 727  //
 728  // - Combining settings from command line arguments and a config file.
 729  //
 730  // - Combining SoftSet and ForceSet calls.
 731  //
 732  // - Testing "main" and "test" network values to make sure settings from network
 733  //   sections are applied and to check for mainnet-specific behaviors like
 734  //   inheriting settings from the default section.
 735  //
 736  // - Testing network-specific settings like "-wallet", that may be ignored
 737  //   outside a network section, and non-network specific settings like "-server"
 738  //   that aren't sensitive to the network.
 739  //
 740  struct ArgsMergeTestingSetup : public BasicTestingSetup {
 741      //! Max number of actions to sequence together. Can decrease this when
 742      //! debugging to make test results easier to understand.
 743      static constexpr int MAX_ACTIONS = 3;
 744  
 745      enum Action { NONE, SET, NEGATE, SECTION_SET, SECTION_NEGATE };
 746      using ActionList = Action[MAX_ACTIONS];
 747  
 748      //! Enumerate all possible test configurations.
 749      template <typename Fn>
 750      void ForEachMergeSetup(Fn&& fn)
 751      {
 752          ActionList arg_actions = {};
 753          // command_line_options do not have sections. Only iterate over SET and NEGATE
 754          ForEachNoDup(arg_actions, SET, NEGATE, [&] {
 755              ActionList conf_actions = {};
 756              ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] {
 757                  for (bool soft_set : {false, true}) {
 758                      for (bool force_set : {false, true}) {
 759                          for (const std::string& section : {ChainTypeToString(ChainType::MAIN), ChainTypeToString(ChainType::TESTNET), ChainTypeToString(ChainType::SIGNET)}) {
 760                              for (const std::string& network : {ChainTypeToString(ChainType::MAIN), ChainTypeToString(ChainType::TESTNET), ChainTypeToString(ChainType::SIGNET)}) {
 761                                  for (bool net_specific : {false, true}) {
 762                                      fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
 763                                  }
 764                              }
 765                          }
 766                      }
 767                  }
 768              });
 769          });
 770      }
 771  
 772      //! Translate actions into a list of <key>=<value> setting strings.
 773      std::vector<std::string> GetValues(const ActionList& actions,
 774          const std::string& section,
 775          const std::string& name,
 776          const std::string& value_prefix)
 777      {
 778          std::vector<std::string> values;
 779          int suffix = 0;
 780          for (Action action : actions) {
 781              if (action == NONE) break;
 782              std::string prefix;
 783              if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + ".";
 784              if (action == SET || action == SECTION_SET) {
 785                  for (int i = 0; i < 2; ++i) {
 786                      values.push_back(prefix + name + "=" + value_prefix + ToString(++suffix));
 787                  }
 788              }
 789              if (action == NEGATE || action == SECTION_NEGATE) {
 790                  values.push_back(prefix + "no" + name + "=1");
 791              }
 792          }
 793          return values;
 794      }
 795  };
 796  
 797  // Regression test covering different ways config settings can be merged. The
 798  // test parses and merges settings, representing the results as strings that get
 799  // compared against an expected hash. To debug, the result strings can be dumped
 800  // to a file (see comments below).
 801  BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup)
 802  {
 803      CHash256 out_sha;
 804      FILE* out_file = nullptr;
 805      if (const char* out_path = getenv("ARGS_MERGE_TEST_OUT")) {
 806          out_file = fsbridge::fopen(out_path, "w");
 807          if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
 808      }
 809  
 810      ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set,
 811                            const std::string& section, const std::string& network, bool net_specific) {
 812          TestArgsManager parser;
 813          LOCK(parser.cs_args);
 814  
 815          std::string desc = "net=";
 816          desc += network;
 817          parser.m_network = network;
 818  
 819          const std::string& name = net_specific ? "wallet" : "server";
 820          const std::string key = "-" + name;
 821          parser.AddArg(key, name, ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
 822          if (net_specific) parser.SetNetworkOnlyArg(key);
 823  
 824          auto args = GetValues(arg_actions, section, name, "a");
 825          std::vector<const char*> argv = {"ignored"};
 826          for (auto& arg : args) {
 827              arg.insert(0, "-");
 828              desc += " ";
 829              desc += arg;
 830              argv.push_back(arg.c_str());
 831          }
 832          std::string error;
 833          BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
 834          BOOST_CHECK_EQUAL(error, "");
 835  
 836          std::string conf;
 837          for (auto& conf_val : GetValues(conf_actions, section, name, "c")) {
 838              desc += " ";
 839              desc += conf_val;
 840              conf += conf_val;
 841              conf += "\n";
 842          }
 843          std::istringstream conf_stream(conf);
 844          BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
 845          BOOST_CHECK_EQUAL(error, "");
 846  
 847          if (soft_set) {
 848              desc += " soft";
 849              parser.SoftSetArg(key, "soft1");
 850              parser.SoftSetArg(key, "soft2");
 851          }
 852  
 853          if (force_set) {
 854              desc += " force";
 855              parser.ForceSetArg(key, "force1");
 856              parser.ForceSetArg(key, "force2");
 857          }
 858  
 859          desc += " || ";
 860  
 861          if (!parser.IsArgSet(key)) {
 862              desc += "unset";
 863              BOOST_CHECK(!parser.IsArgNegated(key));
 864              BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "default");
 865              BOOST_CHECK(parser.GetArgs(key).empty());
 866          } else if (parser.IsArgNegated(key)) {
 867              desc += "negated";
 868              BOOST_CHECK_EQUAL(parser.GetArg(key, "default"), "0");
 869              BOOST_CHECK(parser.GetArgs(key).empty());
 870          } else {
 871              desc += parser.GetArg(key, "default");
 872              desc += " |";
 873              for (const auto& arg : parser.GetArgs(key)) {
 874                  desc += " ";
 875                  desc += arg;
 876              }
 877          }
 878  
 879          std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs();
 880          if (!ignored.empty()) {
 881              desc += " | ignored";
 882              for (const auto& arg : ignored) {
 883                  desc += " ";
 884                  desc += arg;
 885              }
 886          }
 887  
 888          desc += "\n";
 889  
 890          out_sha.Write(MakeUCharSpan(desc));
 891          if (out_file) {
 892              BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
 893          }
 894      });
 895  
 896      if (out_file) {
 897          if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
 898          out_file = nullptr;
 899      }
 900  
 901      unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
 902      out_sha.Finalize(out_sha_bytes);
 903      std::string out_sha_hex = HexStr(out_sha_bytes);
 904  
 905      // If check below fails, should manually dump the results with:
 906      //
 907      //   ARGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ArgsMerge
 908      //
 909      // And verify diff against previous results to make sure the changes are expected.
 910      //
 911      // Results file is formatted like:
 912      //
 913      //   <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
 914      BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82");
 915  }
 916  
 917  // Similar test as above, but for ArgsManager::GetChainTypeString function.
 918  struct ChainMergeTestingSetup : public BasicTestingSetup {
 919      static constexpr int MAX_ACTIONS = 2;
 920  
 921      enum Action { NONE, ENABLE_TEST, DISABLE_TEST, NEGATE_TEST, ENABLE_REG, DISABLE_REG, NEGATE_REG };
 922      using ActionList = Action[MAX_ACTIONS];
 923  
 924      //! Enumerate all possible test configurations.
 925      template <typename Fn>
 926      void ForEachMergeSetup(Fn&& fn)
 927      {
 928          ActionList arg_actions = {};
 929          ForEachNoDup(arg_actions, ENABLE_TEST, NEGATE_REG, [&] {
 930              ActionList conf_actions = {};
 931              ForEachNoDup(conf_actions, ENABLE_TEST, NEGATE_REG, [&] { fn(arg_actions, conf_actions); });
 932          });
 933      }
 934  };
 935  
 936  BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
 937  {
 938      CHash256 out_sha;
 939      FILE* out_file = nullptr;
 940      if (const char* out_path = getenv("CHAIN_MERGE_TEST_OUT")) {
 941          out_file = fsbridge::fopen(out_path, "w");
 942          if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed");
 943      }
 944  
 945      ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions) {
 946          TestArgsManager parser;
 947          LOCK(parser.cs_args);
 948          parser.AddArg("-regtest", "regtest", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
 949          parser.AddArg("-testnet", "testnet", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
 950  
 951          auto arg = [](Action action) { return action == ENABLE_TEST  ? "-testnet=1"   :
 952                                                action == DISABLE_TEST ? "-testnet=0"   :
 953                                                action == NEGATE_TEST  ? "-notestnet=1" :
 954                                                action == ENABLE_REG   ? "-regtest=1"   :
 955                                                action == DISABLE_REG  ? "-regtest=0"   :
 956                                                action == NEGATE_REG   ? "-noregtest=1" : nullptr; };
 957  
 958          std::string desc;
 959          std::vector<const char*> argv = {"ignored"};
 960          for (Action action : arg_actions) {
 961              const char* argstr = arg(action);
 962              if (!argstr) break;
 963              argv.push_back(argstr);
 964              desc += " ";
 965              desc += argv.back();
 966          }
 967          std::string error;
 968          BOOST_CHECK(parser.ParseParameters(argv.size(), argv.data(), error));
 969          BOOST_CHECK_EQUAL(error, "");
 970  
 971          std::string conf;
 972          for (Action action : conf_actions) {
 973              const char* argstr = arg(action);
 974              if (!argstr) break;
 975              desc += " ";
 976              desc += argstr + 1;
 977              conf += argstr + 1;
 978              conf += "\n";
 979          }
 980          std::istringstream conf_stream(conf);
 981          BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error));
 982          BOOST_CHECK_EQUAL(error, "");
 983  
 984          desc += " || ";
 985          try {
 986              desc += parser.GetChainTypeString();
 987          } catch (const std::runtime_error& e) {
 988              desc += "error: ";
 989              desc += e.what();
 990          }
 991          desc += "\n";
 992  
 993          out_sha.Write(MakeUCharSpan(desc));
 994          if (out_file) {
 995              BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size());
 996          }
 997      });
 998  
 999      if (out_file) {
1000          if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed");
1001          out_file = nullptr;
1002      }
1003  
1004      unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
1005      out_sha.Finalize(out_sha_bytes);
1006      std::string out_sha_hex = HexStr(out_sha_bytes);
1007  
1008      // If check below fails, should manually dump the results with:
1009      //
1010      //   CHAIN_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_ChainMerge
1011      //
1012      // And verify diff against previous results to make sure the changes are expected.
1013      //
1014      // Results file is formatted like:
1015      //
1016      //   <input> || <output>
1017      BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c");
1018  }
1019  
1020  BOOST_AUTO_TEST_CASE(util_ReadWriteSettings)
1021  {
1022      // Test writing setting.
1023      TestArgsManager args1;
1024      args1.ForceSetArg("-datadir", fs::PathToString(m_path_root));
1025      args1.LockSettings([&](common::Settings& settings) { settings.rw_settings["name"] = "value"; });
1026      args1.WriteSettingsFile();
1027  
1028      // Test reading setting.
1029      TestArgsManager args2;
1030      args2.ForceSetArg("-datadir", fs::PathToString(m_path_root));
1031      args2.ReadSettingsFile();
1032      args2.LockSettings([&](common::Settings& settings) { BOOST_CHECK_EQUAL(settings.rw_settings["name"].get_str(), "value"); });
1033  
1034      // Test error logging, and remove previously written setting.
1035      {
1036          ASSERT_DEBUG_LOG("Failed renaming settings file");
1037          fs::remove(args1.GetDataDirBase() / "settings.json");
1038          fs::create_directory(args1.GetDataDirBase() / "settings.json");
1039          args2.WriteSettingsFile();
1040          fs::remove(args1.GetDataDirBase() / "settings.json");
1041      }
1042  }
1043  
1044  BOOST_AUTO_TEST_SUITE_END()