From 07aed05238de884f0f13a33d29047a65845f9389 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Sun, 19 Jul 2026 09:58:06 -0400 Subject: [PATCH 1/3] test: remove gMock dependency Replace gMock with small handwritten Node and Wallet test doubles. The doubles expose a `calls` object with one counter per configurable function, using the same name and case as the function. Every configurable override increments its counter. Tests declare exact, minimum, or zero-call expectations, and `VerifyOnExit()` checks them at the end of the scope. Expectations start from the count at registration time, so calls made during setup do not affect checks for the action under test. Tests control results through function callbacks and check captured arguments or event lists directly. The wallet double falls back to harmless stub values for behavior a test does not configure. The normal node double also permits unconfigured calls, while `StrictMockNode` records them and reports them together at scope exit. This keeps call behavior visible in each test without recreating a matcher language. --- .github/workflows/artifacts.yml | 8 +- .github/workflows/ci.yml | 8 - README.md | 2 - test/CMakeLists.txt | 7 - test/gmocktestfixture.h | 99 ---- test/mocks/callcounter.h | 28 + test/mocks/mocknode.h | 391 ++++++++++--- test/mocks/mockwallet.h | 232 ++++++-- test/mocks/stubnode.h | 331 +++++++++++ test/test_banlistmodel.cpp | 91 ++- test/test_blockclockdial.cpp | 11 +- test/test_bumptransactionmodel.cpp | 136 +++-- test/test_networktraffictower.cpp | 43 +- test/test_nodemodel.cpp | 617 ++++++++++----------- test/test_options_model.cpp | 566 +++++++------------ test/test_peerlistmodel.cpp | 95 ++-- test/test_psbtqmlmodel.cpp | 8 +- test/test_qmlinitexecutor_api.cpp | 65 +-- test/test_rpcconsolemodel.cpp | 132 +---- test/test_unit_tests_main.cpp | 3 - test/test_walletlistmodel.cpp | 109 ++-- test/test_walletqmlcontroller.cpp | 313 +++++------ test/test_walletqmlmodel.cpp | 857 +++++++++++++++-------------- 23 files changed, 2159 insertions(+), 1993 deletions(-) delete mode 100644 test/gmocktestfixture.h create mode 100644 test/mocks/callcounter.h create mode 100644 test/mocks/stubnode.h diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index 46fc162902..0ccbcbe6c4 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -30,10 +30,7 @@ jobs: - name: MacOS Install Deps if: contains(matrix.os, 'macos') run: | - brew install ccache boost pkgconf qt@6 qrencode coreutils llvm - if [ "${BUILD_APP_TESTS}" = "ON" ]; then - brew install googletest - fi + brew install ccache boost pkgconf libevent qt@6 qrencode coreutils llvm llvm_prefix="$(brew --prefix llvm)" echo "CC=${llvm_prefix}/bin/clang" >> "$GITHUB_ENV" echo "CXX=${llvm_prefix}/bin/clang++" >> "$GITHUB_ENV" @@ -48,9 +45,6 @@ jobs: libboost-dev libsqlite3-dev libgl-dev libqrencode-dev \ qt6-base-dev qt6-tools-dev qt6-l10n-tools qt6-tools-dev-tools \ qt6-declarative-dev qml6-module-qt-labs-settings qml6-module-qtquick qml6-module-qtquick-controls qml6-module-qtquick-dialogs qml6-module-qtquick-layouts qml6-module-qtquick-templates qml6-module-qtqml - if [ "${BUILD_APP_TESTS}" = "ON" ]; then - sudo apt-get install -y libgtest-dev libgmock-dev - fi echo "CCACHE_DIR=${{ runner.temp }}/ccache" >> "$GITHUB_ENV" - name: Restore Ccache cache diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 619335d383..e3d1bbc6d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,14 +56,6 @@ jobs: libgl-dev \ libqrencode-dev - - name: Install test dependencies - if: env.BUILD_APP_TESTS == 'ON' - run: | - sudo apt-get update - sudo apt-get install -y \ - libgtest-dev \ - libgmock-dev - - name: Configure (CMake) run: | cmake -S . -B build -G Ninja \ diff --git a/README.md b/README.md index dc105f3ea7..2126337766 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,6 @@ In addition the following dependencies are required for the GUI and tests: ``` sudo apt install \ - libgmock-dev \ - libgtest-dev \ qt6-base-dev \ qt6-base-dev-tools \ qt6-tools-dev \ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89a712b800..3646848606 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,15 +3,9 @@ cmake_minimum_required(VERSION 3.22) # Tests for the QML/Qt6 app find_package(Qt 6.2 MODULE REQUIRED COMPONENTS Core Gui Network Test Qml Quick QuickTest Widgets) -find_package(GTest REQUIRED) - -if(NOT TARGET GTest::gmock) - message(FATAL_ERROR "GTest::gmock target is required for app unit tests. Ensure gmock is installed and exported by the active GTest package.") -endif() add_executable(bitcoinqml_unit_tests test_unit_tests_main.cpp - gmocktestfixture.h test_bitcoinaddress.cpp test_blockclockdial.cpp test_bitcoinuri.cpp @@ -70,7 +64,6 @@ target_link_libraries(bitcoinqml_unit_tests bitcoin_common bitcoin_util bitcoin_node - GTest::gmock Qt6::Core Qt6::Gui Qt6::Quick diff --git a/test/gmocktestfixture.h b/test/gmocktestfixture.h deleted file mode 100644 index f0152400ad..0000000000 --- a/test/gmocktestfixture.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2026 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_QML_TEST_GMOCKTESTFIXTURE_H -#define BITCOIN_QML_TEST_GMOCKTESTFIXTURE_H - -#include -#include - -#ifdef Assert -#pragma push_macro("Assert") -#undef Assert -#define BITCOIN_QML_RESTORE_ASSERT_MACRO -#endif - -#include - -#ifdef BITCOIN_QML_RESTORE_ASSERT_MACRO -#pragma pop_macro("Assert") -#undef BITCOIN_QML_RESTORE_ASSERT_MACRO -#endif - -#include -#include -#include - -class GmockFailureLog -{ -public: - void add(std::string msg) - { - std::lock_guard lock(m_mutex); - m_failures.push_back(std::move(msg)); - } - - std::vector drain() - { - std::lock_guard lock(m_mutex); - std::vector out; - out.swap(m_failures); - return out; - } - -private: - std::mutex m_mutex; - std::vector m_failures; -}; - -inline GmockFailureLog& gmockFailureLog() -{ - static GmockFailureLog log; - return log; -} - -class QtestGmockListener : public testing::EmptyTestEventListener -{ -public: - void OnTestPartResult(const testing::TestPartResult& result) override - { - if (result.failed()) { - std::string msg; - if (result.file_name()) { - msg += result.file_name(); - msg += ":"; - msg += std::to_string(result.line_number()); - msg += "\n"; - } - msg += result.message(); - gmockFailureLog().add(std::move(msg)); - } - } -}; - -class GmockTestFixture : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void init() - { - gmockFailureLog().drain(); - } - - void cleanup() - { - auto failures = gmockFailureLog().drain(); - if (!failures.empty()) { - std::string combined{"GMock failure(s):\n"}; - for (const auto& f : failures) { - combined += f; - combined += "\n"; - } - QFAIL(combined.c_str()); - } - } -}; - -#endif // BITCOIN_QML_TEST_GMOCKTESTFIXTURE_H diff --git a/test/mocks/callcounter.h b/test/mocks/callcounter.h new file mode 100644 index 0000000000..74dc7f6b51 --- /dev/null +++ b/test/mocks/callcounter.h @@ -0,0 +1,28 @@ +// Copyright (c) 2026 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QML_TEST_MOCKS_CALLCOUNTER_H +#define BITCOIN_QML_TEST_MOCKS_CALLCOUNTER_H + +#include +#include + +class CallCounter +{ +public: + explicit CallCounter(std::string_view name) : m_name{name} {} + + CallCounter(const CallCounter&) = delete; + CallCounter& operator=(const CallCounter&) = delete; + + int operator++() { return m_calls.fetch_add(1) + 1; } + int load() const { return m_calls.load(); } + std::string_view Name() const { return m_name; } + +private: + std::atomic m_calls{0}; + const std::string_view m_name; +}; + +#endif // BITCOIN_QML_TEST_MOCKS_CALLCOUNTER_H diff --git a/test/mocks/mocknode.h b/test/mocks/mocknode.h index 28bb4709de..d58940cd50 100644 --- a/test/mocks/mocknode.h +++ b/test/mocks/mocknode.h @@ -5,94 +5,313 @@ #ifndef BITCOIN_QML_TEST_MOCKS_MOCKNODE_H #define BITCOIN_QML_TEST_MOCKS_MOCKNODE_H -// Bitcoin's util/check.h defines Assert(val), while gMock declares internal -// Assert(bool, file, line[, msg]) helpers. Some tests include Bitcoin headers -// before this mock header, so temporarily hide the macro while parsing gMock -// and restore it afterward. -#ifdef Assert -#pragma push_macro("Assert") -#undef Assert -#define BITCOIN_QML_RESTORE_ASSERT_MACRO -#endif - -#include - -#ifdef BITCOIN_QML_RESTORE_ASSERT_MACRO -#pragma pop_macro("Assert") -#undef BITCOIN_QML_RESTORE_ASSERT_MACRO -#endif - -#include -#include -#include - -#include -#include -#include -#include - -class MockNode : public interfaces::Node +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class MockNode : public StubNode +{ +public: + class Verification + { + public: + Verification(MockNode& node, std::source_location where) + : m_node{&node}, m_where{where} + { + } + + Verification(const Verification&) = delete; + Verification& operator=(const Verification&) = delete; + Verification(Verification&& other) noexcept + : m_node{std::exchange(other.m_node, nullptr)}, m_where{other.m_where} + { + } + + ~Verification() + { + if (!m_node) return; + for (const auto& failure : m_node->VerificationFailures(m_where)) { + QTest::qFail(failure.message.c_str(), failure.where.file_name(), static_cast(failure.where.line())); + } + } + + private: + MockNode* m_node; + std::source_location m_where; + }; + + explicit MockNode(bool strict = false) : m_strict{strict} {} + + [[nodiscard]] Verification VerifyOnExit(std::source_location where = std::source_location::current()) + { + return Verification{*this, where}; + } + + void ExpectExactly(const CallCounter& counter, int expected, std::source_location where = std::source_location::current()) + { + m_call_expectations.push_back(CallExpectation{&counter, counter.load(), expected, expected, where}); + } + + void ExpectNoCalls(const CallCounter& counter, std::source_location where = std::source_location::current()) + { + ExpectExactly(counter, 0, where); + } + + void ExpectAtLeast(const CallCounter& counter, int minimum, std::source_location where = std::source_location::current()) + { + m_call_expectations.push_back(CallExpectation{&counter, counter.load(), minimum, std::nullopt, where}); + } + + void SetPersistentSetting(std::string name, common::SettingsValue value) + { + m_persistent_settings.insert_or_assign(std::move(name), std::move(value)); + get_persistent_setting_fn = [this](const std::string& setting_name) { + const auto it{m_persistent_settings.find(setting_name)}; + return it == m_persistent_settings.end() ? common::SettingsValue{} : it->second; + }; + } + + std::function get_warnings_fn; + std::function app_init_main_fn; + std::function app_shutdown_fn; + std::function start_shutdown_fn; + std::function shutdown_requested_fn; + std::function get_persistent_setting_fn; + std::function update_rw_setting_fn; + std::function force_setting_fn; + std::function map_port_fn; + std::function get_node_count_fn; + std::function get_nodes_stats_fn; + std::function get_banned_fn; + std::function ban_fn; + std::function unban_fn; + std::function disconnect_by_address_fn; + std::function disconnect_by_id_fn; + std::function>()> list_external_signers_fn; + std::function get_total_bytes_recv_fn; + std::function get_total_bytes_sent_fn; + std::function get_mempool_size_fn; + std::function get_mempool_dynamic_usage_fn; + std::function get_mempool_max_usage_fn; + std::function get_header_tip_fn; + std::function get_num_blocks_fn; + std::function()> get_net_local_addresses_fn; + std::function get_last_block_time_fn; + std::function is_initial_block_download_fn; + std::function get_network_active_fn; + std::function get_dust_relay_fee_fn; + std::function broadcast_transaction_fn; + std::function wallet_loader_fn; + std::function(MessageBoxFn)> handle_message_box_fn; + std::function(QuestionFn)> handle_question_fn; + std::function(NotifyNumConnectionsChangedFn)> handle_notify_num_connections_changed_fn; + std::function(NotifyNetworkActiveChangedFn)> handle_notify_network_active_changed_fn; + std::function(NotifyAlertChangedFn)> handle_notify_alert_changed_fn; + std::function(BannedListChangedFn)> handle_banned_list_changed_fn; + std::function(NotifyBlockTipFn)> handle_notify_block_tip_fn; + std::function(NotifyHeaderTipFn)> handle_notify_header_tip_fn; + + struct Calls { + CallCounter getWarnings{"getWarnings"}; + CallCounter appInitMain{"appInitMain"}; + CallCounter appShutdown{"appShutdown"}; + CallCounter startShutdown{"startShutdown"}; + CallCounter shutdownRequested{"shutdownRequested"}; + CallCounter getPersistentSetting{"getPersistentSetting"}; + CallCounter updateRwSetting{"updateRwSetting"}; + CallCounter forceSetting{"forceSetting"}; + CallCounter mapPort{"mapPort"}; + CallCounter getNodeCount{"getNodeCount"}; + CallCounter getNodesStats{"getNodesStats"}; + CallCounter getBanned{"getBanned"}; + CallCounter ban{"ban"}; + CallCounter unban{"unban"}; + CallCounter disconnectByAddress{"disconnectByAddress"}; + CallCounter disconnectById{"disconnectById"}; + CallCounter listExternalSigners{"listExternalSigners"}; + CallCounter getTotalBytesRecv{"getTotalBytesRecv"}; + CallCounter getTotalBytesSent{"getTotalBytesSent"}; + CallCounter getMempoolSize{"getMempoolSize"}; + CallCounter getMempoolDynamicUsage{"getMempoolDynamicUsage"}; + CallCounter getMempoolMaxUsage{"getMempoolMaxUsage"}; + CallCounter getHeaderTip{"getHeaderTip"}; + CallCounter getNumBlocks{"getNumBlocks"}; + CallCounter getNetLocalAddresses{"getNetLocalAddresses"}; + CallCounter getLastBlockTime{"getLastBlockTime"}; + CallCounter isInitialBlockDownload{"isInitialBlockDownload"}; + CallCounter getNetworkActive{"getNetworkActive"}; + CallCounter getDustRelayFee{"getDustRelayFee"}; + CallCounter broadcastTransaction{"broadcastTransaction"}; + CallCounter walletLoader{"walletLoader"}; + CallCounter handleMessageBox{"handleMessageBox"}; + CallCounter handleQuestion{"handleQuestion"}; + CallCounter handleNotifyNumConnectionsChanged{"handleNotifyNumConnectionsChanged"}; + CallCounter handleNotifyNetworkActiveChanged{"handleNotifyNetworkActiveChanged"}; + CallCounter handleNotifyAlertChanged{"handleNotifyAlertChanged"}; + CallCounter handleBannedListChanged{"handleBannedListChanged"}; + CallCounter handleNotifyBlockTip{"handleNotifyBlockTip"}; + CallCounter handleNotifyHeaderTip{"handleNotifyHeaderTip"}; + } calls; + + std::vector> update_rw_setting_arguments; + std::vector> force_setting_arguments; + std::vector map_port_arguments; + + bilingual_str getWarnings() override { return Invoke(calls.getWarnings, get_warnings_fn, bilingual_str{}); } + bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override { return Invoke(calls.appInitMain, app_init_main_fn, false, tip_info); } + void appShutdown() override { InvokeVoid(calls.appShutdown, app_shutdown_fn); } + void startShutdown() override { InvokeVoid(calls.startShutdown, start_shutdown_fn); } + bool shutdownRequested() override { return Invoke(calls.shutdownRequested, shutdown_requested_fn, false); } + common::SettingsValue getPersistentSetting(const std::string& name) override { return Invoke(calls.getPersistentSetting, get_persistent_setting_fn, common::SettingsValue{}, name); } + void updateRwSetting(const std::string& name, const common::SettingsValue& value) override + { + update_rw_setting_arguments.emplace_back(name, value); + InvokeVoid(calls.updateRwSetting, update_rw_setting_fn, name, value); + } + void forceSetting(const std::string& name, const common::SettingsValue& value) override + { + force_setting_arguments.emplace_back(name, value); + InvokeVoid(calls.forceSetting, force_setting_fn, name, value); + } + void mapPort(bool use_natpmp) override + { + map_port_arguments.push_back(use_natpmp); + InvokeVoid(calls.mapPort, map_port_fn, use_natpmp); + } + size_t getNodeCount(ConnectionDirection direction) override { return Invoke(calls.getNodeCount, get_node_count_fn, size_t{0}, direction); } + bool getNodesStats(NodesStats& stats) override { return Invoke(calls.getNodesStats, get_nodes_stats_fn, false, stats); } + bool getBanned(banmap_t& banned) override { return Invoke(calls.getBanned, get_banned_fn, false, banned); } + bool ban(const CNetAddr& net_addr, int64_t ban_time_offset) override { return Invoke(calls.ban, ban_fn, false, net_addr, ban_time_offset); } + bool unban(const CSubNet& subnet) override { return Invoke(calls.unban, unban_fn, false, subnet); } + bool disconnectByAddress(const CNetAddr& net_addr) override { return Invoke(calls.disconnectByAddress, disconnect_by_address_fn, false, net_addr); } + bool disconnectById(NodeId id) override { return Invoke(calls.disconnectById, disconnect_by_id_fn, false, id); } + std::vector> listExternalSigners() override { return Invoke(calls.listExternalSigners, list_external_signers_fn, std::vector>{}); } + int64_t getTotalBytesRecv() override { return Invoke(calls.getTotalBytesRecv, get_total_bytes_recv_fn, int64_t{0}); } + int64_t getTotalBytesSent() override { return Invoke(calls.getTotalBytesSent, get_total_bytes_sent_fn, int64_t{0}); } + size_t getMempoolSize() override { return Invoke(calls.getMempoolSize, get_mempool_size_fn, size_t{0}); } + size_t getMempoolDynamicUsage() override { return Invoke(calls.getMempoolDynamicUsage, get_mempool_dynamic_usage_fn, size_t{0}); } + size_t getMempoolMaxUsage() override { return Invoke(calls.getMempoolMaxUsage, get_mempool_max_usage_fn, size_t{0}); } + bool getHeaderTip(int& height, int64_t& block_time) override { return Invoke(calls.getHeaderTip, get_header_tip_fn, false, height, block_time); } + int getNumBlocks() override { return Invoke(calls.getNumBlocks, get_num_blocks_fn, 0); } + std::map getNetLocalAddresses() override { return Invoke(calls.getNetLocalAddresses, get_net_local_addresses_fn, std::map{}); } + int64_t getLastBlockTime() override { return Invoke(calls.getLastBlockTime, get_last_block_time_fn, int64_t{0}); } + bool isInitialBlockDownload() override { return Invoke(calls.isInitialBlockDownload, is_initial_block_download_fn, false); } + bool getNetworkActive() override { return Invoke(calls.getNetworkActive, get_network_active_fn, false); } + CFeeRate getDustRelayFee() override { return Invoke(calls.getDustRelayFee, get_dust_relay_fee_fn, CFeeRate{}); } + node::TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override { return Invoke(calls.broadcastTransaction, broadcast_transaction_fn, node::TransactionError{}, std::move(tx), max_tx_fee, err_string); } + interfaces::WalletLoader& walletLoader() override + { + ++calls.walletLoader; + if (wallet_loader_fn) return wallet_loader_fn(); + UnexpectedCall(calls.walletLoader.Name()); + return FallbackWalletLoader(); + } + std::unique_ptr handleMessageBox(MessageBoxFn fn) override { return Invoke(calls.handleMessageBox, handle_message_box_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleQuestion(QuestionFn fn) override { return Invoke(calls.handleQuestion, handle_question_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) override { return Invoke(calls.handleNotifyNumConnectionsChanged, handle_notify_num_connections_changed_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) override { return Invoke(calls.handleNotifyNetworkActiveChanged, handle_notify_network_active_changed_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleNotifyAlertChanged(NotifyAlertChangedFn fn) override { return Invoke(calls.handleNotifyAlertChanged, handle_notify_alert_changed_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleBannedListChanged(BannedListChangedFn fn) override { return Invoke(calls.handleBannedListChanged, handle_banned_list_changed_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleNotifyBlockTip(NotifyBlockTipFn fn) override { return Invoke(calls.handleNotifyBlockTip, handle_notify_block_tip_fn, std::unique_ptr{}, std::move(fn)); } + std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn fn) override { return Invoke(calls.handleNotifyHeaderTip, handle_notify_header_tip_fn, std::unique_ptr{}, std::move(fn)); } + +protected: + void UnhandledCall(const char* name) override { UnexpectedCall(name); } + +private: + struct CallExpectation { + const CallCounter* counter; + int baseline; + int minimum; + std::optional maximum; + std::source_location where; + }; + + struct VerificationFailure { + std::string message; + std::source_location where; + }; + + std::vector VerificationFailures(std::source_location unexpected_where) const + { + std::vector failures; + { + std::lock_guard lock{m_unexpected_mutex}; + if (!m_unexpected_calls.empty()) { + std::ostringstream message; + message << "Unexpected Node call(s):"; + for (const std::string& call : m_unexpected_calls) + message << "\n " << call; + failures.push_back({message.str(), unexpected_where}); + } + } + for (const CallExpectation& expectation : m_call_expectations) { + const int actual{expectation.counter->load() - expectation.baseline}; + if (actual < expectation.minimum || (expectation.maximum && actual > *expectation.maximum)) { + std::ostringstream message; + message << "Expected " << expectation.counter->Name() << " calls to be "; + if (expectation.maximum && expectation.minimum == *expectation.maximum) { + message << expectation.minimum; + } else if (expectation.maximum) { + message << "between " << expectation.minimum << " and " << *expectation.maximum; + } else { + message << "at least " << expectation.minimum; + } + message << ", actual " << actual; + failures.push_back({message.str(), expectation.where}); + } + } + return failures; + } + + template + R Invoke(CallCounter& counter, const std::function& fn, R fallback, CallArgs&&... args) + { + ++counter; + if (fn) return fn(std::forward(args)...); + UnexpectedCall(counter.Name()); + return std::move(fallback); + } + + template + void InvokeVoid(CallCounter& counter, const std::function& fn, CallArgs&&... args) + { + ++counter; + if (fn) { + fn(std::forward(args)...); + } else { + UnexpectedCall(counter.Name()); + } + } + + void UnexpectedCall(std::string_view name) + { + if (!m_strict) return; + std::lock_guard lock{m_unexpected_mutex}; + m_unexpected_calls.emplace_back(name); + } + + const bool m_strict; + mutable std::mutex m_unexpected_mutex; + std::vector m_unexpected_calls; + std::vector m_call_expectations; + std::map m_persistent_settings; +}; + +class StrictMockNode : public MockNode { public: - MOCK_METHOD(void, initLogging, (), (override)); - MOCK_METHOD(void, initParameterInteraction, (), (override)); - MOCK_METHOD(bilingual_str, getWarnings, (), (override)); - MOCK_METHOD(int, getExitStatus, (), (override)); - MOCK_METHOD(BCLog::CategoryMask, getLogCategories, (), (override)); - MOCK_METHOD(bool, baseInitialize, (), (override)); - MOCK_METHOD(bool, appInitMain, (interfaces::BlockAndHeaderTipInfo*), (override)); - MOCK_METHOD(void, appShutdown, (), (override)); - MOCK_METHOD(void, startShutdown, (), (override)); - MOCK_METHOD(bool, shutdownRequested, (), (override)); - MOCK_METHOD(bool, isSettingIgnored, (const std::string&), (override)); - MOCK_METHOD(common::SettingsValue, getPersistentSetting, (const std::string&), (override)); - MOCK_METHOD(void, updateRwSetting, (const std::string&, const common::SettingsValue&), (override)); - MOCK_METHOD(void, forceSetting, (const std::string&, const common::SettingsValue&), (override)); - MOCK_METHOD(void, resetSettings, (), (override)); - MOCK_METHOD(void, mapPort, (bool), (override)); - MOCK_METHOD((std::optional), getProxy, (Network), (override)); - MOCK_METHOD(size_t, getNodeCount, (ConnectionDirection), (override)); - MOCK_METHOD(bool, getNodesStats, (NodesStats&), (override)); - MOCK_METHOD(bool, getBanned, (banmap_t&), (override)); - MOCK_METHOD(bool, ban, (const CNetAddr&, int64_t), (override)); - MOCK_METHOD(bool, unban, (const CSubNet&), (override)); - MOCK_METHOD(bool, disconnectByAddress, (const CNetAddr&), (override)); - MOCK_METHOD(bool, disconnectById, (NodeId), (override)); - MOCK_METHOD((std::vector>), listExternalSigners, (), (override)); - MOCK_METHOD(int64_t, getTotalBytesRecv, (), (override)); - MOCK_METHOD(int64_t, getTotalBytesSent, (), (override)); - MOCK_METHOD(size_t, getMempoolSize, (), (override)); - MOCK_METHOD(size_t, getMempoolDynamicUsage, (), (override)); - MOCK_METHOD(size_t, getMempoolMaxUsage, (), (override)); - MOCK_METHOD(bool, getHeaderTip, (int&, int64_t&), (override)); - MOCK_METHOD(int, getNumBlocks, (), (override)); - MOCK_METHOD((std::map), getNetLocalAddresses, (), (override)); - MOCK_METHOD(uint256, getBestBlockHash, (), (override)); - MOCK_METHOD(int64_t, getLastBlockTime, (), (override)); - MOCK_METHOD(double, getVerificationProgress, (), (override)); - MOCK_METHOD(bool, isInitialBlockDownload, (), (override)); - MOCK_METHOD(bool, isLoadingBlocks, (), (override)); - MOCK_METHOD(void, setNetworkActive, (bool), (override)); - MOCK_METHOD(bool, getNetworkActive, (), (override)); - MOCK_METHOD(CFeeRate, getDustRelayFee, (), (override)); - MOCK_METHOD(UniValue, executeRpc, (const std::string&, const UniValue&, const std::string&), (override)); - MOCK_METHOD((std::vector), listRpcCommands, (), (override)); - MOCK_METHOD((std::optional), getUnspentOutput, (const COutPoint&), (override)); - MOCK_METHOD(node::TransactionError, broadcastTransaction, (CTransactionRef, CAmount, std::string&), (override)); - MOCK_METHOD(interfaces::WalletLoader&, walletLoader, (), (override)); - MOCK_METHOD((std::unique_ptr), handleInitMessage, (InitMessageFn), (override)); - MOCK_METHOD((std::unique_ptr), handleMessageBox, (MessageBoxFn), (override)); - MOCK_METHOD((std::unique_ptr), handleQuestion, (QuestionFn), (override)); - MOCK_METHOD((std::unique_ptr), handleShowProgress, (ShowProgressFn), (override)); - MOCK_METHOD((std::unique_ptr), handleInitWallet, (InitWalletFn), (override)); - MOCK_METHOD((std::unique_ptr), handleNotifyNumConnectionsChanged, (NotifyNumConnectionsChangedFn), (override)); - MOCK_METHOD((std::unique_ptr), handleNotifyNetworkActiveChanged, (NotifyNetworkActiveChangedFn), (override)); - MOCK_METHOD((std::unique_ptr), handleNotifyAlertChanged, (NotifyAlertChangedFn), (override)); - MOCK_METHOD((std::unique_ptr), handleBannedListChanged, (BannedListChangedFn), (override)); - MOCK_METHOD((std::unique_ptr), handleNotifyBlockTip, (NotifyBlockTipFn), (override)); - MOCK_METHOD((std::unique_ptr), handleNotifyHeaderTip, (NotifyHeaderTipFn), (override)); - MOCK_METHOD(node::NodeContext*, context, (), (override)); - MOCK_METHOD(void, setContext, (node::NodeContext*), (override)); + StrictMockNode() : MockNode{true} {} }; #endif // BITCOIN_QML_TEST_MOCKS_MOCKNODE_H diff --git a/test/mocks/mockwallet.h b/test/mocks/mockwallet.h index 52eefba232..6f27edc809 100644 --- a/test/mocks/mockwallet.h +++ b/test/mocks/mockwallet.h @@ -5,26 +5,18 @@ #ifndef BITCOIN_QML_TEST_MOCKS_MOCKWALLET_H #define BITCOIN_QML_TEST_MOCKS_MOCKWALLET_H -#ifdef Assert -#pragma push_macro("Assert") -#undef Assert -#define BITCOIN_QML_RESTORE_ASSERT_MACRO -#endif - -#include - -#ifdef BITCOIN_QML_RESTORE_ASSERT_MACRO -#pragma pop_macro("Assert") -#undef BITCOIN_QML_RESTORE_ASSERT_MACRO -#endif - #include #include +#include #include #include #include +#include + #include +#include +#include class StubWallet : public interfaces::Wallet { @@ -96,14 +88,97 @@ class StubWallet : public interfaces::Wallet std::unique_ptr handleCanGetAddressesChanged(CanGetAddressesChangedFn) override { return {}; } }; +/** + * Configurable wallet test double with permissive defaults. + * + * Unconfigured methods return their StubWallet values. Tests register explicit + * expectations for calls that are part of the behavior under test. + */ class MockWallet : public StubWallet { public: - std::function(const std::vector&, const wallet::CCoinControl&, bool, int&, CAmount&)> createTransactionHandler; + class Verification + { + public: + explicit Verification(MockWallet& wallet) + : m_wallet{&wallet} + { + } + + Verification(const Verification&) = delete; + Verification& operator=(const Verification&) = delete; + Verification(Verification&& other) noexcept + : m_wallet{std::exchange(other.m_wallet, nullptr)} + { + } + + ~Verification() + { + if (!m_wallet) return; + for (const auto& failure : m_wallet->VerificationFailures()) { + QTest::qFail(failure.message.c_str(), failure.where.file_name(), static_cast(failure.where.line())); + } + } + + private: + MockWallet* m_wallet; + }; + + [[nodiscard]] Verification VerifyOnExit() + { + return Verification{*this}; + } + + void ExpectExactly(const CallCounter& counter, int expected, std::source_location where = std::source_location::current()) + { + m_call_expectations.push_back(CallExpectation{&counter, counter.load(), expected, expected, where}); + } + + void ExpectNoCalls(const CallCounter& counter, std::source_location where = std::source_location::current()) + { + ExpectExactly(counter, 0, where); + } + + void ExpectAtLeast(const CallCounter& counter, int minimum, std::source_location where = std::source_location::current()) + { + m_call_expectations.push_back(CallExpectation{&counter, counter.load(), minimum, std::nullopt, where}); + } + + std::function(const std::vector&, const wallet::CCoinControl&, bool, int&, CAmount&)> create_transaction_fn; + std::function get_new_destination_fn; + std::function()> get_wallet_txs_fn; + std::function get_balance_fn; + std::function get_available_balance_fn; + std::function get_required_fee_fn; + std::function list_coins_fn; + std::function get_default_address_type_fn; + std::function(TransactionChangedFn)> handle_transaction_changed_fn; + std::function transaction_can_be_bumped_fn; + std::function&, CAmount&, CAmount&, CMutableTransaction&)> create_bump_transaction_fn; + std::function sign_bump_transaction_fn; + std::function&, Txid&)> commit_bump_transaction_fn; + + struct Calls { + CallCounter getNewDestination{"getNewDestination"}; + CallCounter createTransaction{"createTransaction"}; + CallCounter getWalletTxs{"getWalletTxs"}; + CallCounter getBalance{"getBalance"}; + CallCounter getAvailableBalance{"getAvailableBalance"}; + CallCounter getRequiredFee{"getRequiredFee"}; + CallCounter listCoins{"listCoins"}; + CallCounter getDefaultAddressType{"getDefaultAddressType"}; + CallCounter handleTransactionChanged{"handleTransactionChanged"}; + CallCounter transactionCanBeBumped{"transactionCanBeBumped"}; + CallCounter createBumpTransaction{"createBumpTransaction"}; + CallCounter signBumpTransaction{"signBumpTransaction"}; + CallCounter commitBumpTransaction{"commitBumpTransaction"}; + } calls; util::Result getNewDestination(const OutputType type, const std::string& label) override { - return getNewDestinationValue(type, label); + ++calls.getNewDestination; + if (get_new_destination_fn) return get_new_destination_fn(type, label); + return util::Error{Untranslated("no get_new_destination_fn installed")}; } util::Result createTransaction(const std::vector& recipients, @@ -111,10 +186,11 @@ class MockWallet : public StubWallet bool sign, std::optional) override { - if (createTransactionHandler) { + ++calls.createTransaction; + if (create_transaction_fn) { int change_pos{-1}; CAmount fee{0}; - auto result = createTransactionHandler(recipients, coin_control, sign, change_pos, fee); + auto result = create_transaction_fn(recipients, coin_control, sign, change_pos, fee); if (!result) { return util::Error{util::ErrorString(result)}; } @@ -124,21 +200,113 @@ class MockWallet : public StubWallet change_pos >= 0 ? std::optional{static_cast(change_pos)} : std::nullopt, FeeCalculation{}}; } - return util::Error{Untranslated("no createTransactionHandler installed")}; - } - - MOCK_METHOD(CTxDestination, getNewDestinationValue, (OutputType, const std::string&)); - MOCK_METHOD((std::set), getWalletTxs, (), (override)); - MOCK_METHOD(CAmount, getBalance, (), (override)); - MOCK_METHOD(CAmount, getAvailableBalance, (const wallet::CCoinControl&), (override)); - MOCK_METHOD(CAmount, getRequiredFee, (unsigned int), (override)); - MOCK_METHOD(CoinsList, listCoins, (), (override)); - MOCK_METHOD(OutputType, getDefaultAddressType, (), (override)); - MOCK_METHOD((std::unique_ptr), handleTransactionChanged, (TransactionChangedFn), (override)); - MOCK_METHOD(bool, transactionCanBeBumped, (const Txid&), (override)); - MOCK_METHOD(bool, createBumpTransaction, (const Txid&, const wallet::CCoinControl&, std::vector&, CAmount&, CAmount&, CMutableTransaction&), (override)); - MOCK_METHOD(bool, signBumpTransaction, (CMutableTransaction&), (override)); - MOCK_METHOD(bool, commitBumpTransaction, (const Txid&, CMutableTransaction&&, std::vector&, Txid&), (override)); + return util::Error{Untranslated("no create_transaction_fn installed")}; + } + + std::set getWalletTxs() override + { + ++calls.getWalletTxs; + return get_wallet_txs_fn ? get_wallet_txs_fn() : std::set{}; + } + + CAmount getBalance() override + { + ++calls.getBalance; + return get_balance_fn ? get_balance_fn() : 0; + } + + CAmount getAvailableBalance(const wallet::CCoinControl& coin_control) override + { + ++calls.getAvailableBalance; + return get_available_balance_fn ? get_available_balance_fn(coin_control) : 0; + } + + CAmount getRequiredFee(unsigned int tx_bytes) override + { + ++calls.getRequiredFee; + return get_required_fee_fn ? get_required_fee_fn(tx_bytes) : 0; + } + + CoinsList listCoins() override + { + ++calls.listCoins; + return list_coins_fn ? list_coins_fn() : CoinsList{}; + } + + OutputType getDefaultAddressType() override + { + ++calls.getDefaultAddressType; + return get_default_address_type_fn ? get_default_address_type_fn() : OutputType::BECH32; + } + + std::unique_ptr handleTransactionChanged(TransactionChangedFn fn) override + { + ++calls.handleTransactionChanged; + return handle_transaction_changed_fn ? handle_transaction_changed_fn(std::move(fn)) : nullptr; + } + + bool transactionCanBeBumped(const Txid& txid) override + { + ++calls.transactionCanBeBumped; + return transaction_can_be_bumped_fn ? transaction_can_be_bumped_fn(txid) : false; + } + + bool createBumpTransaction(const Txid& txid, const wallet::CCoinControl& coin_control, std::vector& errors, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) override + { + ++calls.createBumpTransaction; + return create_bump_transaction_fn ? create_bump_transaction_fn(txid, coin_control, errors, old_fee, new_fee, mtx) : false; + } + + bool signBumpTransaction(CMutableTransaction& mtx) override + { + ++calls.signBumpTransaction; + return sign_bump_transaction_fn ? sign_bump_transaction_fn(mtx) : false; + } + + bool commitBumpTransaction(const Txid& txid, CMutableTransaction&& mtx, std::vector& errors, Txid& bumped_txid) override + { + ++calls.commitBumpTransaction; + return commit_bump_transaction_fn ? commit_bump_transaction_fn(txid, std::move(mtx), errors, bumped_txid) : false; + } + +private: + struct CallExpectation { + const CallCounter* counter; + int baseline; + int minimum; + std::optional maximum; + std::source_location where; + }; + + struct VerificationFailure { + std::string message; + std::source_location where; + }; + + std::vector VerificationFailures() const + { + std::vector failures; + for (const CallExpectation& expectation : m_call_expectations) { + const int actual{expectation.counter->load() - expectation.baseline}; + if (actual < expectation.minimum || (expectation.maximum && actual > *expectation.maximum)) { + std::ostringstream message; + message << "Expected " << expectation.counter->Name() << " calls to be "; + if (expectation.maximum && expectation.minimum == *expectation.maximum) { + message << expectation.minimum; + } else if (expectation.maximum) { + message << "between " << expectation.minimum << " and " << *expectation.maximum; + } else { + message << "at least " << expectation.minimum; + } + message << ", actual " << actual; + failures.push_back({message.str(), expectation.where}); + } + } + return failures; + } + + std::vector m_call_expectations; }; #endif // BITCOIN_QML_TEST_MOCKS_MOCKWALLET_H diff --git a/test/mocks/stubnode.h b/test/mocks/stubnode.h new file mode 100644 index 0000000000..e128b41966 --- /dev/null +++ b/test/mocks/stubnode.h @@ -0,0 +1,331 @@ +// Copyright (c) 2026 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QML_TEST_MOCKS_STUBNODE_H +#define BITCOIN_QML_TEST_MOCKS_STUBNODE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +class StubWalletLoader : public interfaces::WalletLoader +{ +public: + void registerRpcs() override {} + bool verify() override { return false; } + bool load() override { return false; } + void start(CScheduler&) override {} + void stop() override {} + void setMockTime(int64_t) override {} + void schedulerMockForward(std::chrono::seconds) override {} + + util::Result> createWallet( + const std::string&, const SecureString&, uint64_t, std::vector&) override + { + return util::Error{}; + } + + util::Result> loadWallet( + const std::string&, std::vector&) override + { + return util::Error{}; + } + + std::string getWalletDir() override { return {}; } + + util::Result> restoreWallet( + const fs::path&, const std::string&, std::vector&, bool) override + { + return util::Error{}; + } + + util::Result migrateWallet( + const std::string&, const SecureString&) override + { + return util::Error{}; + } + + bool isEncrypted(const std::string&) override { return false; } + std::vector> listWalletDir() override { return {}; } + std::vector> getWallets() override { return {}; } + std::unique_ptr handleLoadWallet(LoadWalletFn) override { return {}; } +}; + +class StubNode : public interfaces::Node +{ +public: + void initLogging() override { UnhandledCall("initLogging"); } + void initParameterInteraction() override { UnhandledCall("initParameterInteraction"); } + bilingual_str getWarnings() override + { + UnhandledCall("getWarnings"); + return {}; + } + int getExitStatus() override + { + UnhandledCall("getExitStatus"); + return 0; + } + BCLog::CategoryMask getLogCategories() override + { + UnhandledCall("getLogCategories"); + return {}; + } + bool baseInitialize() override + { + UnhandledCall("baseInitialize"); + return false; + } + bool appInitMain(interfaces::BlockAndHeaderTipInfo*) override + { + UnhandledCall("appInitMain"); + return false; + } + void appShutdown() override { UnhandledCall("appShutdown"); } + void startShutdown() override { UnhandledCall("startShutdown"); } + bool shutdownRequested() override + { + UnhandledCall("shutdownRequested"); + return false; + } + bool isSettingIgnored(const std::string&) override + { + UnhandledCall("isSettingIgnored"); + return false; + } + common::SettingsValue getPersistentSetting(const std::string&) override + { + UnhandledCall("getPersistentSetting"); + return {}; + } + void updateRwSetting(const std::string&, const common::SettingsValue&) override { UnhandledCall("updateRwSetting"); } + void forceSetting(const std::string&, const common::SettingsValue&) override { UnhandledCall("forceSetting"); } + void resetSettings() override { UnhandledCall("resetSettings"); } + void mapPort(bool) override { UnhandledCall("mapPort"); } + std::optional getProxy(Network) override + { + UnhandledCall("getProxy"); + return std::nullopt; + } + size_t getNodeCount(ConnectionDirection) override + { + UnhandledCall("getNodeCount"); + return 0; + } + bool getNodesStats(NodesStats&) override + { + UnhandledCall("getNodesStats"); + return false; + } + bool getBanned(banmap_t&) override + { + UnhandledCall("getBanned"); + return false; + } + bool ban(const CNetAddr&, int64_t) override + { + UnhandledCall("ban"); + return false; + } + bool unban(const CSubNet&) override + { + UnhandledCall("unban"); + return false; + } + bool disconnectByAddress(const CNetAddr&) override + { + UnhandledCall("disconnectByAddress"); + return false; + } + bool disconnectById(NodeId) override + { + UnhandledCall("disconnectById"); + return false; + } + std::vector> listExternalSigners() override + { + UnhandledCall("listExternalSigners"); + return {}; + } + int64_t getTotalBytesRecv() override + { + UnhandledCall("getTotalBytesRecv"); + return 0; + } + int64_t getTotalBytesSent() override + { + UnhandledCall("getTotalBytesSent"); + return 0; + } + size_t getMempoolSize() override + { + UnhandledCall("getMempoolSize"); + return 0; + } + size_t getMempoolDynamicUsage() override + { + UnhandledCall("getMempoolDynamicUsage"); + return 0; + } + size_t getMempoolMaxUsage() override + { + UnhandledCall("getMempoolMaxUsage"); + return 0; + } + bool getHeaderTip(int&, int64_t&) override + { + UnhandledCall("getHeaderTip"); + return false; + } + int getNumBlocks() override + { + UnhandledCall("getNumBlocks"); + return 0; + } + std::map getNetLocalAddresses() override + { + UnhandledCall("getNetLocalAddresses"); + return {}; + } + uint256 getBestBlockHash() override + { + UnhandledCall("getBestBlockHash"); + return {}; + } + int64_t getLastBlockTime() override + { + UnhandledCall("getLastBlockTime"); + return 0; + } + double getVerificationProgress() override + { + UnhandledCall("getVerificationProgress"); + return 0.0; + } + bool isInitialBlockDownload() override + { + UnhandledCall("isInitialBlockDownload"); + return false; + } + bool isLoadingBlocks() override + { + UnhandledCall("isLoadingBlocks"); + return false; + } + void setNetworkActive(bool) override { UnhandledCall("setNetworkActive"); } + bool getNetworkActive() override + { + UnhandledCall("getNetworkActive"); + return false; + } + CFeeRate getDustRelayFee() override + { + UnhandledCall("getDustRelayFee"); + return {}; + } + UniValue executeRpc(const std::string&, const UniValue&, const std::string&) override + { + UnhandledCall("executeRpc"); + return {}; + } + std::vector listRpcCommands() override + { + UnhandledCall("listRpcCommands"); + return {}; + } + std::optional getUnspentOutput(const COutPoint&) override + { + UnhandledCall("getUnspentOutput"); + return std::nullopt; + } + node::TransactionError broadcastTransaction(CTransactionRef, CAmount, std::string&) override + { + UnhandledCall("broadcastTransaction"); + return {}; + } + interfaces::WalletLoader& walletLoader() override + { + UnhandledCall("walletLoader"); + return m_wallet_loader; + } + std::unique_ptr handleInitMessage(InitMessageFn) override + { + UnhandledCall("handleInitMessage"); + return {}; + } + std::unique_ptr handleMessageBox(MessageBoxFn) override + { + UnhandledCall("handleMessageBox"); + return {}; + } + std::unique_ptr handleQuestion(QuestionFn) override + { + UnhandledCall("handleQuestion"); + return {}; + } + std::unique_ptr handleShowProgress(ShowProgressFn) override + { + UnhandledCall("handleShowProgress"); + return {}; + } + std::unique_ptr handleInitWallet(InitWalletFn) override + { + UnhandledCall("handleInitWallet"); + return {}; + } + std::unique_ptr handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn) override + { + UnhandledCall("handleNotifyNumConnectionsChanged"); + return {}; + } + std::unique_ptr handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn) override + { + UnhandledCall("handleNotifyNetworkActiveChanged"); + return {}; + } + std::unique_ptr handleNotifyAlertChanged(NotifyAlertChangedFn) override + { + UnhandledCall("handleNotifyAlertChanged"); + return {}; + } + std::unique_ptr handleBannedListChanged(BannedListChangedFn) override + { + UnhandledCall("handleBannedListChanged"); + return {}; + } + std::unique_ptr handleNotifyBlockTip(NotifyBlockTipFn) override + { + UnhandledCall("handleNotifyBlockTip"); + return {}; + } + std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn) override + { + UnhandledCall("handleNotifyHeaderTip"); + return {}; + } + +protected: + virtual void UnhandledCall(const char*) {} + interfaces::WalletLoader& FallbackWalletLoader() { return m_wallet_loader; } + +private: + StubWalletLoader m_wallet_loader; +}; + +#endif // BITCOIN_QML_TEST_MOCKS_STUBNODE_H diff --git a/test/test_banlistmodel.cpp b/test/test_banlistmodel.cpp index 59ac0b7ba4..4dbf6e1016 100644 --- a/test/test_banlistmodel.cpp +++ b/test/test_banlistmodel.cpp @@ -47,28 +47,24 @@ private Q_SLOTS: void BanListModelTests::refreshPopulatesRolesAndRows() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - banmap_t banned; const CSubNet subnet_a = ParseSubnet("10.0.0.0/8"); const CSubNet subnet_b = ParseSubnet("127.0.0.1/32"); banned.emplace(subnet_a, MakeBanEntry(1'900'000'000)); banned.emplace(subnet_b, MakeBanEntry(2'000'000'000)); - NiceMock node; - EXPECT_CALL(node, getBanned(_)) - .Times(1) - .WillOnce(DoAll(SetArgReferee<0>(banned), Return(true))); + MockNode node; + node.get_banned_fn = [&](banmap_t& result) { + result = banned; + return true; + }; BanListModel model{node, nullptr}; QSignalSpy count_spy(&model, &BanListModel::countChanged); model.refresh(); + QCOMPARE(node.calls.getBanned.load(), 1); QCOMPARE(model.count(), 2); QCOMPARE(model.rowCount(), 2); QCOMPARE(model.rowCount(model.index(0, 0)), 0); @@ -97,85 +93,74 @@ void BanListModelTests::refreshPopulatesRolesAndRows() void BanListModelTests::unbanAtTargetsSelectedSubnet() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - using ::testing::Truly; - banmap_t banned; const CSubNet subnet = ParseSubnet("10.0.0.0/8"); banned.emplace(subnet, MakeBanEntry(1'900'000'000)); - NiceMock node; - EXPECT_CALL(node, getBanned(_)) - .Times(1) - .WillOnce(DoAll(SetArgReferee<0>(banned), Return(true))); + MockNode node; + node.get_banned_fn = [&](banmap_t& result) { + result = banned; + return true; + }; BanListModel model{node, nullptr}; model.refresh(); - EXPECT_CALL(node, unban(Truly([&](const CSubNet& value) { - return value.ToString() == subnet.ToString(); - }))).Times(1).WillOnce(Return(true)); + bool received_expected_subnet{false}; + node.unban_fn = [&](const CSubNet& value) { + received_expected_subnet = value.ToString() == subnet.ToString(); + return true; + }; QVERIFY(model.unbanAt(0)); + QCOMPARE(node.calls.getBanned.load(), 1); + QCOMPARE(node.calls.unban.load(), 1); + QVERIFY(received_expected_subnet); } void BanListModelTests::unbanAtReturnsFalseWhenNodeRejectsSubnet() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - banmap_t banned; banned.emplace(ParseSubnet("10.0.0.0/8"), MakeBanEntry(1'900'000'000)); - NiceMock node; - EXPECT_CALL(node, getBanned(_)) - .Times(1) - .WillOnce(DoAll(SetArgReferee<0>(banned), Return(true))); + MockNode node; + node.get_banned_fn = [&](banmap_t& result) { + result = banned; + return true; + }; BanListModel model{node, nullptr}; model.refresh(); - EXPECT_CALL(node, unban(_)).Times(1).WillOnce(Return(false)); + node.unban_fn = [](const CSubNet&) { return false; }; QVERIFY(!model.unbanAt(0)); + QCOMPARE(node.calls.getBanned.load(), 1); + QCOMPARE(node.calls.unban.load(), 1); } void BanListModelTests::unbanAtIgnoresInvalidRows() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - banmap_t banned; banned.emplace(ParseSubnet("10.0.0.0/8"), MakeBanEntry(1'900'000'000)); - NiceMock node; - EXPECT_CALL(node, getBanned(_)) - .Times(1) - .WillOnce(DoAll(SetArgReferee<0>(banned), Return(true))); + MockNode node; + node.get_banned_fn = [&](banmap_t& result) { + result = banned; + return true; + }; BanListModel model{node, nullptr}; model.refresh(); - EXPECT_CALL(node, unban(_)).Times(0); QVERIFY(!model.unbanAt(-1)); QVERIFY(!model.unbanAt(42)); + QCOMPARE(node.calls.getBanned.load(), 1); + QCOMPARE(node.calls.unban.load(), 0); } -int RunBanListModelTests(int argc, char* argv[]) -{ - BanListModelTests tests; - return QTest::qExec(&tests, argc, argv); -} - -#ifndef BITCOINQML_NO_TEST_MAIN +#ifdef BITCOINQML_NO_TEST_MAIN +#include +BITCOINQML_REGISTER_QT_TEST(BanListModelTests) +#else QTEST_MAIN(BanListModelTests) #endif #include "test_banlistmodel.moc" diff --git a/test/test_blockclockdial.cpp b/test/test_blockclockdial.cpp index e708d58be1..3f48c9e4c8 100644 --- a/test/test_blockclockdial.cpp +++ b/test/test_blockclockdial.cpp @@ -177,13 +177,10 @@ void BlockClockDialTests::connectingDelayControlsInitialAnimation() QVERIFY(CountConfirmationPixels(RenderDial(immediate_dial)) > 0); } -int RunBlockClockDialTests(int argc, char* argv[]) -{ - BlockClockDialTests tests; - return QTest::qExec(&tests, argc, argv); -} - -#ifndef BITCOINQML_NO_TEST_MAIN +#ifdef BITCOINQML_NO_TEST_MAIN +#include +BITCOINQML_REGISTER_QT_TEST(BlockClockDialTests) +#else QTEST_MAIN(BlockClockDialTests) #endif #include "test_blockclockdial.moc" diff --git a/test/test_bumptransactionmodel.cpp b/test/test_bumptransactionmodel.cpp index 98897a01bb..135faf9856 100644 --- a/test/test_bumptransactionmodel.cpp +++ b/test/test_bumptransactionmodel.cpp @@ -12,16 +12,12 @@ #include namespace { -using ::testing::Invoke; -using ::testing::NiceMock; -using ::testing::Return; - const auto TEST_TXID = QStringLiteral("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); -std::unique_ptr> MakeMockWallet() +std::unique_ptr MakeBumpableWallet() { - auto wallet = std::make_unique>(); - ON_CALL(*wallet, transactionCanBeBumped(testing::_)).WillByDefault(Return(true)); + auto wallet = std::make_unique(); + wallet->transaction_can_be_bumped_fn = [](const Txid&) { return true; }; return wallet; } } // namespace @@ -33,21 +29,20 @@ class BumpTransactionModelTests : public QObject private Q_SLOTS: void stateIsIdleByDefault() { - auto wallet = MakeMockWallet(); + auto wallet = MakeBumpableWallet(); BumpTransactionModel model(wallet.get()); QCOMPARE(model.state(), BumpTransactionModel::Idle); } void prepareFeeBump_transitionsToNeedsConfirmation() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector&, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { - old_fee = 500; - new_fee = 1000; - return true; - })); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector&, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { + old_fee = 500; + new_fee = 1000; + return true; + }; BumpTransactionModel model(wallet.get()); QSignalSpy stateSpy(&model, &BumpTransactionModel::stateChanged); @@ -60,14 +55,13 @@ private Q_SLOTS: void prepareFeeBump_populatesFees() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector&, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { - old_fee = 500; - new_fee = 1000; - return true; - })); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector&, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { + old_fee = 500; + new_fee = 1000; + return true; + }; BumpTransactionModel model(wallet.get()); model.prepareFeeBump(TEST_TXID, 1); @@ -79,13 +73,12 @@ private Q_SLOTS: void prepareFeeBump_failureSetsErrorState() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector& errors, - CAmount&, CAmount&, CMutableTransaction&) { - errors.emplace_back(Untranslated("insufficient fee")); - return false; - })); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector& errors, + CAmount&, CAmount&, CMutableTransaction&) { + errors.emplace_back(Untranslated("insufficient fee")); + return false; + }; BumpTransactionModel model(wallet.get()); model.prepareFeeBump(TEST_TXID, 1); @@ -96,7 +89,7 @@ private Q_SLOTS: void prepareFeeBump_invalidTxidFails() { - auto wallet = MakeMockWallet(); + auto wallet = MakeBumpableWallet(); BumpTransactionModel model(wallet.get()); model.prepareFeeBump(QStringLiteral("not-a-txid"), 1); @@ -115,20 +108,18 @@ private Q_SLOTS: void confirmFeeBump_signsAndCommits() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector&, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { - old_fee = 500; - new_fee = 1000; - return true; - })); - ON_CALL(*wallet, signBumpTransaction(testing::_)).WillByDefault(Return(true)); - ON_CALL(*wallet, commitBumpTransaction(testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, CMutableTransaction&&, std::vector&, Txid& bumped_txid) { - bumped_txid = Txid::FromUint256(*uint256::FromHex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")); - return true; - })); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector&, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { + old_fee = 500; + new_fee = 1000; + return true; + }; + wallet->sign_bump_transaction_fn = [](CMutableTransaction&) { return true; }; + wallet->commit_bump_transaction_fn = [](const Txid&, CMutableTransaction&&, std::vector&, Txid& bumped_txid) { + bumped_txid = Txid::FromUint256(*uint256::FromHex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")); + return true; + }; BumpTransactionModel model(wallet.get()); model.prepareFeeBump(TEST_TXID, 1); @@ -142,16 +133,14 @@ private Q_SLOTS: void confirmFeeBump_signFailureDoesNotCommit() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector&, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { - old_fee = 500; - new_fee = 1000; - return true; - })); - ON_CALL(*wallet, signBumpTransaction(testing::_)).WillByDefault(Return(false)); - EXPECT_CALL(*wallet, commitBumpTransaction(testing::_, testing::_, testing::_, testing::_)).Times(0); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector&, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { + old_fee = 500; + new_fee = 1000; + return true; + }; + wallet->sign_bump_transaction_fn = [](CMutableTransaction&) { return false; }; BumpTransactionModel model(wallet.get()); model.prepareFeeBump(TEST_TXID, 1); @@ -159,11 +148,12 @@ private Q_SLOTS: model.confirmFeeBump(); QCOMPARE(model.state(), BumpTransactionModel::Failed); + QCOMPARE(wallet->calls.commitBumpTransaction.load(), 0); } void confirmFeeBump_rejectsWhenNotReady() { - auto wallet = MakeMockWallet(); + auto wallet = MakeBumpableWallet(); BumpTransactionModel model(wallet.get()); model.confirmFeeBump(); @@ -173,19 +163,18 @@ private Q_SLOTS: void confirmFeeBump_rechecksEligibility() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector&, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { - old_fee = 500; - new_fee = 1000; - return true; - })); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector&, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { + old_fee = 500; + new_fee = 1000; + return true; + }; BumpTransactionModel model(wallet.get()); model.prepareFeeBump(TEST_TXID, 1); - ON_CALL(*wallet, transactionCanBeBumped(testing::_)).WillByDefault(Return(false)); + wallet->transaction_can_be_bumped_fn = [](const Txid&) { return false; }; model.confirmFeeBump(); QCOMPARE(model.state(), BumpTransactionModel::Failed); @@ -193,14 +182,13 @@ private Q_SLOTS: void reset_clearsState() { - auto wallet = MakeMockWallet(); - ON_CALL(*wallet, createBumpTransaction(testing::_, testing::_, testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(Invoke([](const Txid&, const wallet::CCoinControl&, std::vector&, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { - old_fee = 500; - new_fee = 1000; - return true; - })); + auto wallet = MakeBumpableWallet(); + wallet->create_bump_transaction_fn = [](const Txid&, const wallet::CCoinControl&, std::vector&, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction&) { + old_fee = 500; + new_fee = 1000; + return true; + }; BumpTransactionModel model(wallet.get()); model.prepareFeeBump(TEST_TXID, 1); diff --git a/test/test_networktraffictower.cpp b/test/test_networktraffictower.cpp index 8b90e63fa1..74ca2206f4 100644 --- a/test/test_networktraffictower.cpp +++ b/test/test_networktraffictower.cpp @@ -15,9 +15,6 @@ #include namespace { -using ::testing::Invoke; -using ::testing::NiceMock; - constexpr int TEST_SAMPLE_INTERVAL_MS{10}; constexpr int ASYNC_TIMEOUT_MS{1'000}; } // namespace @@ -35,22 +32,22 @@ private Q_SLOTS: void NetworkTrafficTowerTests::samplesOffGuiThreadWithoutPublishingWhileInactive() { - NiceMock node; + MockNode node; std::atomic received_calls{0}; std::atomic sent_calls{0}; std::atomic sampled_on_gui_thread{false}; QThread* const gui_thread{QThread::currentThread()}; - ON_CALL(node, getTotalBytesRecv()).WillByDefault(Invoke([&] { + node.get_total_bytes_recv_fn = [&] { ++received_calls; if (QThread::currentThread() == gui_thread) sampled_on_gui_thread = true; return int64_t{1'000}; - })); - ON_CALL(node, getTotalBytesSent()).WillByDefault(Invoke([&] { + }; + node.get_total_bytes_sent_fn = [&] { ++sent_calls; if (QThread::currentThread() == gui_thread) sampled_on_gui_thread = true; return int64_t{2'000}; - })); + }; { NetworkTrafficTower tower{node, TEST_SAMPLE_INTERVAL_MS}; @@ -74,18 +71,18 @@ void NetworkTrafficTowerTests::samplesOffGuiThreadWithoutPublishingWhileInactive void NetworkTrafficTowerTests::activeControlsPublishingWithoutDiscardingBackgroundHistory() { - NiceMock node; + MockNode node; std::atomic total_received{1'000}; std::atomic total_sent{2'000}; std::atomic received_calls{0}; - ON_CALL(node, getTotalBytesRecv()).WillByDefault(Invoke([&] { + node.get_total_bytes_recv_fn = [&] { ++received_calls; return total_received.load(); - })); - ON_CALL(node, getTotalBytesSent()).WillByDefault(Invoke([&] { + }; + node.get_total_bytes_sent_fn = [&] { return total_sent.load(); - })); + }; NetworkTrafficTower tower{node, TEST_SAMPLE_INTERVAL_MS}; QSignalSpy received_list_spy{&tower, &NetworkTrafficTower::receivedRateListChanged}; @@ -124,16 +121,16 @@ void NetworkTrafficTowerTests::activeControlsPublishingWithoutDiscardingBackgrou void NetworkTrafficTowerTests::filterWindowChangesPreserveTotalsAndHistory() { - NiceMock node; + MockNode node; std::atomic received_calls{0}; - ON_CALL(node, getTotalBytesRecv()).WillByDefault(Invoke([&] { + node.get_total_bytes_recv_fn = [&] { ++received_calls; return int64_t{1'000}; - })); - ON_CALL(node, getTotalBytesSent()).WillByDefault(Invoke([] { + }; + node.get_total_bytes_sent_fn = [] { return int64_t{2'000}; - })); + }; NetworkTrafficTower tower{node, TEST_SAMPLE_INTERVAL_MS}; tower.setActive(true); @@ -156,16 +153,16 @@ void NetworkTrafficTowerTests::filterWindowChangesPreserveTotalsAndHistory() void NetworkTrafficTowerTests::stopsSamplingWhenDestroyedWhileActive() { - NiceMock node; + MockNode node; std::atomic received_calls{0}; - ON_CALL(node, getTotalBytesRecv()).WillByDefault(Invoke([&] { + node.get_total_bytes_recv_fn = [&] { ++received_calls; return int64_t{1'000}; - })); - ON_CALL(node, getTotalBytesSent()).WillByDefault(Invoke([] { + }; + node.get_total_bytes_sent_fn = [] { return int64_t{2'000}; - })); + }; { NetworkTrafficTower tower{node, TEST_SAMPLE_INTERVAL_MS}; diff --git a/test/test_nodemodel.cpp b/test/test_nodemodel.cpp index 8bb51a5f54..8c0cdd505f 100644 --- a/test/test_nodemodel.cpp +++ b/test/test_nodemodel.cpp @@ -4,7 +4,6 @@ #include -#include #include #include @@ -29,11 +28,6 @@ #include namespace { -using ::testing::Invoke; -using ::testing::NiceMock; -using ::testing::Return; -using ::testing::Truly; - constexpr int ASYNC_TIMEOUT_MS{1'000}; std::unique_ptr MakeNoopHandler() @@ -54,46 +48,22 @@ struct PeerCountState { std::atomic outbound{0}; }; -void InstallDefaultHandlers(NiceMock& node) +void ConfigureNodeModelDefaults(MockNode& node) { - ON_CALL(node, handleNotifyBlockTip(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::NotifyBlockTipFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleNotifyHeaderTip(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::NotifyHeaderTipFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleNotifyNumConnectionsChanged(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::NotifyNumConnectionsChangedFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleNotifyNetworkActiveChanged(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::NotifyNetworkActiveChangedFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleNotifyAlertChanged(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::NotifyAlertChangedFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::MessageBoxFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleQuestion(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::QuestionFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleBannedListChanged(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::BannedListChangedFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, getWarnings()).WillByDefault(Return(Untranslated(""))); + node.handle_notify_block_tip_fn = [](interfaces::Node::NotifyBlockTipFn) { return MakeNoopHandler(); }; + node.handle_notify_header_tip_fn = [](interfaces::Node::NotifyHeaderTipFn) { return MakeNoopHandler(); }; + node.handle_notify_num_connections_changed_fn = [](interfaces::Node::NotifyNumConnectionsChangedFn) { return MakeNoopHandler(); }; + node.handle_notify_network_active_changed_fn = [](interfaces::Node::NotifyNetworkActiveChangedFn) { return MakeNoopHandler(); }; + node.handle_notify_alert_changed_fn = [](interfaces::Node::NotifyAlertChangedFn) { return MakeNoopHandler(); }; + node.handle_message_box_fn = [](interfaces::Node::MessageBoxFn) { return MakeNoopHandler(); }; + node.handle_question_fn = [](interfaces::Node::QuestionFn) { return MakeNoopHandler(); }; + node.handle_banned_list_changed_fn = [](interfaces::Node::BannedListChangedFn) { return MakeNoopHandler(); }; + node.get_warnings_fn = [] { return Untranslated(""); }; } -void InstallPeerCountGetters(NiceMock& node, PeerCountState& peers) +void ConfigurePeerCountGetters(MockNode& node, PeerCountState& peers) { - ON_CALL(node, getNodeCount(testing::_)).WillByDefault(Invoke([&peers](ConnectionDirection direction) { + node.get_node_count_fn = [&peers](ConnectionDirection direction) { switch (direction) { case ConnectionDirection::Both: return peers.total.load(); @@ -105,21 +75,21 @@ void InstallPeerCountGetters(NiceMock& node, PeerCountState& peers) return size_t{0}; } return size_t{0}; - })); + }; } -void InstallMempoolGetters(NiceMock& node, MempoolState& mempool) +void ConfigureMempoolGetters(MockNode& node, MempoolState& mempool) { - ON_CALL(node, getMempoolSize()).WillByDefault(Invoke([&mempool] { + node.get_mempool_size_fn = [&mempool] { return mempool.count.load(); - })); - ON_CALL(node, getMempoolDynamicUsage()).WillByDefault(Invoke([&mempool] { + }; + node.get_mempool_dynamic_usage_fn = [&mempool] { return mempool.usage_bytes.load(); - })); - ON_CALL(node, getMempoolMaxUsage()).WillByDefault(Invoke([&mempool] { + }; + node.get_mempool_max_usage_fn = [&mempool] { ++mempool.max_usage_calls; return mempool.max_bytes.load(); - })); + }; } void WaitForInitialMempoolRefresh(MempoolState& mempool) @@ -129,7 +99,7 @@ void WaitForInitialMempoolRefresh(MempoolState& mempool) } } // namespace -class NodeModelTests : public GmockTestFixture +class NodeModelTests : public QObject { Q_OBJECT @@ -172,10 +142,10 @@ private Q_SLOTS: void NodeModelTests::refreshMempoolInfoUpdatesProperties() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -195,10 +165,10 @@ void NodeModelTests::refreshMempoolInfoUpdatesProperties() void NodeModelTests::activatingMempoolPollingEmitsSignalsAndRefreshesImmediately() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -227,7 +197,7 @@ void NodeModelTests::activatingMempoolPollingEmitsSignalsAndRefreshesImmediately void NodeModelTests::peerCountsInitializeAndRefreshFromDirectionalNodeCounts() { - NiceMock node; + MockNode node; MempoolState mempool; PeerCountState peers; interfaces::Node::NotifyNumConnectionsChangedFn connections_changed_fn; @@ -236,14 +206,13 @@ void NodeModelTests::peerCountsInitializeAndRefreshFromDirectionalNodeCounts() peers.inbound = 1; peers.outbound = 2; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - InstallPeerCountGetters(node, peers); - ON_CALL(node, handleNotifyNumConnectionsChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyNumConnectionsChangedFn fn) { - connections_changed_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + ConfigurePeerCountGetters(node, peers); + node.handle_notify_num_connections_changed_fn = [&](interfaces::Node::NotifyNumConnectionsChangedFn fn) { + connections_changed_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -283,33 +252,40 @@ void NodeModelTests::peerCountsInitializeAndRefreshFromDirectionalNodeCounts() void NodeModelTests::disconnectPeerReturnsNodeResult() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); - EXPECT_CALL(node, disconnectById(7)).Times(1).WillOnce(Return(true)); + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + std::vector disconnected_ids; + node.disconnect_by_id_fn = [&](NodeId id) { + disconnected_ids.push_back(id); + return id == 7; + }; + node.ExpectExactly(node.calls.disconnectById, 2); QVERIFY(model.disconnectPeer(7)); - EXPECT_CALL(node, disconnectById(8)).Times(1).WillOnce(Return(false)); QVERIFY(!model.disconnectPeer(8)); + QCOMPARE(disconnected_ids, std::vector({7, 8})); } void NodeModelTests::banPeerRejectsInvalidInputs() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); - EXPECT_CALL(node, ban(testing::_, testing::_)).Times(0); - EXPECT_CALL(node, disconnectByAddress(testing::_)).Times(0); + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + node.ExpectNoCalls(node.calls.ban); + node.ExpectNoCalls(node.calls.disconnectByAddress); QVERIFY(!model.banPeer(QStringLiteral("not an address"), 3600)); QVERIFY(!model.banPeer(QStringLiteral("127.0.0.1"), 0)); @@ -318,47 +294,62 @@ void NodeModelTests::banPeerRejectsInvalidInputs() void NodeModelTests::banPeerReturnsFalseWithoutDisconnectWhenBackendFails() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); - EXPECT_CALL(node, ban(Truly([](const CNetAddr& value) { - return value.ToStringAddr() == "127.0.0.1"; - }), 3600)).Times(1).WillOnce(Return(false)); - EXPECT_CALL(node, disconnectByAddress(testing::_)).Times(0); + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + bool received_expected_ban{false}; + node.ban_fn = [&](const CNetAddr& value, int64_t duration) { + received_expected_ban = value.ToStringAddr() == "127.0.0.1" && duration == 3600; + return false; + }; + node.ExpectExactly(node.calls.ban, 1); + node.ExpectNoCalls(node.calls.disconnectByAddress); QVERIFY(!model.banPeer(QStringLiteral("127.0.0.1"), 3600)); + QVERIFY(received_expected_ban); } void NodeModelTests::banPeerDisconnectsAddressAfterSuccessfulBan() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); - auto is_loopback = [](const CNetAddr& value) { - return value.ToStringAddr() == "127.0.0.1"; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + bool received_expected_ban{false}; + bool received_expected_disconnect{false}; + node.ban_fn = [&](const CNetAddr& value, int64_t duration) { + received_expected_ban = value.ToStringAddr() == "127.0.0.1" && duration == 3600; + return true; }; - EXPECT_CALL(node, ban(Truly(is_loopback), 3600)).Times(1).WillOnce(Return(true)); - EXPECT_CALL(node, disconnectByAddress(Truly(is_loopback))).Times(1).WillOnce(Return(true)); + node.disconnect_by_address_fn = [&](const CNetAddr& value) { + received_expected_disconnect = value.ToStringAddr() == "127.0.0.1"; + return true; + }; + node.ExpectExactly(node.calls.ban, 1); + node.ExpectExactly(node.calls.disconnectByAddress, 1); QVERIFY(model.banPeer(QStringLiteral("127.0.0.1"), 3600)); + QVERIFY(received_expected_ban); + QVERIFY(received_expected_disconnect); } void NodeModelTests::requestShutdownEmitsOnlyOnce() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -372,11 +363,11 @@ void NodeModelTests::requestShutdownEmitsOnlyOnce() void NodeModelTests::initializationFailureRequestsShutdownWhenCoreWasInterrupted() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, shutdownRequested()).WillByDefault(Return(true)); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.shutdown_requested_fn = [] { return true; }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -394,11 +385,11 @@ void NodeModelTests::initializationFailureRequestsShutdownWhenCoreWasInterrupted void NodeModelTests::initializationFailureWithoutCoreInterruptOnlySetsErrorState() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, shutdownRequested()).WillByDefault(Return(false)); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.shutdown_requested_fn = [] { return false; }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -416,11 +407,11 @@ void NodeModelTests::initializationFailureWithoutCoreInterruptOnlySetsErrorState void NodeModelTests::initializationSuccessDuringCoreShutdownSkipsReadyState() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, shutdownRequested()).WillByDefault(Return(true)); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.shutdown_requested_fn = [] { return true; }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -437,46 +428,38 @@ void NodeModelTests::initializationSuccessDuringCoreShutdownSkipsReadyState() void NodeModelTests::destructorUnsubscribesCoreSignalsBeforeStoppingPolling() { - NiceMock node; + MockNode node; MempoolState mempool; std::atomic disconnected_handlers{0}; auto counted_handler = [&] { return interfaces::MakeCleanupHandler([&] { ++disconnected_handlers; }); }; - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleNotifyBlockTip(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyBlockTipFn) { - return counted_handler(); - })); - ON_CALL(node, handleNotifyHeaderTip(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyHeaderTipFn) { - return counted_handler(); - })); - ON_CALL(node, handleNotifyNumConnectionsChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyNumConnectionsChangedFn) { - return counted_handler(); - })); - ON_CALL(node, handleNotifyNetworkActiveChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyNetworkActiveChangedFn) { - return counted_handler(); - })); - ON_CALL(node, handleNotifyAlertChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyAlertChangedFn) { - return counted_handler(); - })); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn) { - return counted_handler(); - })); - ON_CALL(node, handleQuestion(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::QuestionFn) { - return counted_handler(); - })); - ON_CALL(node, handleBannedListChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::BannedListChangedFn) { - return counted_handler(); - })); + ConfigureMempoolGetters(node, mempool); + node.handle_notify_block_tip_fn = [&](interfaces::Node::NotifyBlockTipFn) { + return counted_handler(); + }; + node.handle_notify_header_tip_fn = [&](interfaces::Node::NotifyHeaderTipFn) { + return counted_handler(); + }; + node.handle_notify_num_connections_changed_fn = [&](interfaces::Node::NotifyNumConnectionsChangedFn) { + return counted_handler(); + }; + node.handle_notify_network_active_changed_fn = [&](interfaces::Node::NotifyNetworkActiveChangedFn) { + return counted_handler(); + }; + node.handle_notify_alert_changed_fn = [&](interfaces::Node::NotifyAlertChangedFn) { + return counted_handler(); + }; + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn) { + return counted_handler(); + }; + node.handle_question_fn = [&](interfaces::Node::QuestionFn) { + return counted_handler(); + }; + node.handle_banned_list_changed_fn = [&](interfaces::Node::BannedListChangedFn) { + return counted_handler(); + }; auto model{std::make_unique(node)}; WaitForInitialMempoolRefresh(mempool); @@ -501,31 +484,28 @@ void NodeModelTests::destructorUnsubscribesCoreSignalsBeforeStoppingPolling() void NodeModelTests::nodeNotificationHandlersUpdateModelThroughQueuedSignals() { - NiceMock node; + MockNode node; MempoolState mempool; PeerCountState peers; interfaces::Node::NotifyBlockTipFn block_tip_fn; interfaces::Node::NotifyNumConnectionsChangedFn connections_changed_fn; interfaces::Node::BannedListChangedFn banned_list_changed_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - InstallPeerCountGetters(node, peers); - ON_CALL(node, handleNotifyBlockTip(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyBlockTipFn fn) { - block_tip_fn = std::move(fn); - return MakeNoopHandler(); - })); - ON_CALL(node, handleNotifyNumConnectionsChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyNumConnectionsChangedFn fn) { - connections_changed_fn = std::move(fn); - return MakeNoopHandler(); - })); - ON_CALL(node, handleBannedListChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::BannedListChangedFn fn) { - banned_list_changed_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + ConfigurePeerCountGetters(node, peers); + node.handle_notify_block_tip_fn = [&](interfaces::Node::NotifyBlockTipFn fn) { + block_tip_fn = std::move(fn); + return MakeNoopHandler(); + }; + node.handle_notify_num_connections_changed_fn = [&](interfaces::Node::NotifyNumConnectionsChangedFn fn) { + connections_changed_fn = std::move(fn); + return MakeNoopHandler(); + }; + node.handle_banned_list_changed_fn = [&](interfaces::Node::BannedListChangedFn fn) { + banned_list_changed_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -566,25 +546,22 @@ void NodeModelTests::nodeNotificationHandlersUpdateModelThroughQueuedSignals() void NodeModelTests::blockTipUpdatesQueuedAcrossThreadsRetainPayloadValues() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::NotifyBlockTipFn block_tip_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleNotifyBlockTip(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyBlockTipFn fn) { - block_tip_fn = std::move(fn); - return MakeNoopHandler(); - })); - ON_CALL(node, handleNotifyNumConnectionsChanged(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::NotifyNumConnectionsChangedFn) { - return MakeNoopHandler(); - })); - ON_CALL(node, handleBannedListChanged(testing::_)) - .WillByDefault(Invoke([](interfaces::Node::BannedListChangedFn) { - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_notify_block_tip_fn = [&](interfaces::Node::NotifyBlockTipFn fn) { + block_tip_fn = std::move(fn); + return MakeNoopHandler(); + }; + node.handle_notify_num_connections_changed_fn = [](interfaces::Node::NotifyNumConnectionsChangedFn) { + return MakeNoopHandler(); + }; + node.handle_banned_list_changed_fn = [](interfaces::Node::BannedListChangedFn) { + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -626,19 +603,18 @@ void NodeModelTests::blockTipUpdatesQueuedAcrossThreadsRetainPayloadValues() void NodeModelTests::blockSyncActiveFollowsInitializationAndBlockTipState() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::NotifyBlockTipFn block_tip_fn; bool initial_block_download{true}; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, isInitialBlockDownload()).WillByDefault(Invoke([&] { return initial_block_download; })); - ON_CALL(node, handleNotifyBlockTip(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyBlockTipFn fn) { - block_tip_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.is_initial_block_download_fn = [&] { return initial_block_download; }; + node.handle_notify_block_tip_fn = [&](interfaces::Node::NotifyBlockTipFn fn) { + block_tip_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -679,19 +655,18 @@ void NodeModelTests::blockSyncActiveFollowsInitializationAndBlockTipState() void NodeModelTests::alertNotificationsRefreshWarningList() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::NotifyAlertChangedFn alert_changed_fn; bilingual_str warnings{Untranslated("")}; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, getWarnings()).WillByDefault(Invoke([&] { return warnings; })); - ON_CALL(node, handleNotifyAlertChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyAlertChangedFn fn) { - alert_changed_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.get_warnings_fn = [&] { return warnings; }; + node.handle_notify_alert_changed_fn = [&](interfaces::Node::NotifyAlertChangedFn fn) { + alert_changed_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -710,17 +685,16 @@ void NodeModelTests::alertNotificationsRefreshWarningList() void NodeModelTests::headerTipNotificationsExposeHeaderSyncProgress() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::NotifyHeaderTipFn header_tip_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleNotifyHeaderTip(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyHeaderTipFn fn) { - header_tip_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_notify_header_tip_fn = [&](interfaces::Node::NotifyHeaderTipFn fn) { + header_tip_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -743,25 +717,23 @@ void NodeModelTests::headerTipNotificationsExposeHeaderSyncProgress() void NodeModelTests::startupWarningsAreShownOnceAndDoNotBecomeCurrentWarnings() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; interfaces::Node::NotifyAlertChangedFn alert_changed_fn; bilingual_str warnings{"network warning", "Translated network warning"}; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, getWarnings()).WillByDefault(Invoke([&] { return warnings; })); - ON_CALL(node, handleNotifyAlertChanged(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::NotifyAlertChangedFn fn) { - alert_changed_fn = std::move(fn); - return MakeNoopHandler(); - })); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.get_warnings_fn = [&] { return warnings; }; + node.handle_notify_alert_changed_fn = [&](interfaces::Node::NotifyAlertChangedFn fn) { + alert_changed_fn = std::move(fn); + return MakeNoopHandler(); + }; + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; model.addStartupWarnings({QStringLiteral("Translated early startup warning")}); @@ -802,17 +774,16 @@ void NodeModelTests::startupWarningsAreShownOnceAndDoNotBecomeCurrentWarnings() void NodeModelTests::runtimeMessageHandlerOpensAfterInitialization() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -848,17 +819,16 @@ void NodeModelTests::runtimeMessageHandlerOpensAfterInitialization() void NodeModelTests::runtimeQuestionHandlerBlocksForAnswerAndReturnsResult() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::QuestionFn question_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleQuestion(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::QuestionFn fn) { - question_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_question_fn = [&](interfaces::Node::QuestionFn fn) { + question_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -897,17 +867,16 @@ void NodeModelTests::runtimeQuestionHandlerBlocksForAnswerAndReturnsResult() void NodeModelTests::runtimeStartupQuestionFailureLetsInitializeResultRequestShutdown() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::QuestionFn question_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleQuestion(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::QuestionFn fn) { - question_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_question_fn = [&](interfaces::Node::QuestionFn fn) { + question_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -958,17 +927,16 @@ void NodeModelTests::runtimeStartupQuestionFailureLetsInitializeResultRequestShu void NodeModelTests::runtimeStartupErrorDialogLetsInitializeResultRequestShutdown() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1017,17 +985,16 @@ void NodeModelTests::runtimeStartupErrorDialogLetsInitializeResultRequestShutdow void NodeModelTests::runtimeDialogDefaultsToOkWhenNoButtonsAreSpecified() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1058,17 +1025,16 @@ void NodeModelTests::runtimeDialogDefaultsToOkWhenNoButtonsAreSpecified() void NodeModelTests::runtimeDialogExposesFullCoreButtonMask() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1103,17 +1069,16 @@ void NodeModelTests::runtimeDialogExposesFullCoreButtonMask() void NodeModelTests::runtimeBlockingDialogsAreQueued() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::QuestionFn question_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleQuestion(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::QuestionFn fn) { - question_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_question_fn = [&](interfaces::Node::QuestionFn fn) { + question_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1156,17 +1121,16 @@ void NodeModelTests::runtimeBlockingDialogsAreQueued() void NodeModelTests::runtimeNonBlockingDialogsAreQueued() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1200,12 +1164,12 @@ void NodeModelTests::runtimeNonBlockingDialogsAreQueued() void NodeModelTests::initializeFailureShowsStartupWarningsWithoutMakingThemCurrentWarnings() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, getWarnings()).WillByDefault(Return(bilingual_str{"pre-release warning", "Translated pre-release warning"})); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.get_warnings_fn = [] { return bilingual_str{"pre-release warning", "Translated pre-release warning"}; }; NodeModel model{node}; model.addStartupWarnings({QStringLiteral("Translated startup warning")}); @@ -1224,18 +1188,17 @@ void NodeModelTests::initializeFailureShowsStartupWarningsWithoutMakingThemCurre void NodeModelTests::initializeFailureUsesNodeErrorMessages() { - NiceMock node; + MockNode node; MempoolState mempool; interfaces::Node::MessageBoxFn message_box_fn; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, getWarnings()).WillByDefault(Return(bilingual_str{"pre-release warning", "Translated pre-release warning"})); - ON_CALL(node, handleMessageBox(testing::_)) - .WillByDefault(Invoke([&](interfaces::Node::MessageBoxFn fn) { - message_box_fn = std::move(fn); - return MakeNoopHandler(); - })); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.get_warnings_fn = [] { return bilingual_str{"pre-release warning", "Translated pre-release warning"}; }; + node.handle_message_box_fn = [&](interfaces::Node::MessageBoxFn fn) { + message_box_fn = std::move(fn); + return MakeNoopHandler(); + }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1272,11 +1235,11 @@ void NodeModelTests::initializeFailureUsesNodeErrorMessages() void NodeModelTests::runawayExceptionSetsFatalStartupError() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1288,16 +1251,17 @@ void NodeModelTests::runawayExceptionSetsFatalStartupError() void NodeModelTests::nodeInformationRowsAvoidChainmanBeforeInitialization() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - EXPECT_CALL(node, getHeaderTip(testing::_, testing::_)).Times(0); - EXPECT_CALL(node, getNumBlocks()).Times(0); - EXPECT_CALL(node, getLastBlockTime()).Times(0); - EXPECT_CALL(node, getNetLocalAddresses()).Times(0); - EXPECT_CALL(node, getNetworkActive()).Times(0); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + node.ExpectNoCalls(node.calls.getHeaderTip); + node.ExpectNoCalls(node.calls.getNumBlocks); + node.ExpectNoCalls(node.calls.getLastBlockTime); + node.ExpectNoCalls(node.calls.getNetLocalAddresses); + node.ExpectNoCalls(node.calls.getNetworkActive); NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1317,7 +1281,7 @@ void NodeModelTests::nodeInformationRowsAvoidChainmanBeforeInitialization() void NodeModelTests::nodeInformationRowsExposeDiagnostics() { - NiceMock node; + MockNode node; MempoolState mempool; PeerCountState peers; @@ -1325,19 +1289,18 @@ void NodeModelTests::nodeInformationRowsExposeDiagnostics() peers.inbound = 1; peers.outbound = 2; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - InstallPeerCountGetters(node, peers); - ON_CALL(node, getNumBlocks()).WillByDefault(Return(321)); - ON_CALL(node, getHeaderTip(testing::_, testing::_)) - .WillByDefault(Invoke([](int& height, int64_t& block_time) { - height = 333; - block_time = 1'700'000'333; - return true; - })); - ON_CALL(node, getLastBlockTime()).WillByDefault(Return(1'700'000'321)); - ON_CALL(node, getNetworkActive()).WillByDefault(Return(true)); - ON_CALL(node, getNetLocalAddresses()).WillByDefault(Return(std::map{})); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + ConfigurePeerCountGetters(node, peers); + node.get_num_blocks_fn = [] { return 321; }; + node.get_header_tip_fn = [](int& height, int64_t& block_time) { + height = 333; + block_time = 1'700'000'333; + return true; + }; + node.get_last_block_time_fn = [] { return int64_t{1'700'000'321}; }; + node.get_network_active_fn = [] { return true; }; + node.get_net_local_addresses_fn = [] { return std::map{}; }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); @@ -1366,8 +1329,8 @@ void NodeModelTests::nodeInformationRowsExposeDiagnostics() void NodeModelTests::initEmitsRequestedInitialize() { - NiceMock node; - InstallDefaultHandlers(node); + MockNode node; + ConfigureNodeModelDefaults(node); NodeModel model{node}; QSignalSpy spy{&model, &NodeModel::requestedInitialize}; @@ -1377,8 +1340,8 @@ void NodeModelTests::initEmitsRequestedInitialize() void NodeModelTests::initGuardBlocksSecondEmission() { - NiceMock node; - InstallDefaultHandlers(node); + MockNode node; + ConfigureNodeModelDefaults(node); NodeModel model{node}; QSignalSpy spy{&model, &NodeModel::requestedInitialize}; @@ -1389,20 +1352,22 @@ void NodeModelTests::initGuardBlocksSecondEmission() void NodeModelTests::shutdownPollingStartsShutdownBeforeEmittingSignal() { - NiceMock node; + MockNode node; MempoolState mempool; - InstallDefaultHandlers(node); - InstallMempoolGetters(node, mempool); - ON_CALL(node, shutdownRequested()).WillByDefault(Return(true)); + ConfigureNodeModelDefaults(node); + ConfigureMempoolGetters(node, mempool); + node.shutdown_requested_fn = [] { return true; }; NodeModel model{node}; WaitForInitialMempoolRefresh(mempool); QSignalSpy shutdown_spy{&model, &NodeModel::requestedShutdown}; bool started_before_signal{false}; - EXPECT_CALL(node, startShutdown()).WillOnce(Invoke([&] { + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + node.start_shutdown_fn = [&] { started_before_signal = shutdown_spy.count() == 0; - })); + }; + node.ExpectExactly(node.calls.startShutdown, 1); model.startShutdownPolling(); diff --git a/test/test_options_model.cpp b/test/test_options_model.cpp index 765b7056a0..b531330c2f 100644 --- a/test/test_options_model.cpp +++ b/test/test_options_model.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -31,7 +30,7 @@ const TranslateFn G_TRANSLATION_FUN{nullptr}; #endif -class OptionsModelTests : public GmockTestFixture +class OptionsModelTests : public QObject { Q_OBJECT @@ -160,8 +159,7 @@ private Q_SLOTS: void onboardingApplyWritesTouchedParameterInteractionOverride(); }; -// Convenience: set up a NiceMock whose getPersistentSetting returns null for -// all keys by default, but returns a given address for the specified key. +// Convenience helpers for persistent setting values used by the node double. static common::SettingsValue MakeAddress(const std::string& addr) { return common::SettingsValue{addr}; @@ -172,32 +170,39 @@ static common::SettingsValue MakeInt(int value) return common::SettingsValue{value}; } -static void InstallPersistentSettings(MockNode& node, ArgsManager& args) +static int SettingWriteCount(const MockNode& node, const std::string& name) { - using ::testing::_; - using ::testing::Invoke; + return std::count_if(node.update_rw_setting_arguments.begin(), node.update_rw_setting_arguments.end(), [&](const auto& write) { + return write.first == name; + }); +} - ON_CALL(node, getPersistentSetting(_)) - .WillByDefault(Invoke([&args](const std::string& name) { - return args.GetPersistentSetting(name); - })); +static const common::SettingsValue* FindSettingWrite(const MockNode& node, const std::string& name) +{ + const auto it{std::find_if(node.update_rw_setting_arguments.rbegin(), node.update_rw_setting_arguments.rend(), [&](const auto& write) { + return write.first == name; + })}; + return it == node.update_rw_setting_arguments.rend() ? nullptr : &it->second; } -static void InstallRwSettingsWriter(MockNode& node, ArgsManager& args) +static void InstallPersistentSettings(MockNode& node, ArgsManager& args) { - using ::testing::_; - using ::testing::Invoke; + node.get_persistent_setting_fn = [&args](const std::string& name) { + return args.GetPersistentSetting(name); + }; +} - ON_CALL(node, updateRwSetting(_, _)) - .WillByDefault(Invoke([&args](const std::string& name, const common::SettingsValue& value) { - args.LockSettings([&](common::Settings& settings) { - if (value.isNull()) { - settings.rw_settings.erase(name); - } else { - settings.rw_settings[name] = value; - } - }); - })); +static void InstallRwSettingsWriter(MockNode& node, ArgsManager& args) +{ + node.update_rw_setting_fn = [&args](const std::string& name, const common::SettingsValue& value) { + args.LockSettings([&](common::Settings& settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + }; } static std::vector TestArgv() @@ -368,63 +373,44 @@ static void PrepareArgsForDataDir(ArgsManager& args, const QString& data_dir) void OptionsModelTests::proxyDisabledRemovesKey() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; // Simulate a previously-saved proxy address so that m_proxy_enabled=true on construction. - ON_CALL(node, getPersistentSetting(std::string{"proxy"})) - .WillByDefault(Return(MakeAddress("127.0.0.1:9050"))); + node.SetPersistentSetting("proxy", MakeAddress("127.0.0.1:9050")); OptionsQmlModel model(node); QVERIFY(model.proxyEnabled()); // When proxy is disabled, updateRwSetting must be called with a null (not // empty-string) SettingsValue so that the key is erased from settings.json. - EXPECT_CALL(node, updateRwSetting(std::string{"proxy-prev"}, _)); - EXPECT_CALL(node, updateRwSetting(std::string{"proxy"}, - Truly([](const common::SettingsValue& v) { return v.isNull(); }))); - model.setProxyEnabled(false); QVERIFY(!model.proxyEnabled()); + QCOMPARE(node.update_rw_setting_arguments.size(), 2U); + QCOMPARE(SettingWriteCount(node, "proxy-prev"), 1); + const auto* proxy_write{FindSettingWrite(node, "proxy")}; + QVERIFY(proxy_write != nullptr); + QVERIFY(proxy_write->isNull()); } void OptionsModelTests::torDisabledRemovesKey() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"onion"})) - .WillByDefault(Return(MakeAddress("127.0.0.1:9150"))); + MockNode node; + node.SetPersistentSetting("onion", MakeAddress("127.0.0.1:9150")); OptionsQmlModel model(node); QVERIFY(model.torEnabled()); - EXPECT_CALL(node, updateRwSetting(std::string{"onion-prev"}, _)); - EXPECT_CALL(node, updateRwSetting(std::string{"onion"}, - Truly([](const common::SettingsValue& v) { return v.isNull(); }))); - model.setTorEnabled(false); QVERIFY(!model.torEnabled()); + QCOMPARE(node.update_rw_setting_arguments.size(), 2U); + QCOMPARE(SettingWriteCount(node, "onion-prev"), 1); + const auto* onion_write{FindSettingWrite(node, "onion")}; + QVERIFY(onion_write != nullptr); + QVERIFY(onion_write->isNull()); } void OptionsModelTests::proxyEnabledWritesAddress() { - using ::testing::_; - using ::testing::Eq; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; // Construct with no saved proxy — m_proxy_enabled=false, m_proxy_address="". OptionsQmlModel model(node); @@ -432,27 +418,24 @@ void OptionsModelTests::proxyEnabledWritesAddress() // Pre-load an address into the model (as QML does before toggling the switch). model.setProxyAddress("10.0.0.1:9050"); + node.update_rw_setting_arguments.clear(); // Enabling proxy must write the address string to settings. - EXPECT_CALL(node, updateRwSetting(std::string{"proxy"}, - Truly([](const common::SettingsValue& v) { - return v.isStr() && v.get_str() == "10.0.0.1:9050"; - }))); - EXPECT_CALL(node, updateRwSetting(std::string{"proxy-prev"}, - Truly([](const common::SettingsValue& v) { return v.isNull(); }))); - model.setProxyEnabled(true); QVERIFY(model.proxyEnabled()); + QCOMPARE(node.update_rw_setting_arguments.size(), 2U); + const auto* proxy_write{FindSettingWrite(node, "proxy")}; + QVERIFY(proxy_write != nullptr); + QVERIFY(proxy_write->isStr()); + QCOMPARE(proxy_write->get_str(), std::string{"10.0.0.1:9050"}); + const auto* previous_write{FindSettingWrite(node, "proxy-prev")}; + QVERIFY(previous_write != nullptr); + QVERIFY(previous_write->isNull()); } void OptionsModelTests::proxyDirtySetAtRuntime() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.proxySettingsDirty()); @@ -463,12 +446,7 @@ void OptionsModelTests::proxyDirtySetAtRuntime() void OptionsModelTests::proxyDirtyResetWhenReverted() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; // Start onboarded with proxy disabled (no saved proxy). OptionsQmlModel model(node); @@ -492,14 +470,8 @@ void OptionsModelTests::proxyDirtyResetWhenReverted() void OptionsModelTests::mempoolSizeLoadedFromSettings() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"maxmempool"})) - .WillByDefault(Return(MakeInt(456))); + MockNode node; + node.SetPersistentSetting("maxmempool", MakeInt(456)); OptionsQmlModel model(node); QCOMPARE(model.maxMempoolSizeMB(), 456); @@ -507,120 +479,81 @@ void OptionsModelTests::mempoolSizeLoadedFromSettings() void OptionsModelTests::mempoolSizeWritesSetting() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); - EXPECT_CALL(node, updateRwSetting(std::string{"maxmempool"}, - Truly([](const common::SettingsValue& v) { - return v.isNum() && v.getInt() == 456; - }))); - model.setMaxMempoolSizeMB(456); QCOMPARE(model.maxMempoolSizeMB(), 456); + QCOMPARE(node.update_rw_setting_arguments.size(), 1U); + const auto* mempool_write{FindSettingWrite(node, "maxmempool")}; + QVERIFY(mempool_write != nullptr); + QVERIFY(mempool_write->isNum()); + QCOMPARE(mempool_write->getInt(), int64_t{456}); } void OptionsModelTests::mempoolSizeDoesNotRewriteUnchangedSetting() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"maxmempool"})) - .WillByDefault(Return(MakeInt(456))); + MockNode node; + node.SetPersistentSetting("maxmempool", MakeInt(456)); OptionsQmlModel model(node); - EXPECT_CALL(node, updateRwSetting(std::string{"maxmempool"}, _)).Times(0); - model.setMaxMempoolSizeMB(456); QCOMPARE(model.maxMempoolSizeMB(), 456); + QCOMPARE(SettingWriteCount(node, "maxmempool"), 0); } void OptionsModelTests::legacyNumericSettingsWriteStrings() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); - EXPECT_CALL(node, updateRwSetting(std::string{"dbcache"}, - Truly([](const common::SettingsValue& value) { - return value.isStr() && value.get_str() == "600"; - }))); - EXPECT_CALL(node, updateRwSetting(std::string{"par"}, - Truly([](const common::SettingsValue& value) { - return value.isStr() && value.get_str() == "12"; - }))); - model.setDbcacheSizeMiB(600); model.setScriptThreads(12); + QCOMPARE(node.update_rw_setting_arguments.size(), 2U); + const auto* dbcache_write{FindSettingWrite(node, "dbcache")}; + const auto* par_write{FindSettingWrite(node, "par")}; + QVERIFY(dbcache_write != nullptr && dbcache_write->isStr()); + QVERIFY(par_write != nullptr && par_write->isStr()); + QCOMPARE(dbcache_write->get_str(), std::string{"600"}); + QCOMPARE(par_write->get_str(), std::string{"12"}); } void OptionsModelTests::externalSignerPathWritesSigner() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); - EXPECT_CALL(node, updateRwSetting(std::string{"signer"}, - Truly([](const common::SettingsValue& v) { - return v.isStr() && v.get_str() == "/usr/local/bin/hwi"; - }))); - model.setExternalSignerPath("/usr/local/bin/hwi"); QCOMPARE(model.externalSignerPath(), QString("/usr/local/bin/hwi")); + const auto* signer_write{FindSettingWrite(node, "signer")}; + QVERIFY(signer_write != nullptr && signer_write->isStr()); + QCOMPARE(signer_write->get_str(), std::string{"/usr/local/bin/hwi"}); + QCOMPARE(node.update_rw_setting_arguments.size(), 1U); } void OptionsModelTests::externalSignerPathClearedRemovesKey() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"signer"})) - .WillByDefault(Return(MakeAddress("/usr/local/bin/hwi"))); + MockNode node; + node.SetPersistentSetting("signer", MakeAddress("/usr/local/bin/hwi")); OptionsQmlModel model(node); QCOMPARE(model.externalSignerPath(), QString("/usr/local/bin/hwi")); - EXPECT_CALL(node, updateRwSetting(std::string{"signer"}, - Truly([](const common::SettingsValue& v) { return v.isNull(); }))); - model.setExternalSignerPath(""); QVERIFY(model.externalSignerPath().isEmpty()); + const auto* signer_write{FindSettingWrite(node, "signer")}; + QVERIFY(signer_write != nullptr); + QVERIFY(signer_write->isNull()); + QCOMPARE(node.update_rw_setting_arguments.size(), 1U); } void OptionsModelTests::walletSettingsDirtyTracksExternalSignerPath() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.walletSettingsDirty()); @@ -634,14 +567,8 @@ void OptionsModelTests::walletSettingsDirtyTracksExternalSignerPath() void OptionsModelTests::signerPathLoadedFromSettings() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"signer"})) - .WillByDefault(Return(MakeAddress("/opt/hwi/ledger.py"))); + MockNode node; + node.SetPersistentSetting("signer", MakeAddress("/opt/hwi/ledger.py")); OptionsQmlModel model(node); QCOMPARE(model.externalSignerPath(), QString("/opt/hwi/ledger.py")); @@ -649,34 +576,22 @@ void OptionsModelTests::signerPathLoadedFromSettings() void OptionsModelTests::signerPathWritesSetting() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(model.externalSignerPath().isEmpty()); - EXPECT_CALL(node, updateRwSetting(std::string{"signer"}, - Truly([](const common::SettingsValue& v) { - return v.isStr() && v.get_str() == "/opt/hwi/ledger.py"; - }))); - model.setExternalSignerPath("/opt/hwi/ledger.py"); QCOMPARE(model.externalSignerPath(), QString("/opt/hwi/ledger.py")); + const auto* signer_write{FindSettingWrite(node, "signer")}; + QVERIFY(signer_write != nullptr && signer_write->isStr()); + QCOMPARE(signer_write->get_str(), std::string{"/opt/hwi/ledger.py"}); + QCOMPARE(node.update_rw_setting_arguments.size(), 1U); } void OptionsModelTests::signerDirtySetAtRuntime() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.walletSettingsDirty()); @@ -687,12 +602,7 @@ void OptionsModelTests::signerDirtySetAtRuntime() void OptionsModelTests::signerDirtyResetWhenReverted() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.walletSettingsDirty()); @@ -706,12 +616,7 @@ void OptionsModelTests::signerDirtyResetWhenReverted() void OptionsModelTests::externalSignerPathValidationRejectsMissingPath() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QCOMPARE(model.externalSignerPathValidationError("/definitely/not/a/real/signer"), @@ -720,12 +625,7 @@ void OptionsModelTests::externalSignerPathValidationRejectsMissingPath() void OptionsModelTests::externalSignerPathValidationAcceptsExecutablePath() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; QTemporaryDir temp_dir; QVERIFY(temp_dir.isValid()); @@ -743,13 +643,8 @@ void OptionsModelTests::externalSignerPathValidationAcceptsExecutablePath() void OptionsModelTests::connectionDirtyTracksRestartSettings() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"listen"})).WillByDefault(Return(common::SettingsValue{true})); + MockNode node; + node.SetPersistentSetting("listen", common::SettingsValue{true}); OptionsQmlModel model(node); QVERIFY(!model.connectionSettingsDirty()); @@ -766,30 +661,21 @@ void OptionsModelTests::connectionDirtyTracksRestartSettings() void OptionsModelTests::natpmpAppliesLiveWithoutRestartDirty() { - using ::testing::_; - using ::testing::InSequence; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(model.natpmp()); - InSequence sequence; - EXPECT_CALL(node, updateRwSetting(std::string{"natpmp"}, - Truly([](const common::SettingsValue& value) { - const std::optional parsed = SettingToBool(value); - return parsed.has_value() && !*parsed; - }))); - EXPECT_CALL(node, mapPort(false)); - EXPECT_CALL(node, updateRwSetting(std::string{"natpmp"}, - Truly([](const common::SettingsValue& value) { - const std::optional parsed = SettingToBool(value); - return value.isNull() || (parsed.has_value() && *parsed); - }))); - EXPECT_CALL(node, mapPort(true)); + std::vector events; + node.update_rw_setting_fn = [&](const std::string& name, const common::SettingsValue& value) { + if (name == "natpmp") { + const auto parsed{SettingToBool(value)}; + events.push_back(value.isNull() ? QStringLiteral("write:null") : parsed && *parsed ? QStringLiteral("write:true") : + QStringLiteral("write:false")); + } + }; + node.map_port_fn = [&](bool enabled) { + events.push_back(enabled ? QStringLiteral("map:true") : QStringLiteral("map:false")); + }; model.setNatpmp(false); QVERIFY(!model.natpmp()); @@ -802,16 +688,16 @@ void OptionsModelTests::natpmpAppliesLiveWithoutRestartDirty() QVERIFY(!model.connectionSettingsDirty()); QVERIFY(!model.restartRequired()); QTest::qWait(300); + QCOMPARE(events.size(), 4U); + QCOMPARE(events.at(0), QStringLiteral("write:false")); + QCOMPARE(events.at(1), QStringLiteral("map:false")); + QVERIFY(events.at(2) == QStringLiteral("write:null") || events.at(2) == QStringLiteral("write:true")); + QCOMPARE(events.at(3), QStringLiteral("map:true")); } void OptionsModelTests::storageDirtyIgnoresDisabledPruneSize() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.prune()); @@ -825,40 +711,28 @@ void OptionsModelTests::storageDirtyIgnoresDisabledPruneSize() void OptionsModelTests::pruneDisabledPreservesPreviousValue() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"prune"})) - .WillByDefault(Return(MakeInt(QmlCoreSettings::PruneGBToMiB(10)))); + MockNode node; + node.SetPersistentSetting("prune", MakeInt(QmlCoreSettings::PruneGBToMiB(10))); OptionsQmlModel model(node); QVERIFY(model.prune()); QCOMPARE(model.pruneSizeGB(), 10); - EXPECT_CALL(node, updateRwSetting(std::string{"prune-prev"}, - Truly([](const common::SettingsValue& value) { - return value.isStr() && value.get_str() == std::to_string(QmlCoreSettings::PruneGBToMiB(10)); - }))); - EXPECT_CALL(node, updateRwSetting(std::string{"prune"}, - Truly([](const common::SettingsValue& value) { return value.isNull(); }))); - model.setPrune(false); QVERIFY(!model.prune()); QCOMPARE(model.pruneSizeGB(), 10); + QCOMPARE(node.update_rw_setting_arguments.size(), 2U); + const auto* previous_write{FindSettingWrite(node, "prune-prev")}; + QVERIFY(previous_write != nullptr && previous_write->isStr()); + QCOMPARE(previous_write->get_str(), std::to_string(QmlCoreSettings::PruneGBToMiB(10))); + const auto* prune_write{FindSettingWrite(node, "prune")}; + QVERIFY(prune_write != nullptr); + QVERIFY(prune_write->isNull()); } void OptionsModelTests::developerDirtyTracksRestartSettings() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.developerSettingsDirty()); @@ -874,12 +748,7 @@ void OptionsModelTests::developerDirtyTracksRestartSettings() void OptionsModelTests::mempoolDirtyTracksRestartSettings() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.developerSettingsDirty()); @@ -899,12 +768,7 @@ void OptionsModelTests::mempoolDirtyTracksRestartSettings() void OptionsModelTests::proxyValidationAndCommit() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(model.validateProxyLocation("127.0.0.1:9050").isEmpty()); @@ -933,33 +797,23 @@ void OptionsModelTests::proxyValidationAndCommit() void OptionsModelTests::proxyDisabledPreservesPreviousValue() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::Truly; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"proxy"})) - .WillByDefault(Return(MakeAddress("127.0.0.1:9050"))); + MockNode node; + node.SetPersistentSetting("proxy", MakeAddress("127.0.0.1:9050")); OptionsQmlModel model(node); - EXPECT_CALL(node, updateRwSetting(std::string{"proxy-prev"}, - Truly([](const common::SettingsValue& v) { - return v.isStr() && v.get_str() == "127.0.0.1:9050"; - }))); - EXPECT_CALL(node, updateRwSetting(std::string{"proxy"}, - Truly([](const common::SettingsValue& v) { return v.isNull(); }))); - model.setProxyEnabled(false); QVERIFY(!model.proxyEnabled()); + QCOMPARE(node.update_rw_setting_arguments.size(), 2U); + const auto* previous_write{FindSettingWrite(node, "proxy-prev")}; + QVERIFY(previous_write != nullptr && previous_write->isStr()); + QCOMPARE(previous_write->get_str(), std::string{"127.0.0.1:9050"}); + const auto* proxy_write{FindSettingWrite(node, "proxy")}; + QVERIFY(proxy_write != nullptr); + QVERIFY(proxy_write->isNull()); } void OptionsModelTests::customDataDirValidationRejectsFile() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; QTemporaryDir temp_dir; QVERIFY(temp_dir.isValid()); @@ -968,8 +822,7 @@ void OptionsModelTests::customDataDirValidationRejectsFile() QVERIFY(file.open(QIODevice::WriteOnly)); file.close(); - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(!model.validateCustomDataDir(file_path).isEmpty()); @@ -978,9 +831,6 @@ void OptionsModelTests::customDataDirValidationRejectsFile() void OptionsModelTests::customDataDirSelectionCreatesDirectoryAndPersists() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; SavedGuiDataDirSettings saved_settings; QSettings settings; @@ -992,8 +842,7 @@ void OptionsModelTests::customDataDirSelectionCreatesDirectoryAndPersists() const QString data_dir = QDir(temp_dir.path()).filePath("selected-data-dir"); QVERIFY(!QFileInfo::exists(data_dir)); - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(model.validateCustomDataDir(data_dir).isEmpty()); @@ -1008,9 +857,6 @@ void OptionsModelTests::customDataDirSelectionCreatesDirectoryAndPersists() void OptionsModelTests::customDataDirSelectionPreservesExistingWalletDiscovery() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; SavedGuiDataDirSettings saved_settings; QSettings settings; @@ -1024,8 +870,7 @@ void OptionsModelTests::customDataDirSelectionPreservesExistingWalletDiscovery() const QString wallets_dir = QDir(data_dir).filePath("wallets"); QVERIFY(!QFileInfo::exists(wallets_dir)); - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QVERIFY(model.validateCustomDataDir(data_dir).isEmpty()); @@ -1134,7 +979,6 @@ void OptionsModelTests::guiDataDirSettingSkipsExplicitDatadir() void OptionsModelTests::runtimeDataDirUsesExplicitDatadirOverSavedGuiSetting() { - using ::testing::NiceMock; SavedGuiDataDirSettings saved_settings; QSettings settings; @@ -1147,7 +991,7 @@ void OptionsModelTests::runtimeDataDirUsesExplicitDatadirOverSavedGuiSetting() ArgsManager args; PrepareArgsForDataDir(args, explicit_data_dir.path()); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2222,16 +2066,12 @@ void OptionsModelTests::storageSpaceCheckRejectsExistingFile() void OptionsModelTests::thirdPartyTransactionLinksParseValidUrls() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; QSettings settings; const bool had_urls_setting = settings.contains(SettingsKeys::THIRD_PARTY_TRANSACTION_URLS); const QVariant previous_urls_setting = settings.value(SettingsKeys::THIRD_PARTY_TRANSACTION_URLS); - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); model.setThirdPartyTransactionUrls( @@ -2262,17 +2102,13 @@ void OptionsModelTests::thirdPartyTransactionLinksParseValidUrls() void OptionsModelTests::moneyFontChoicePersists() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; QSettings settings; const bool had_font_setting = settings.contains(SettingsKeys::MONEY_FONT_CHOICE); const QVariant previous_font_setting = settings.value(SettingsKeys::MONEY_FONT_CHOICE); settings.remove(SettingsKeys::MONEY_FONT_CHOICE); - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QCOMPARE(model.moneyFontChoice(), QString("embedded")); @@ -2289,17 +2125,13 @@ void OptionsModelTests::moneyFontChoicePersists() void OptionsModelTests::displayUnitUsesQtCompatibleSettingsKey() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; QSettings settings; const bool had_display_unit_setting = settings.contains(SettingsKeys::DISPLAY_UNIT); const QVariant previous_display_unit_setting = settings.value(SettingsKeys::DISPLAY_UNIT); settings.setValue(SettingsKeys::DISPLAY_UNIT, QVariant::fromValue(LegacyDisplayUnit::SAT)); - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); + MockNode node; OptionsQmlModel model(node); QCOMPARE(QString::fromUtf8(SettingsKeys::DISPLAY_UNIT), QStringLiteral("DisplayBitcoinUnit")); @@ -2332,7 +2164,6 @@ void OptionsModelTests::displayUnitUsesQtCompatibleSettingsKey() void OptionsModelTests::displayUnitUsesLegacyQtFallback() { - using ::testing::NiceMock; SavedSettingsFormat settings_format{QSettings::IniFormat}; SavedGuiDataDirSettings saved_settings; @@ -2354,7 +2185,7 @@ DisplayBitcoinUnit=@Variant(\0\0\0\x7f\0\0\0\fBitcoinUnit\0\x3) QVERIFY2(PrepareTestArgs(args, TestArgv(), parse_error), parse_error.c_str()); args.SelectConfigNetwork(args.GetChainTypeString()); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2365,7 +2196,6 @@ DisplayBitcoinUnit=@Variant(\0\0\0\x7f\0\0\0\fBitcoinUnit\0\x3) void OptionsModelTests::displayUnitPrefersQmlSettingOverLegacyQtFallback() { - using ::testing::NiceMock; SavedGuiDataDirSettings saved_settings; SavedNamedSettings legacy_settings{QStringLiteral("Bitcoin"), QStringLiteral("Bitcoin-Qt-regtest")}; @@ -2379,7 +2209,7 @@ void OptionsModelTests::displayUnitPrefersQmlSettingOverLegacyQtFallback() QVERIFY2(PrepareTestArgs(args, TestArgv(), parse_error), parse_error.c_str()); args.SelectConfigNetwork(args.GetChainTypeString()); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2496,13 +2326,12 @@ void OptionsModelTests::coreSettingsLegacyNumericOverridesWriteStrings() void OptionsModelTests::coreSettingsModelEntryMutatesRuntimeModel() { - using ::testing::NiceMock; ArgsManager args; std::string error; QVERIFY(PrepareTestArgs(args, TestArgv(), error)); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); InstallRwSettingsWriter(node, args); @@ -2684,14 +2513,8 @@ void OptionsModelTests::coreSettingsSessionProxyDisabledPreservesPreviousValue() void OptionsModelTests::coreSettingsLoadPersistentPrunePreviousValue() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"prune-prev"})) - .WillByDefault(Return(MakeInt(QmlCoreSettings::PruneGBToMiB(10)))); + MockNode node; + node.SetPersistentSetting("prune-prev", MakeInt(QmlCoreSettings::PruneGBToMiB(10))); const QmlCoreSettings::Values values = QmlCoreSettings::LoadPersistentValues(node); QVERIFY(!values.prune); @@ -2749,7 +2572,6 @@ void OptionsModelTests::coreSettingsSessionProxyCommitAcceptsUnchangedAddressWit void OptionsModelTests::coreSettingStatusTracksSourcePrecedence() { - using ::testing::NiceMock; { ArgsManager args; @@ -2758,7 +2580,7 @@ void OptionsModelTests::coreSettingStatusTracksSourcePrecedence() settings.ro_config[""]["listen"].push_back(common::SettingsValue{true}); }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2777,7 +2599,7 @@ void OptionsModelTests::coreSettingStatusTracksSourcePrecedence() settings.rw_settings["listen"] = common::SettingsValue{false}; }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2795,7 +2617,7 @@ void OptionsModelTests::coreSettingStatusTracksSourcePrecedence() settings.command_line_options["listen"].push_back(common::SettingsValue{true}); }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2809,12 +2631,11 @@ void OptionsModelTests::coreSettingStatusTracksSourcePrecedence() void OptionsModelTests::runtimeCoreSettingStatusesRefreshAfterWrite() { - using ::testing::NiceMock; ArgsManager args; args.SelectConfigNetwork("main"); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); InstallRwSettingsWriter(node, args); @@ -2831,8 +2652,6 @@ void OptionsModelTests::runtimeCoreSettingStatusesRefreshAfterWrite() void OptionsModelTests::commandLineOverriddenSettingDoesNotWrite() { - using ::testing::_; - using ::testing::NiceMock; ArgsManager args; args.SelectConfigNetwork("main"); @@ -2841,7 +2660,7 @@ void OptionsModelTests::commandLineOverriddenSettingDoesNotWrite() settings.ro_config[""]["listen"].push_back(common::SettingsValue{true}); }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2849,16 +2668,15 @@ void OptionsModelTests::commandLineOverriddenSettingDoesNotWrite() QCOMPARE(model.coreSettingStatus(QStringLiteral("listen")).value("source").toString(), QString("command_line")); QCOMPARE(model.coreSettingStatus(QStringLiteral("listen")).value("canEdit").toBool(), false); - EXPECT_CALL(node, updateRwSetting(std::string{"listen"}, _)).Times(0); + node.update_rw_setting_arguments.clear(); model.setListen(true); QVERIFY(!model.listen()); + QCOMPARE(SettingWriteCount(node, "listen"), 0); } void OptionsModelTests::runtimeCommandLineOverridesDisplayEffectiveValues() { - using ::testing::_; - using ::testing::NiceMock; ArgsManager args; args.SelectConfigNetwork("main"); @@ -2881,7 +2699,7 @@ void OptionsModelTests::runtimeCommandLineOverridesDisplayEffectiveValues() settings.command_line_options["signer"].push_back(common::SettingsValue{std::string{"cli-signer"}}); }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -2906,9 +2724,6 @@ void OptionsModelTests::runtimeCommandLineOverridesDisplayEffectiveValues() void OptionsModelTests::runtimeParameterInteractionsDisplayEffectiveValues() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Truly; std::vector proxy_argv = TestArgv(); proxy_argv.emplace_back("-proxy=127.0.0.1:9050"); @@ -2920,7 +2735,7 @@ void OptionsModelTests::runtimeParameterInteractionsDisplayEffectiveValues() proxy_args.SelectConfigNetwork(proxy_args.GetChainTypeString()); InitParameterInteraction(proxy_args); - NiceMock proxy_node; + MockNode proxy_node; InstallPersistentSettings(proxy_node, proxy_args); InstallRwSettingsWriter(proxy_node, proxy_args); @@ -2942,23 +2757,25 @@ void OptionsModelTests::runtimeParameterInteractionsDisplayEffectiveValues() QCOMPARE(listen_entry->status().value("hasRwSetting").toBool(), false); QSignalSpy listen_entry_status_spy(listen_entry, &CoreSettingEntryModel::statusChanged); - EXPECT_CALL(proxy_node, updateRwSetting(std::string{"listen"}, - Truly([](const common::SettingsValue& value) { - return value.isBool() && value.get_bool(); - }))); + proxy_node.update_rw_setting_arguments.clear(); proxy_model.setListen(true); QVERIFY(proxy_model.listen()); QCOMPARE(SettingToBool(proxy_args.GetPersistentSetting("listen")), true); QCOMPARE(proxy_model.coreSettingStatus(QStringLiteral("listen")).value("hasRwSetting").toBool(), true); QCOMPARE(listen_entry->status().value("hasRwSetting").toBool(), true); QVERIFY(listen_entry_status_spy.count() >= 1); + QCOMPARE(proxy_node.update_rw_setting_arguments.size(), 1U); + const auto* enabled_write{FindSettingWrite(proxy_node, "listen")}; + QVERIFY(enabled_write != nullptr && enabled_write->isBool()); + QVERIFY(enabled_write->get_bool()); - EXPECT_CALL(proxy_node, updateRwSetting(std::string{"listen"}, - Truly([](const common::SettingsValue& value) { - return value.isNull(); - }))); + proxy_node.update_rw_setting_arguments.clear(); proxy_model.setListen(false); QVERIFY(!proxy_model.listen()); + QCOMPARE(proxy_node.update_rw_setting_arguments.size(), 1U); + const auto* disabled_write{FindSettingWrite(proxy_node, "listen")}; + QVERIFY(disabled_write != nullptr); + QVERIFY(disabled_write->isNull()); bool has_listen_override{true}; proxy_args.LockSettings([&](common::Settings& settings) { @@ -2975,7 +2792,7 @@ void OptionsModelTests::runtimeParameterInteractionsDisplayEffectiveValues() blocksonly_args.SelectConfigNetwork(blocksonly_args.GetChainTypeString()); InitParameterInteraction(blocksonly_args); - NiceMock blocksonly_node; + MockNode blocksonly_node; InstallPersistentSettings(blocksonly_node, blocksonly_args); OptionsQmlModel blocksonly_model(blocksonly_node, blocksonly_args); @@ -2989,8 +2806,6 @@ void OptionsModelTests::runtimeParameterInteractionsDisplayEffectiveValues() void OptionsModelTests::commandLineOverriddenSettingsPreservePersistentValues() { - using ::testing::_; - using ::testing::NiceMock; ArgsManager args; args.SelectConfigNetwork("main"); @@ -3011,12 +2826,12 @@ void OptionsModelTests::commandLineOverriddenSettingsPreservePersistentValues() settings.command_line_options["signer"].push_back(common::SettingsValue{std::string{"cli-signer"}}); }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); - EXPECT_CALL(node, updateRwSetting(_, _)).Times(0); - EXPECT_CALL(node, forceSetting(_, _)).Times(0); + node.update_rw_setting_arguments.clear(); + node.force_setting_arguments.clear(); model.setServer(false); model.setPrune(false); @@ -3033,13 +2848,12 @@ void OptionsModelTests::commandLineOverriddenSettingsPreservePersistentValues() QCOMPARE(SettingTo(args.GetPersistentSetting("par"), -1), 1); QCOMPARE(SettingTo(args.GetPersistentSetting("maxmempool"), -1), 300); QCOMPARE(QString::fromStdString(SettingToString(args.GetPersistentSetting("signer"), "")), QString("saved-signer")); + QVERIFY(node.update_rw_setting_arguments.empty()); + QVERIFY(node.force_setting_arguments.empty()); } void OptionsModelTests::revertingToConfigValueDeletesRwOverride() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Truly; ArgsManager args; args.SelectConfigNetwork("main"); @@ -3048,24 +2862,23 @@ void OptionsModelTests::revertingToConfigValueDeletesRwOverride() settings.rw_settings["listen"] = common::SettingsValue{false}; }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); InstallRwSettingsWriter(node, args); OptionsQmlModel model(node, args); QVERIFY(!model.listen()); - EXPECT_CALL(node, updateRwSetting(std::string{"listen"}, - Truly([](const common::SettingsValue& value) { return value.isNull(); }))); - + node.update_rw_setting_arguments.clear(); model.setListen(true); + QCOMPARE(node.update_rw_setting_arguments.size(), 1U); + const auto* listen_write{FindSettingWrite(node, "listen")}; + QVERIFY(listen_write != nullptr); + QVERIFY(listen_write->isNull()); } void OptionsModelTests::revertingToDefaultValueDeletesRwOverride() { - using ::testing::_; - using ::testing::NiceMock; - using ::testing::Truly; ArgsManager args; args.SelectConfigNetwork("main"); @@ -3073,23 +2886,23 @@ void OptionsModelTests::revertingToDefaultValueDeletesRwOverride() settings.rw_settings["maxmempool"] = common::SettingsValue{456}; }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); InstallRwSettingsWriter(node, args); OptionsQmlModel model(node, args); QCOMPARE(model.maxMempoolSizeMB(), 456); - EXPECT_CALL(node, updateRwSetting(std::string{"maxmempool"}, - Truly([](const common::SettingsValue& value) { return value.isNull(); }))); - + node.update_rw_setting_arguments.clear(); model.setMaxMempoolSizeMB(DEFAULT_MAX_MEMPOOL_SIZE_MB); + QCOMPARE(node.update_rw_setting_arguments.size(), 1U); + const auto* mempool_write{FindSettingWrite(node, "maxmempool")}; + QVERIFY(mempool_write != nullptr); + QVERIFY(mempool_write->isNull()); } void OptionsModelTests::languageCommandLineOverrideDoesNotPersist() { - using ::testing::_; - using ::testing::NiceMock; QSettings settings; const bool had_language_setting = settings.contains(SettingsKeys::LANGUAGE); @@ -3102,7 +2915,7 @@ void OptionsModelTests::languageCommandLineOverrideDoesNotPersist() settings.command_line_options["lang"].push_back(common::SettingsValue{std::string{"fr"}}); }); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); @@ -3122,7 +2935,6 @@ void OptionsModelTests::languageCommandLineOverrideDoesNotPersist() void OptionsModelTests::legacyQtSettingsMigrateToCoreSettings() { - using ::testing::NiceMock; SavedNamedSettings qml_core_settings{QStringLiteral("BitcoinCore"), QStringLiteral("BitcoinCore-App-regtest")}; SavedNamedSettings legacy_settings{QStringLiteral("Bitcoin"), QStringLiteral("Bitcoin-Qt-regtest")}; @@ -3204,7 +3016,7 @@ void OptionsModelTests::legacyQtSettingsMigrateToCoreSettings() QVERIFY(!settings.contains(QStringLiteral("addrSeparateProxyTor"))); QVERIFY(!settings.contains(QStringLiteral("language"))); - NiceMock node; + MockNode node; InstallPersistentSettings(node, args); OptionsQmlModel model(node, args); QCOMPARE(model.language(), QStringLiteral("de")); diff --git a/test/test_peerlistmodel.cpp b/test/test_peerlistmodel.cpp index 63e4a6bb35..7ee9b7fac7 100644 --- a/test/test_peerlistmodel.cpp +++ b/test/test_peerlistmodel.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -44,7 +43,7 @@ constexpr auto AUTO_REFRESH_TRIGGER_TIMEOUT{2'000}; constexpr auto AUTO_REFRESH_STOP_WAIT{450}; } // namespace -class PeerListModelTests : public GmockTestFixture +class PeerListModelTests : public QObject { Q_OBJECT @@ -58,17 +57,12 @@ private Q_SLOTS: void PeerListModelTests::mapsRoleData() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - const auto stats{MakeStats({MakeNodeStats(7, "127.0.0.1:8333", false, ConnectionType::OUTBOUND_FULL_RELAY, NET_IPV4)})}; - NiceMock node; - EXPECT_CALL(node, getNodesStats(_)) - .Times(1) - .WillOnce(DoAll(SetArgReferee<0>(stats), Return(true))); + MockNode node; + node.get_nodes_stats_fn = [&](interfaces::Node::NodesStats& result) { + result = stats; + return true; + }; PeerListModel model{node, nullptr}; QCOMPARE(model.rowCount(), 1); @@ -101,16 +95,11 @@ void PeerListModelTests::mapsRoleData() QCOMPARE(model.flags(QModelIndex{}), Qt::NoItemFlags); QVERIFY(model.flags(index).testFlag(Qt::ItemIsSelectable)); QVERIFY(model.flags(index).testFlag(Qt::ItemIsEnabled)); + QCOMPARE(node.calls.getNodesStats.load(), 1); } void PeerListModelTests::refreshUpdatesRows() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - const auto stats_initial{MakeStats({ MakeNodeStats(1, "10.0.0.1:8333", true, ConnectionType::INBOUND, NET_IPV4), MakeNodeStats(2, "10.0.0.2:8333", false, ConnectionType::MANUAL, NET_IPV6), @@ -123,12 +112,13 @@ void PeerListModelTests::refreshUpdatesRows() MakeNodeStats(3, "10.0.0.3:8333", false, ConnectionType::BLOCK_RELAY, NET_ONION), })}; - NiceMock node; - EXPECT_CALL(node, getNodesStats(_)) - .Times(3) - .WillOnce(DoAll(SetArgReferee<0>(stats_initial), Return(true))) - .WillOnce(DoAll(SetArgReferee<0>(stats_remove), Return(true))) - .WillOnce(DoAll(SetArgReferee<0>(stats_insert), Return(true))); + MockNode node; + const std::vector responses{stats_initial, stats_remove, stats_insert}; + size_t response_index{0}; + node.get_nodes_stats_fn = [&](interfaces::Node::NodesStats& result) { + result = responses.at(response_index++); + return true; + }; PeerListModel model{node, nullptr}; QCOMPARE(model.rowCount(), 2); @@ -143,23 +133,21 @@ void PeerListModelTests::refreshUpdatesRows() QCOMPARE(model.rowCount(), 2); QCOMPARE(model.data(model.index(0, 0), PeerListModel::NetNodeId).toLongLong(), 2LL); QCOMPARE(model.data(model.index(1, 0), PeerListModel::NetNodeId).toLongLong(), 3LL); + QCOMPARE(node.calls.getNodesStats.load(), 3); } void PeerListModelTests::refreshHandlesGetNodesStatsFailure() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - const auto stats{MakeStats({MakeNodeStats(1, "10.0.0.1:8333", true, ConnectionType::INBOUND, NET_IPV4)})}; - NiceMock node; - EXPECT_CALL(node, getNodesStats(_)) - .Times(2) - .WillOnce(DoAll(SetArgReferee<0>(stats), Return(true))) - .WillOnce(Return(false)); + MockNode node; + node.get_nodes_stats_fn = [&](interfaces::Node::NodesStats& result) { + if (node.calls.getNodesStats.load() == 1) { + result = stats; + return true; + } + return false; + }; PeerListModel model{node, nullptr}; QCOMPARE(model.rowCount(), 1); @@ -168,26 +156,20 @@ void PeerListModelTests::refreshHandlesGetNodesStatsFailure() model.refresh(); QCOMPARE(model.rowCount(), 1); QCOMPARE(model.data(model.index(0, 0), PeerListModel::NetNodeId).toLongLong(), 1LL); + QCOMPARE(node.calls.getNodesStats.load(), 2); } void PeerListModelTests::startStopAutoRefresh() { - using ::testing::_; - using ::testing::AtLeast; - using ::testing::Invoke; - using ::testing::NiceMock; - const auto stats{MakeStats({MakeNodeStats(1, "10.0.0.1:8333", true, ConnectionType::INBOUND, NET_IPV4)})}; - NiceMock node; + MockNode node; int get_nodes_stats_calls{0}; - EXPECT_CALL(node, getNodesStats(_)) - .Times(AtLeast(2)) - .WillRepeatedly(Invoke([&](interfaces::Node::NodesStats& out_stats) { - ++get_nodes_stats_calls; - out_stats = stats; - return true; - })); + node.get_nodes_stats_fn = [&](interfaces::Node::NodesStats& out_stats) { + ++get_nodes_stats_calls; + out_stats = stats; + return true; + }; PeerListModel model{node, nullptr}; const int calls_after_ctor = get_nodes_stats_calls; @@ -200,16 +182,11 @@ void PeerListModelTests::startStopAutoRefresh() const int calls_after_stop = get_nodes_stats_calls; QTest::qWait(AUTO_REFRESH_STOP_WAIT); QCOMPARE(get_nodes_stats_calls, calls_after_stop); + QVERIFY(node.calls.getNodesStats.load() >= 2); } void PeerListModelTests::sortProxySortsByRoles() { - using ::testing::_; - using ::testing::DoAll; - using ::testing::NiceMock; - using ::testing::Return; - using ::testing::SetArgReferee; - auto stats_a = MakeNodeStats(10, "10.0.0.20:8333", false, ConnectionType::MANUAL, NET_IPV6); stats_a.m_connected = NodeClock::time_point{std::chrono::seconds{200}}; stats_a.m_min_ping_time = std::chrono::microseconds{5'000}; @@ -239,10 +216,11 @@ void PeerListModelTests::sortProxySortsByRoles() {stats_c.nodeid, stats_c}, }; - NiceMock node; - EXPECT_CALL(node, getNodesStats(_)) - .Times(1) - .WillOnce(DoAll(SetArgReferee<0>(stats), Return(true))); + MockNode node; + node.get_nodes_stats_fn = [&](interfaces::Node::NodesStats& result) { + result = stats; + return true; + }; PeerListModel model{node, nullptr}; PeerListSortProxy proxy{nullptr}; @@ -289,6 +267,7 @@ void PeerListModelTests::sortProxySortsByRoles() assert_sort("sent", [](const CNodeStats& left, const CNodeStats& right) { return left.nSendBytes < right.nSendBytes; }); assert_sort("received", [](const CNodeStats& left, const CNodeStats& right) { return left.nRecvBytes < right.nRecvBytes; }); assert_sort("subversion", [](const CNodeStats& left, const CNodeStats& right) { return left.cleanSubVer.compare(right.cleanSubVer) < 0; }); + QCOMPARE(node.calls.getNodesStats.load(), 1); } #ifdef BITCOINQML_NO_TEST_MAIN diff --git a/test/test_psbtqmlmodel.cpp b/test/test_psbtqmlmodel.cpp index 55374243ec..d63960de83 100644 --- a/test/test_psbtqmlmodel.cpp +++ b/test/test_psbtqmlmodel.cpp @@ -206,9 +206,10 @@ void PsbtQmlModelTests::savePreservesPsbtMetadata() void PsbtQmlModelTests::broadcastsCompletePsbt() { - testing::NiceMock node; - EXPECT_CALL(node, broadcastTransaction(testing::_, testing::_, testing::_)) - .WillOnce(testing::Return(node::TransactionError::OK)); + MockNode node; + node.broadcast_transaction_fn = [](CTransactionRef, CAmount, std::string&) { + return node::TransactionError::OK; + }; QTemporaryDir temp_dir; QVERIFY(temp_dir.isValid()); @@ -222,6 +223,7 @@ void PsbtQmlModelTests::broadcastsCompletePsbt() model.broadcast(); QVERIFY(model.status().startsWith(QStringLiteral("Transaction broadcast successfully."))); + QCOMPARE(node.calls.broadcastTransaction.load(), 1); } #ifdef BITCOINQML_NO_TEST_MAIN diff --git a/test/test_qmlinitexecutor_api.cpp b/test/test_qmlinitexecutor_api.cpp index 1372fca9a1..f735da93c8 100644 --- a/test/test_qmlinitexecutor_api.cpp +++ b/test/test_qmlinitexecutor_api.cpp @@ -2,15 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include - -#include #include -#include #include - +#include #include +#include + +#include #include Q_DECLARE_METATYPE(interfaces::BlockAndHeaderTipInfo) @@ -19,7 +18,7 @@ namespace { constexpr auto SIGNAL_TIMEOUT{5'000}; } -class QmlInitExecutorApiTests : public GmockTestFixture +class QmlInitExecutorApiTests : public QObject { Q_OBJECT @@ -38,14 +37,11 @@ void QmlInitExecutorApiTests::initTestCase() void QmlInitExecutorApiTests::initializeEmitsResultAndRunsOffMainThread() { - using ::testing::_; - using ::testing::Invoke; - using ::testing::StrictMock; - - StrictMock node; - bool ran_off_main_thread{false}; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + std::atomic_bool ran_off_main_thread{false}; - EXPECT_CALL(node, appInitMain(_)).WillOnce(Invoke([&](interfaces::BlockAndHeaderTipInfo* tip_info) { + node.app_init_main_fn = [&](interfaces::BlockAndHeaderTipInfo* tip_info) { ran_off_main_thread = QThread::currentThread() != QCoreApplication::instance()->thread(); tip_info->block_height = 101; tip_info->block_time = 1'700'000'001; @@ -53,7 +49,7 @@ void QmlInitExecutorApiTests::initializeEmitsResultAndRunsOffMainThread() tip_info->header_time = 1'700'000'099; tip_info->verification_progress = 0.75; return true; - })); + }; QmlInitExecutor executor{node}; QSignalSpy initialize_spy(&executor, &QmlInitExecutor::initializeResult); @@ -64,7 +60,8 @@ void QmlInitExecutorApiTests::initializeEmitsResultAndRunsOffMainThread() QVERIFY(initialize_spy.wait(SIGNAL_TIMEOUT)); QCOMPARE(initialize_spy.count(), 1); QCOMPARE(runaway_spy.count(), 0); - QVERIFY(ran_off_main_thread); + QVERIFY(ran_off_main_thread.load()); + QCOMPARE(node.calls.appInitMain.load(), 1); const QList arguments = initialize_spy.takeFirst(); QCOMPARE(arguments.at(0).toBool(), true); @@ -79,15 +76,12 @@ void QmlInitExecutorApiTests::initializeEmitsResultAndRunsOffMainThread() void QmlInitExecutorApiTests::initializeEmitsRunawayExceptionOnFailure() { - using ::testing::_; - using ::testing::Invoke; - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); - EXPECT_CALL(node, appInitMain(_)).WillOnce(Invoke([](interfaces::BlockAndHeaderTipInfo*) -> bool { + node.app_init_main_fn = [](interfaces::BlockAndHeaderTipInfo*) -> bool { throw std::runtime_error{"init failed"}; - })); + }; QmlInitExecutor executor{node}; QSignalSpy initialize_spy(&executor, &QmlInitExecutor::initializeResult); @@ -99,19 +93,18 @@ void QmlInitExecutorApiTests::initializeEmitsRunawayExceptionOnFailure() QCOMPARE(runaway_spy.count(), 1); QCOMPARE(initialize_spy.count(), 0); QCOMPARE(runaway_spy.takeFirst().at(0).toString(), QString{"init failed"}); + QCOMPARE(node.calls.appInitMain.load(), 1); } void QmlInitExecutorApiTests::shutdownEmitsResultAndRunsOffMainThread() { - using ::testing::Invoke; - using ::testing::StrictMock; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + std::atomic_bool ran_off_main_thread{false}; - StrictMock node; - bool ran_off_main_thread{false}; - - EXPECT_CALL(node, appShutdown()).WillOnce(Invoke([&] { + node.app_shutdown_fn = [&] { ran_off_main_thread = QThread::currentThread() != QCoreApplication::instance()->thread(); - })); + }; QmlInitExecutor executor{node}; QSignalSpy shutdown_spy(&executor, &QmlInitExecutor::shutdownResult); @@ -122,19 +115,18 @@ void QmlInitExecutorApiTests::shutdownEmitsResultAndRunsOffMainThread() QVERIFY(shutdown_spy.wait(SIGNAL_TIMEOUT)); QCOMPARE(shutdown_spy.count(), 1); QCOMPARE(runaway_spy.count(), 0); - QVERIFY(ran_off_main_thread); + QVERIFY(ran_off_main_thread.load()); + QCOMPARE(node.calls.appShutdown.load(), 1); } void QmlInitExecutorApiTests::shutdownEmitsRunawayExceptionOnFailure() { - using ::testing::Invoke; - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); - EXPECT_CALL(node, appShutdown()).WillOnce(Invoke([] { + node.app_shutdown_fn = [] { throw std::runtime_error{"shutdown failed"}; - })); + }; QmlInitExecutor executor{node}; QSignalSpy shutdown_spy(&executor, &QmlInitExecutor::shutdownResult); @@ -146,6 +138,7 @@ void QmlInitExecutorApiTests::shutdownEmitsRunawayExceptionOnFailure() QCOMPARE(runaway_spy.count(), 1); QCOMPARE(shutdown_spy.count(), 0); QCOMPARE(runaway_spy.takeFirst().at(0).toString(), QString{"shutdown failed"}); + QCOMPARE(node.calls.appShutdown.load(), 1); } #ifdef BITCOINQML_NO_TEST_MAIN diff --git a/test/test_rpcconsolemodel.cpp b/test/test_rpcconsolemodel.cpp index 92ea515f0b..a13e3b716e 100644 --- a/test/test_rpcconsolemodel.cpp +++ b/test/test_rpcconsolemodel.cpp @@ -11,143 +11,29 @@ // and executeRpc() is never reached. A minimal RpcTestStubNode that compiles is // therefore sufficient. -#include -#include -#include -#include - -#include - -#include - #include #include #include #include +#include +#include #include #include #include +#include +#include +#include +#include + +#include #include #include #include #include #include -// --------------------------------------------------------------------------- -// Minimal mock for interfaces::WalletLoader (required by RpcTestStubNode::walletLoader) -// --------------------------------------------------------------------------- -class MockWalletLoader : public interfaces::WalletLoader -{ -public: - // ChainClient - void registerRpcs() override {} - bool verify() override { return false; } - bool load() override { return false; } - void start(CScheduler&) override {} - void stop() override {} - void setMockTime(int64_t) override {} - void schedulerMockForward(std::chrono::seconds) override {} - - // WalletLoader - util::Result> createWallet( - const std::string&, const SecureString&, uint64_t, - std::vector&) override - { return util::Error{}; } - - util::Result> loadWallet( - const std::string&, std::vector&) override - { return util::Error{}; } - - std::string getWalletDir() override { return {}; } - - util::Result> restoreWallet( - const fs::path&, const std::string&, std::vector&, bool) override - { return util::Error{}; } - - util::Result migrateWallet( - const std::string&, const SecureString&) override - { return util::Error{}; } - - bool isEncrypted(const std::string&) override { return false; } - - std::vector> listWalletDir() override { return {}; } - - std::vector> getWallets() override { return {}; } - - std::unique_ptr handleLoadWallet(LoadWalletFn) override { return {}; } -}; - -// --------------------------------------------------------------------------- -// Minimal mock for interfaces::Node -// --------------------------------------------------------------------------- -class RpcTestStubNode : public interfaces::Node -{ -public: - // Trivial stubs — none are called during history tests. - void initLogging() override {} - void initParameterInteraction() override {} - bilingual_str getWarnings() override { return {}; } - int getExitStatus() override { return 0; } - BCLog::CategoryMask getLogCategories() override { return {}; } - bool baseInitialize() override { return false; } - bool appInitMain(interfaces::BlockAndHeaderTipInfo*) override { return false; } - void appShutdown() override {} - void startShutdown() override {} - bool shutdownRequested() override { return false; } - bool isSettingIgnored(const std::string&) override { return false; } - common::SettingsValue getPersistentSetting(const std::string&) override { return {}; } - void updateRwSetting(const std::string&, const common::SettingsValue&) override {} - void forceSetting(const std::string&, const common::SettingsValue&) override {} - void resetSettings() override {} - void mapPort(bool) override {} - std::optional getProxy(Network) override { return std::nullopt; } - size_t getNodeCount(ConnectionDirection) override { return 0; } - bool getNodesStats(NodesStats&) override { return false; } - bool getBanned(banmap_t&) override { return false; } - bool ban(const CNetAddr&, int64_t) override { return false; } - bool unban(const CSubNet&) override { return false; } - bool disconnectByAddress(const CNetAddr&) override { return false; } - bool disconnectById(NodeId) override { return false; } - std::vector> listExternalSigners() override { return {}; } - int64_t getTotalBytesRecv() override { return 0; } - int64_t getTotalBytesSent() override { return 0; } - size_t getMempoolSize() override { return 0; } - size_t getMempoolDynamicUsage() override { return 0; } - size_t getMempoolMaxUsage() override { return 0; } - bool getHeaderTip(int&, int64_t&) override { return false; } - int getNumBlocks() override { return 0; } - std::map getNetLocalAddresses() override { return {}; } - uint256 getBestBlockHash() override { return {}; } - int64_t getLastBlockTime() override { return 0; } - double getVerificationProgress() override { return 0.0; } - bool isInitialBlockDownload() override { return false; } - bool isLoadingBlocks() override { return false; } - void setNetworkActive(bool) override {} - bool getNetworkActive() override { return false; } - CFeeRate getDustRelayFee() override { return {}; } - UniValue executeRpc(const std::string&, const UniValue&, const std::string&) override { return {}; } - std::vector listRpcCommands() override { return {}; } - std::optional getUnspentOutput(const COutPoint&) override { return std::nullopt; } - node::TransactionError broadcastTransaction(CTransactionRef, CAmount, std::string&) override - { return {}; } - interfaces::WalletLoader& walletLoader() override { return m_wallet_loader; } - - std::unique_ptr handleInitMessage(InitMessageFn) override { return {}; } - std::unique_ptr handleMessageBox(MessageBoxFn) override { return {}; } - std::unique_ptr handleQuestion(QuestionFn) override { return {}; } - std::unique_ptr handleShowProgress(ShowProgressFn) override { return {}; } - std::unique_ptr handleInitWallet(InitWalletFn) override { return {}; } - std::unique_ptr handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn) override { return {}; } - std::unique_ptr handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn) override { return {}; } - std::unique_ptr handleNotifyAlertChanged(NotifyAlertChangedFn) override { return {}; } - std::unique_ptr handleBannedListChanged(BannedListChangedFn) override { return {}; } - std::unique_ptr handleNotifyBlockTip(NotifyBlockTipFn) override { return {}; } - std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn) override { return {}; } - -private: - MockWalletLoader m_wallet_loader; -}; +using RpcTestStubNode = StubNode; // --------------------------------------------------------------------------- // Tests diff --git a/test/test_unit_tests_main.cpp b/test/test_unit_tests_main.cpp index 0cf3743ff8..5d2eba830d 100644 --- a/test/test_unit_tests_main.cpp +++ b/test/test_unit_tests_main.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -13,8 +12,6 @@ const TranslateFn G_TRANSLATION_FUN{nullptr}; int main(int argc, char* argv[]) { - testing::InitGoogleMock(&argc, argv); - testing::UnitTest::GetInstance()->listeners().Append(new QtestGmockListener()); QApplication app(argc, argv); SelectParams(ChainType::REGTEST); diff --git a/test/test_walletlistmodel.cpp b/test/test_walletlistmodel.cpp index e8c9bc19ec..f78d0d64d1 100644 --- a/test/test_walletlistmodel.cpp +++ b/test/test_walletlistmodel.cpp @@ -12,8 +12,6 @@ #include #include -#include - #include namespace { @@ -58,13 +56,10 @@ class FakeWalletLoader : public interfaces::WalletLoader } }; -void ExpectWalletLoader(MockNode& node, FakeWalletLoader& loader) +void ConfigureExpectedWalletLoader(MockNode& node, FakeWalletLoader& loader) { - using ::testing::AtLeast; - using ::testing::ReturnRef; - - ON_CALL(node, walletLoader()).WillByDefault(ReturnRef(loader)); - EXPECT_CALL(node, walletLoader()).Times(AtLeast(1)).WillRepeatedly(ReturnRef(loader)); + node.wallet_loader_fn = [&loader]() -> interfaces::WalletLoader& { return loader; }; + node.ExpectAtLeast(node.calls.walletLoader, 1); } } // namespace @@ -99,15 +94,14 @@ void WalletListModelTests::init() void WalletListModelTests::listWalletDirMapsNameAndLoadStateRoles() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, {"beta_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -126,15 +120,14 @@ void WalletListModelTests::listWalletDirMapsNameAndLoadStateRoles() void WalletListModelTests::listWalletDirRemovesMissingEntries() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, {"beta_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -151,9 +144,8 @@ void WalletListModelTests::listWalletDirRemovesMissingEntries() void WalletListModelTests::listWalletDirSortsCaseInsensitivelyAndPreservesDuplicateRows() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"zulu_wallet", "sqlite"}, @@ -162,7 +154,7 @@ void WalletListModelTests::listWalletDirSortsCaseInsensitivelyAndPreservesDuplic {"alpha_wallet", "bdb"}, {"bravo_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -180,14 +172,13 @@ void WalletListModelTests::listWalletDirSortsCaseInsensitivelyAndPreservesDuplic void WalletListModelTests::displayNameRoleUsesStoredAlias() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); QSettings settings; settings.setValue("walletDisplayNames/alpha_wallet", "Personal"); @@ -201,15 +192,14 @@ void WalletListModelTests::displayNameRoleUsesStoredAlias() void WalletListModelTests::setWalletLoadStateUpdatesLoadStateRole() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, {"beta_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -232,9 +222,8 @@ void WalletListModelTests::setWalletLoadStateUpdatesLoadStateRole() void WalletListModelTests::setWalletLoadStateSortsLoadedRowsFirst() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"zulu_wallet", "sqlite"}, @@ -242,7 +231,7 @@ void WalletListModelTests::setWalletLoadStateSortsLoadedRowsFirst() {"bravo_wallet", "sqlite"}, {"charlie_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -270,15 +259,14 @@ void WalletListModelTests::setWalletLoadStateSortsLoadedRowsFirst() void WalletListModelTests::setWalletLoadStateBeforeListWalletDirSeedsInitialRows() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, {"beta_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.setWalletLoadState("beta_wallet", WalletListModel::LoadState::Open); @@ -295,14 +283,13 @@ void WalletListModelTests::setWalletLoadStateBeforeListWalletDirSeedsInitialRows void WalletListModelTests::setWalletLoadStateAddsNewLoadedWalletAfterInitialList() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -321,11 +308,10 @@ void WalletListModelTests::setWalletLoadStateAddsNewLoadedWalletAfterInitialList void WalletListModelTests::setWalletLoadStateRemovesOpenOnlyWalletOnUnload() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -352,15 +338,14 @@ void WalletListModelTests::setWalletLoadStateRemovesOpenOnlyWalletOnUnload() void WalletListModelTests::setWalletLoadStateLoadingExposesLoadingRoleAndClearsOnOpen() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, {"beta_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -381,14 +366,13 @@ void WalletListModelTests::setWalletLoadStateLoadingExposesLoadingRoleAndClearsO void WalletListModelTests::setWalletLoadStateLoadErrorExposesErrorMessageRoleAndClearsOnClosed() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -411,15 +395,14 @@ void WalletListModelTests::setWalletLoadStateLoadErrorExposesErrorMessageRoleAnd void WalletListModelTests::setWalletInfoUpdatesBalanceAndKeySchemeRolesForRowOnly() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, {"beta_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -449,14 +432,13 @@ void WalletListModelTests::setWalletInfoUpdatesBalanceAndKeySchemeRolesForRowOnl void WalletListModelTests::listWalletDirPreservesBalanceAndKeySchemeAcrossRebuilds() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = { {"alpha_wallet", "sqlite"}, }; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; model.listWalletDir(); @@ -480,11 +462,10 @@ void WalletListModelTests::listWalletDirPreservesBalanceAndKeySchemeAcrossRebuil void WalletListModelTests::walletDirLoadedFlipsAfterFirstList() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectWalletLoader(node, loader); + ConfigureExpectedWalletLoader(node, loader); WalletListModel model{node, nullptr}; QSignalSpy wallet_dir_loaded_spy(&model, &WalletListModel::walletDirLoadedChanged); diff --git a/test/test_walletqmlcontroller.cpp b/test/test_walletqmlcontroller.cpp index 89a00747d2..1a9149957a 100644 --- a/test/test_walletqmlcontroller.cpp +++ b/test/test_walletqmlcontroller.cpp @@ -16,8 +16,6 @@ #include #include -#include - #include #include #include @@ -259,21 +257,16 @@ class FakeWallet : public interfaces::Wallet State* m_state; }; -void ExpectControllerInitialization(MockNode& node, FakeWalletLoader& loader) +void ConfigureExpectedControllerInitialization(MockNode& node, FakeWalletLoader& loader) { - using ::testing::_; - using ::testing::AtLeast; - using ::testing::Invoke; - using ::testing::Return; - using ::testing::ReturnRef; - - ON_CALL(node, walletLoader()).WillByDefault(ReturnRef(loader)); - EXPECT_CALL(node, walletLoader()).Times(AtLeast(2)).WillRepeatedly(ReturnRef(loader)); - EXPECT_CALL(node, getPersistentSetting(_)).WillOnce(Return(common::SettingsValue{})); - EXPECT_CALL(node, forceSetting(_, _)).Times(1); - EXPECT_CALL(node, listExternalSigners()).WillOnce(Invoke([] { - return std::vector>{}; - })); + node.wallet_loader_fn = [&loader]() -> interfaces::WalletLoader& { return loader; }; + node.get_persistent_setting_fn = [](const std::string&) { return common::SettingsValue{}; }; + node.force_setting_fn = [](const std::string&, const common::SettingsValue&) {}; + node.list_external_signers_fn = [] { return std::vector>{}; }; + node.ExpectAtLeast(node.calls.walletLoader, 2); + node.ExpectExactly(node.calls.getPersistentSetting, 1); + node.ExpectExactly(node.calls.forceSetting, 1); + node.ExpectExactly(node.calls.listExternalSigners, 1); } const QString VALID_XPUB{ @@ -336,8 +329,7 @@ void WalletQmlControllerTests::initTestCase() void WalletQmlControllerTests::validateXpubAcceptsValidKey() { - using ::testing::NiceMock; - NiceMock node; + MockNode node; WalletQmlController controller(node); QVERIFY(controller.validateXpub(VALID_XPUB)); @@ -345,8 +337,7 @@ void WalletQmlControllerTests::validateXpubAcceptsValidKey() void WalletQmlControllerTests::validateXpubRejectsGarbage() { - using ::testing::NiceMock; - NiceMock node; + MockNode node; WalletQmlController controller(node); QVERIFY(!controller.validateXpub("not-an-xpub")); @@ -354,8 +345,7 @@ void WalletQmlControllerTests::validateXpubRejectsGarbage() void WalletQmlControllerTests::validateXpubRejectsEmpty() { - using ::testing::NiceMock; - NiceMock node; + MockNode node; WalletQmlController controller(node); QVERIFY(!controller.validateXpub("")); @@ -363,8 +353,7 @@ void WalletQmlControllerTests::validateXpubRejectsEmpty() void WalletQmlControllerTests::validateXpubTrimsWhitespace() { - using ::testing::NiceMock; - NiceMock node; + MockNode node; WalletQmlController controller(node); QVERIFY(controller.validateXpub(" " + VALID_XPUB + " ")); @@ -372,11 +361,10 @@ void WalletQmlControllerTests::validateXpubTrimsWhitespace() void WalletQmlControllerTests::createWatchOnlyInvalidXpubSetsError() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -391,11 +379,10 @@ void WalletQmlControllerTests::createWatchOnlyInvalidXpubSetsError() void WalletQmlControllerTests::createWatchOnlyCleansUpWhenDescriptorImportFails() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -433,15 +420,11 @@ void WalletQmlControllerTests::createWatchOnlyCleansUpWhenDescriptorImportFails( void WalletQmlControllerTests::externalSignerCreationRequiresConfiguredPath() { - using ::testing::_; - using ::testing::Invoke; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - EXPECT_CALL(node, listExternalSigners()) - .WillOnce(Invoke([] { return MakeSigners({"Ledger Nano X"}); })); + MockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + node.get_persistent_setting_fn = [](const std::string&) { return common::SettingsValue{}; }; + node.list_external_signers_fn = [] { return MakeSigners({"Ledger Nano X"}); }; + node.ExpectExactly(node.calls.listExternalSigners, 1); WalletQmlController controller(node); controller.refreshExternalSignerStatus(); @@ -453,17 +436,13 @@ void WalletQmlControllerTests::externalSignerCreationRequiresConfiguredPath() void WalletQmlControllerTests::externalSignerCreationRequiresExactlyOneSigner() { - using ::testing::_; - using ::testing::Invoke; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"signer"})) - .WillByDefault(Return(common::SettingsValue{std::string{"/usr/bin/hwi"}})); - EXPECT_CALL(node, listExternalSigners()) - .WillOnce(Invoke([] { return MakeSigners({"Signer A", "Signer B"}); })); + MockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + node.get_persistent_setting_fn = [](const std::string& name) { + return name == "signer" ? common::SettingsValue{std::string{"/usr/bin/hwi"}} : common::SettingsValue{}; + }; + node.list_external_signers_fn = [] { return MakeSigners({"Signer A", "Signer B"}); }; + node.ExpectExactly(node.calls.listExternalSigners, 1); WalletQmlController controller(node); controller.refreshExternalSignerStatus(); @@ -475,17 +454,13 @@ void WalletQmlControllerTests::externalSignerCreationRequiresExactlyOneSigner() void WalletQmlControllerTests::externalSignerSuggestionUsesSignerName() { - using ::testing::_; - using ::testing::Invoke; - using ::testing::NiceMock; - using ::testing::Return; - - NiceMock node; - ON_CALL(node, getPersistentSetting(_)).WillByDefault(Return(common::SettingsValue{})); - ON_CALL(node, getPersistentSetting(std::string{"signer"})) - .WillByDefault(Return(common::SettingsValue{std::string{"/usr/bin/hwi"}})); - EXPECT_CALL(node, listExternalSigners()) - .WillOnce(Invoke([] { return MakeSigners({"Coldcard Mk4"}); })); + MockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); + node.get_persistent_setting_fn = [](const std::string& name) { + return name == "signer" ? common::SettingsValue{std::string{"/usr/bin/hwi"}} : common::SettingsValue{}; + }; + node.list_external_signers_fn = [] { return MakeSigners({"Coldcard Mk4"}); }; + node.ExpectExactly(node.calls.listExternalSigners, 1); WalletQmlController controller(node); controller.refreshExternalSignerStatus(); @@ -497,12 +472,11 @@ void WalletQmlControllerTests::externalSignerSuggestionUsesSignerName() void WalletQmlControllerTests::initializedControllerSignalsMigrationForLegacyWallet() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"legacy_wallet", "bdb"}}; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -519,9 +493,8 @@ void WalletQmlControllerTests::initializedControllerSignalsMigrationForLegacyWal void WalletQmlControllerTests::createWalletBeforeInitializationReturnsFalseAndSetsError() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); WalletQmlController controller(node); controller.createSingleSigWallet("test_wallet", "secret"); @@ -532,9 +505,8 @@ void WalletQmlControllerTests::createWalletBeforeInitializationReturnsFalseAndSe void WalletQmlControllerTests::importWalletBeforeInitializationSetsLoadError() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); WalletQmlController controller(node); controller.importWallet("/tmp/test_wallet.dat"); @@ -543,9 +515,8 @@ void WalletQmlControllerTests::importWalletBeforeInitializationSetsLoadError() void WalletQmlControllerTests::migrateWalletBeforeInitializationSetsMigrationError() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); WalletQmlController controller(node); controller.migrateWallet("legacy_wallet", "secret"); @@ -554,9 +525,8 @@ void WalletQmlControllerTests::migrateWalletBeforeInitializationSetsMigrationErr void WalletQmlControllerTests::selectWalletBeforeInitializationSetsLoadError() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); WalletQmlController controller(node); controller.setSelectedWallet("test_wallet"); @@ -565,11 +535,10 @@ void WalletQmlControllerTests::selectWalletBeforeInitializationSetsLoadError() void WalletQmlControllerTests::initializedControllerPropagatesCreateErrors() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -601,12 +570,11 @@ void WalletQmlControllerTests::initializedControllerPropagatesCreateErrors() void WalletQmlControllerTests::initializedControllerForwardsMigrationPassphrase() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"legacy_wallet", "bdb"}}; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -633,11 +601,10 @@ void WalletQmlControllerTests::initializedControllerForwardsMigrationPassphrase( void WalletQmlControllerTests::initializedControllerForwardsUtf8CreatePassphrase() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -665,11 +632,10 @@ void WalletQmlControllerTests::initializedControllerForwardsUtf8CreatePassphrase void WalletQmlControllerTests::initializedControllerEmitsWalletCreateSucceeded() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -696,11 +662,10 @@ void WalletQmlControllerTests::initializedControllerEmitsWalletCreateSucceeded() void WalletQmlControllerTests::initializedControllerDoesNotCompleteCreateForUnrelatedLoadNotification() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -740,12 +705,11 @@ void WalletQmlControllerTests::initializedControllerDoesNotCompleteCreateForUnre void WalletQmlControllerTests::initializedControllerDoesNotCompleteLoadForUnrelatedLoadNotification() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"target_wallet", "sqlite"}}; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -783,11 +747,10 @@ void WalletQmlControllerTests::initializedControllerDoesNotCompleteLoadForUnrela void WalletQmlControllerTests::initializedControllerReportsCreateWarnings() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -810,11 +773,10 @@ void WalletQmlControllerTests::initializedControllerReportsCreateWarnings() void WalletQmlControllerTests::initializedControllerIgnoresDuplicateCreateWhileInProgress() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -839,9 +801,8 @@ void WalletQmlControllerTests::initializedControllerIgnoresDuplicateCreateWhileI void WalletQmlControllerTests::watchOnlyCreateBeforeInitializationSetsLoadError() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); WalletQmlController controller(node); controller.createWatchOnlyWallet("watch_only", VALID_XPUB); @@ -852,11 +813,10 @@ void WalletQmlControllerTests::watchOnlyCreateBeforeInitializationSetsLoadError( void WalletQmlControllerTests::watchOnlyCreateWhileWalletLoadInProgressIsIgnored() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -889,11 +849,10 @@ void WalletQmlControllerTests::watchOnlyCreateWhileWalletLoadInProgressIsIgnored void WalletQmlControllerTests::watchOnlyCreateFailureAfterCreateDoesNotPublishWallet() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -926,15 +885,14 @@ void WalletQmlControllerTests::watchOnlyCreateFailureAfterCreateDoesNotPublishWa void WalletQmlControllerTests::initializedControllerRequestsPassphraseBeforeEncryptedMigration() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"legacy_wallet", "bdb"}}; loader.is_encrypted_fn = [](const std::string& name) { return name == "legacy_wallet"; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -952,15 +910,14 @@ void WalletQmlControllerTests::initializedControllerRequestsPassphraseBeforeEncr void WalletQmlControllerTests::initializedControllerMigratesUnencryptedWalletWithoutPassphrase() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"legacy_wallet", "bdb"}}; loader.is_encrypted_fn = [](const std::string&) { return false; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -985,9 +942,8 @@ void WalletQmlControllerTests::initializedControllerMigratesUnencryptedWalletWit void WalletQmlControllerTests::initializedControllerClosesSelectedWalletAndSelectsRemainingLoadedWallet() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; FakeWallet::State alpha_state; FakeWallet::State beta_state; @@ -997,7 +953,7 @@ void WalletQmlControllerTests::initializedControllerClosesSelectedWalletAndSelec wallets.emplace_back(std::make_unique("beta_wallet", &beta_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1034,9 +990,8 @@ void WalletQmlControllerTests::initializedControllerClosesSelectedWalletAndSelec void WalletQmlControllerTests::initializedControllerClosesNonSelectedWalletWithoutChangingSelection() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; FakeWallet::State alpha_state; FakeWallet::State beta_state; @@ -1046,7 +1001,7 @@ void WalletQmlControllerTests::initializedControllerClosesNonSelectedWalletWitho wallets.emplace_back(std::make_unique("beta_wallet", &beta_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1070,9 +1025,8 @@ void WalletQmlControllerTests::initializedControllerClosesNonSelectedWalletWitho void WalletQmlControllerTests::initializedControllerEmitsWalletLoadStateChanged() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; FakeWallet::State alpha_state; FakeWallet::State beta_state; @@ -1082,7 +1036,7 @@ void WalletQmlControllerTests::initializedControllerEmitsWalletLoadStateChanged( wallets.emplace_back(std::make_unique("beta_wallet", &beta_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); QSignalSpy load_state_spy(&controller, &WalletQmlController::walletLoadStateChanged); @@ -1099,9 +1053,8 @@ void WalletQmlControllerTests::initializedControllerEmitsWalletLoadStateChanged( void WalletQmlControllerTests::initializedControllerHandlesExternalWalletUnload() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; FakeWallet::State alpha_state; FakeWallet::State beta_state; @@ -1111,7 +1064,7 @@ void WalletQmlControllerTests::initializedControllerHandlesExternalWalletUnload( wallets.emplace_back(std::make_unique("beta_wallet", &beta_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1136,9 +1089,8 @@ void WalletQmlControllerTests::initializedControllerHandlesExternalWalletUnload( void WalletQmlControllerTests::initializedControllerUnloadWalletsClearsSelectionAndOpenWallets() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; FakeWallet::State alpha_state; FakeWallet::State beta_state; @@ -1148,7 +1100,7 @@ void WalletQmlControllerTests::initializedControllerUnloadWalletsClearsSelection wallets.emplace_back(std::make_unique("beta_wallet", &beta_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1173,12 +1125,11 @@ void WalletQmlControllerTests::initializedControllerUnloadWalletsClearsSelection void WalletQmlControllerTests::initializedControllerEmitsLoadingThenLoadErrorOnFailedLoad() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"alpha_wallet", "sqlite"}}; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1207,9 +1158,8 @@ void WalletQmlControllerTests::initializedControllerEmitsLoadingThenLoadErrorOnF void WalletQmlControllerTests::publishOpenWalletsInfoEmitsWalletInfoChangedForEachOpenWallet() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; FakeWallet::State alpha_state; FakeWallet::State beta_state; @@ -1219,7 +1169,7 @@ void WalletQmlControllerTests::publishOpenWalletsInfoEmitsWalletInfoChangedForEa wallets.emplace_back(std::make_unique("beta_wallet", &beta_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1236,11 +1186,10 @@ void WalletQmlControllerTests::publishOpenWalletsInfoEmitsWalletInfoChangedForEa void WalletQmlControllerTests::walletNameAvailabilityErrorRejectsEmptyAndWhitespace() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1252,12 +1201,11 @@ void WalletQmlControllerTests::walletNameAvailabilityErrorRejectsEmptyAndWhitesp void WalletQmlControllerTests::walletNameAvailabilityErrorRejectsExistingNameWithSurroundingWhitespace() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"alpha_wallet", "sqlite"}}; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1272,12 +1220,11 @@ void WalletQmlControllerTests::walletNameAvailabilityErrorRejectsExistingNameWit void WalletQmlControllerTests::walletNameAvailabilityErrorReturnsEmptyForAvailableName() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; loader.wallet_dir_entries = {{"alpha_wallet", "sqlite"}}; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1287,9 +1234,8 @@ void WalletQmlControllerTests::walletNameAvailabilityErrorReturnsEmptyForAvailab void WalletQmlControllerTests::openSelectedWalletLocationOpensWalletDirectory() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; QTemporaryDir wallet_dir; QVERIFY(wallet_dir.isValid()); @@ -1301,7 +1247,7 @@ void WalletQmlControllerTests::openSelectedWalletLocationOpensWalletDirectory() wallets.emplace_back(std::make_unique("created_wallet", &wallet_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1319,9 +1265,8 @@ void WalletQmlControllerTests::openSelectedWalletLocationOpensWalletDirectory() void WalletQmlControllerTests::openSelectedWalletLocationReportsOpenFailure() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; QTemporaryDir wallet_dir; QVERIFY(wallet_dir.isValid()); @@ -1333,7 +1278,7 @@ void WalletQmlControllerTests::openSelectedWalletLocationReportsOpenFailure() wallets.emplace_back(std::make_unique("created_wallet", &wallet_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1351,9 +1296,8 @@ void WalletQmlControllerTests::openSelectedWalletLocationReportsOpenFailure() void WalletQmlControllerTests::openSelectedWalletLocationReportsMissingWalletPath() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); FakeWalletLoader loader; QTemporaryDir wallet_dir; QVERIFY(wallet_dir.isValid()); @@ -1364,7 +1308,7 @@ void WalletQmlControllerTests::openSelectedWalletLocationReportsMissingWalletPat wallets.emplace_back(std::make_unique("missing_wallet", &wallet_state)); return wallets; }; - ExpectControllerInitialization(node, loader); + ConfigureExpectedControllerInitialization(node, loader); WalletQmlController controller(node); controller.initialize(); @@ -1382,9 +1326,8 @@ void WalletQmlControllerTests::openSelectedWalletLocationReportsMissingWalletPat void WalletQmlControllerTests::openSelectedWalletLocationReportsMissingSelectedWallet() { - using ::testing::StrictMock; - - StrictMock node; + StrictMockNode node; + [[maybe_unused]] auto verify_node = node.VerifyOnExit(); WalletQmlController controller(node); QVERIFY(!controller.openSelectedWalletLocation()); diff --git a/test/test_walletqmlmodel.cpp b/test/test_walletqmlmodel.cpp index 990ebb4556..c050e7010a 100644 --- a/test/test_walletqmlmodel.cpp +++ b/test/test_walletqmlmodel.cpp @@ -2,19 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include #include +#include #include #include #include @@ -22,30 +11,41 @@ #include #include #include -#include #include +#include +#include +#include +#include +#include +#include +#include #include