@@ -158,6 +158,9 @@ struct TestArgsManager : public ArgsManager
158
158
AddArg (args[i], " " , false , OptionsCategory::OPTIONS);
159
159
}
160
160
}
161
+ using ArgsManager::ReadConfigStream;
162
+ using ArgsManager::cs_args;
163
+ using ArgsManager::m_network;
161
164
};
162
165
163
166
BOOST_AUTO_TEST_CASE (util_ParseParameters)
@@ -575,6 +578,232 @@ BOOST_AUTO_TEST_CASE(util_GetChainName)
575
578
BOOST_CHECK_THROW (test_args.GetChainName (), std::runtime_error);
576
579
}
577
580
581
+ // Test different ways settings can be merged, and verify results. This test can
582
+ // be used to confirm that updates to settings code don't change behavior
583
+ // unintentially.
584
+ //
585
+ // The test covers:
586
+ //
587
+ // - Combining different setting actions. Possible actions are: configuring a
588
+ // setting, negating a setting (adding "-no" prefix), and configuring/negating
589
+ // settings in a network section (adding "main." or "test." prefixes).
590
+ //
591
+ // - Combining settings from command line arguments and a config file.
592
+ //
593
+ // - Combining SoftSet and ForceSet calls.
594
+ //
595
+ // - Testing "main" and "test" network values to make sure settings from network
596
+ // sections are applied and to check for mainnet-specific behaviors like
597
+ // inheriting settings from the default section.
598
+ //
599
+ // - Testing network-specific settings like "-wallet", that may be ignored
600
+ // outside a network section, and non-network specific settings like "-server"
601
+ // that aren't sensitive to the network.
602
+ //
603
+ struct SettingsMergeTestingSetup : public BasicTestingSetup {
604
+ // ! Max number of actions to sequence together. Can decrease this when
605
+ // ! debugging to make test results easier to understand.
606
+ static constexpr int MAX_ACTIONS = 3 ;
607
+
608
+ enum Action { SET = 0 , NEGATE, SECTION_SET, SECTION_NEGATE, END };
609
+ using ActionList = Action[MAX_ACTIONS];
610
+
611
+ // ! Enumerate all possible test configurations.
612
+ template <typename Fn>
613
+ void ForEachMergeSetup (Fn&& fn)
614
+ {
615
+ ForEachActionList ([&](const ActionList& arg_actions) {
616
+ ForEachActionList ([&](const ActionList& conf_actions) {
617
+ for (bool soft_set : {false , true }) {
618
+ for (bool force_set : {false , true }) {
619
+ for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) {
620
+ for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) {
621
+ for (bool net_specific : {false , true }) {
622
+ fn (arg_actions, conf_actions, soft_set, force_set, section, network, net_specific);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ });
629
+ });
630
+ }
631
+
632
+ // ! Enumerate interesting combinations of actions.
633
+ template <typename Fn>
634
+ void ForEachActionList (Fn&& fn)
635
+ {
636
+ ActionList actions = {SET};
637
+ for (bool done = false ; !done;) {
638
+ int prev_action = -1 ;
639
+ bool skip_actions = false ;
640
+ for (Action action : actions) {
641
+ if ((prev_action == END && action != END) || (prev_action != END && action == prev_action)) {
642
+ // To cut down list of enumerated settings, skip enumerating
643
+ // settings with ignored actions after an END, and settings that
644
+ // repeat the same action twice in a row.
645
+ skip_actions = true ;
646
+ break ;
647
+ }
648
+ prev_action = action;
649
+ }
650
+ if (!skip_actions) fn (actions);
651
+ done = true ;
652
+ for (Action& action : actions) {
653
+ action = Action (action < END ? action + 1 : 0 );
654
+ if (action) {
655
+ done = false ;
656
+ break ;
657
+ }
658
+ }
659
+ }
660
+ }
661
+
662
+ // ! Translate actions into a list of <key>=<value> setting strings.
663
+ std::vector<std::string> GetValues (const ActionList& actions,
664
+ const std::string& section,
665
+ const std::string& name,
666
+ const std::string& value_prefix)
667
+ {
668
+ std::vector<std::string> values;
669
+ int suffix = 0 ;
670
+ for (Action action : actions) {
671
+ if (action == END) break ;
672
+ std::string prefix;
673
+ if (action == SECTION_SET || action == SECTION_NEGATE) prefix = section + " ." ;
674
+ if (action == SET || action == SECTION_SET) {
675
+ for (int i = 0 ; i < 2 ; ++i) {
676
+ values.push_back (prefix + name + " =" + value_prefix + std::to_string (++suffix));
677
+ }
678
+ }
679
+ if (action == NEGATE || action == SECTION_NEGATE) {
680
+ values.push_back (prefix + " no" + name + " =1" );
681
+ }
682
+ }
683
+ return values;
684
+ }
685
+ };
686
+
687
+ // Regression test covering different ways config settings can be merged. The
688
+ // test parses and merges settings, representing the results as strings that get
689
+ // compared against an expected hash. To debug, the result strings can be dumped
690
+ // to a file (see below).
691
+ BOOST_FIXTURE_TEST_CASE (util_SettingsMerge, SettingsMergeTestingSetup)
692
+ {
693
+ CHash256 out_sha;
694
+ FILE* out_file = nullptr ;
695
+ if (const char * out_path = getenv (" SETTINGS_MERGE_TEST_OUT" )) {
696
+ out_file = fsbridge::fopen (out_path, " w" );
697
+ if (!out_file) throw std::system_error (errno, std::generic_category (), " fopen failed" );
698
+ }
699
+
700
+ ForEachMergeSetup ([&](const ActionList& arg_actions, const ActionList& conf_actions, bool soft_set, bool force_set,
701
+ const std::string& section, const std::string& network, bool net_specific) {
702
+ TestArgsManager parser;
703
+ LOCK (parser.cs_args );
704
+
705
+ std::string desc = " net=" ;
706
+ desc += network;
707
+ parser.m_network = network;
708
+
709
+ const std::string& name = net_specific ? " server" : " wallet" ;
710
+ const std::string key = " -" + name;
711
+ parser.AddArg (key, name, false , OptionsCategory::OPTIONS);
712
+ if (net_specific) parser.SetNetworkOnlyArg (key);
713
+
714
+ auto args = GetValues (arg_actions, section, name, " a" );
715
+ std::vector<const char *> argv = {" ignored" };
716
+ for (auto & arg : args) {
717
+ arg.insert (0 , " -" );
718
+ desc += " " ;
719
+ desc += arg;
720
+ argv.push_back (arg.c_str ());
721
+ }
722
+ std::string error;
723
+ BOOST_CHECK (parser.ParseParameters (argv.size (), argv.data (), error));
724
+ BOOST_CHECK_EQUAL (error, " " );
725
+
726
+ std::string conf;
727
+ for (auto & conf_val : GetValues (conf_actions, section, name, " c" )) {
728
+ desc += " " ;
729
+ desc += conf_val;
730
+ conf += conf_val;
731
+ conf += " \n " ;
732
+ }
733
+ std::istringstream conf_stream (conf);
734
+ BOOST_CHECK (parser.ReadConfigStream (conf_stream, " filepath" , error));
735
+ BOOST_CHECK_EQUAL (error, " " );
736
+
737
+ if (soft_set) {
738
+ desc += " soft" ;
739
+ parser.SoftSetArg (key, " soft1" );
740
+ parser.SoftSetArg (key, " soft2" );
741
+ }
742
+
743
+ if (force_set) {
744
+ desc += " force" ;
745
+ parser.ForceSetArg (key, " force1" );
746
+ parser.ForceSetArg (key, " force2" );
747
+ }
748
+
749
+ desc += " || " ;
750
+
751
+ if (!parser.IsArgSet (key)) {
752
+ desc += " unset" ;
753
+ BOOST_CHECK (!parser.IsArgNegated (key));
754
+ BOOST_CHECK_EQUAL (parser.GetArg (key, " default" ), " default" );
755
+ BOOST_CHECK (parser.GetArgs (key).empty ());
756
+ } else if (parser.IsArgNegated (key)) {
757
+ desc += " negated" ;
758
+ BOOST_CHECK_EQUAL (parser.GetArg (key, " default" ), " 0" );
759
+ BOOST_CHECK (parser.GetArgs (key).empty ());
760
+ } else {
761
+ desc += parser.GetArg (key, " default" );
762
+ desc += " |" ;
763
+ for (const auto & arg : parser.GetArgs (key)) {
764
+ desc += " " ;
765
+ desc += arg;
766
+ }
767
+ }
768
+
769
+ std::set<std::string> ignored = parser.GetUnsuitableSectionOnlyArgs ();
770
+ if (!ignored.empty ()) {
771
+ desc += " | ignored" ;
772
+ for (const auto & arg : ignored) {
773
+ desc += " " ;
774
+ desc += arg;
775
+ }
776
+ }
777
+
778
+ desc += " \n " ;
779
+
780
+ out_sha.Write ((const unsigned char *)desc.data (), desc.size ());
781
+ if (out_file) {
782
+ BOOST_REQUIRE (fwrite (desc.data (), 1 , desc.size (), out_file) == desc.size ());
783
+ }
784
+ });
785
+
786
+ if (out_file) {
787
+ if (fclose (out_file)) throw std::system_error (errno, std::generic_category (), " fclose failed" );
788
+ out_file = nullptr ;
789
+ }
790
+
791
+ unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE];
792
+ out_sha.Finalize (out_sha_bytes);
793
+ std::string out_sha_hex = HexStr (std::begin (out_sha_bytes), std::end (out_sha_bytes));
794
+
795
+ // If check below fails, should manually dump the results with:
796
+ //
797
+ // SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=util_tests/util_SettingsMerge
798
+ //
799
+ // And verify diff against previous results to make sure the changes are expected.
800
+ //
801
+ // Results file is formatted like:
802
+ //
803
+ // <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output>
804
+ BOOST_CHECK_EQUAL (out_sha_hex, " 80964e17fbd3c5569d3c824d032e28e2d319ef57494735b0e76eb7aad9957f2c" );
805
+ }
806
+
578
807
BOOST_AUTO_TEST_CASE (util_FormatMoney)
579
808
{
580
809
BOOST_CHECK_EQUAL (FormatMoney (0 ), " 0.00" );
0 commit comments