diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index 46fc162902..f2c48bc48c 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -31,9 +31,6 @@ jobs: 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 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/.github/workflows/depends-build.yml b/.github/workflows/depends-build.yml index f726ba4ed1..c90eb7425b 100644 --- a/.github/workflows/depends-build.yml +++ b/.github/workflows/depends-build.yml @@ -143,14 +143,18 @@ jobs: cmake -S . -B "${BUILD_DIR}" -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="${PWD}/bitcoin/depends/${HOST}/toolchain.cmake" \ - -DBUILD_APP_TESTS=OFF \ + -DBUILD_APP_TESTS=ON \ -DBUILD_GUI=ON \ -DENABLE_WALLET=ON \ -DENABLE_IPC=OFF - - name: Build bitcoin-core-app + - name: Build run: | - cmake --build "${BUILD_DIR}" --target bitcoin-core-app --parallel "${JOBS}" + cmake --build "${BUILD_DIR}" --parallel "${JOBS}" + + - name: Run app tests + run: | + ctest --test-dir "${BUILD_DIR}" --output-on-failure --parallel "${JOBS}" - name: Upload depends logs uses: actions/upload-artifact@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index b65bc4c155..f8d640a000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ project(BitcoinCoreApp LANGUAGES CXX ) +include(CMakeDependentOption) + # Language setup if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT CMAKE_HOST_APPLE) # We do not use the install_name_tool when cross-compiling for macOS. @@ -53,6 +55,7 @@ set(USE_QRCODE TRUE) # We need this libraries, can ignore the executable bitcoin-qt set(BUILD_GUI ON) set(ENABLE_WALLET ON) +cmake_dependent_option(BUILD_APP_TESTS "Build tests for the app" ON "BUILD_GUI" OFF) # Bitcoin Core codebase # Builds libraries: univalue, core_interface, bitcoin_node, bitcoin_wallet @@ -193,7 +196,6 @@ target_link_libraries(bitcoin-core-app ) qt6_import_qml_plugins(bitcoin-core-app) -option(BUILD_APP_TESTS "Build unit tests for the app" OFF) if(BUILD_APP_TESTS) include(CTest) enable_testing() 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..1292b009a0 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 @@ -79,7 +72,7 @@ target_link_libraries(bitcoinqml_unit_tests Qt6::Widgets ) -add_test(NAME bitcoinqml_unit_tests COMMAND bitcoinqml_unit_tests -platform offscreen) +add_test(NAME bitcoinqml_unit_tests COMMAND bitcoinqml_unit_tests -platform minimal) qt6_add_resources(QMLTESTS_QRC_CPP ${QML_QRC} qml/bitcoin_qmltests.qrc) @@ -110,4 +103,19 @@ target_include_directories(bitcoinqml_qmltests ${CMAKE_CURRENT_SOURCE_DIR}/.. ) -add_test(NAME bitcoinqml_qmltests COMMAND bitcoinqml_qmltests -platform offscreen) +add_test(NAME bitcoinqml_qmltests COMMAND bitcoinqml_qmltests -platform minimal) + +get_target_property(qt_lib_type Qt6::Core TYPE) +if(qt_lib_type STREQUAL "STATIC_LIBRARY") + qt6_import_plugins(bitcoinqml_unit_tests INCLUDE Qt6::QMinimalIntegrationPlugin) + qt6_import_plugins(bitcoinqml_qmltests INCLUDE Qt6::QMinimalIntegrationPlugin) + qt6_import_qml_plugins(bitcoinqml_qmltests) + target_link_libraries(bitcoinqml_qmltests PRIVATE + Qt6::qtquickdialogsplugin + ) + if(Qt6_VERSION VERSION_GREATER_EQUAL 6.5) + target_link_libraries(bitcoinqml_qmltests PRIVATE Qt6::qtqmlcoreplugin) + else() + target_link_libraries(bitcoinqml_qmltests PRIVATE Qt6::qmlsettingsplugin) + endif() +endif() 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/qml/tst_debuglogoutputview.qml b/test/qml/tst_debuglogoutputview.qml index 144f55740e..79cfe14b61 100644 --- a/test/qml/tst_debuglogoutputview.qml +++ b/test/qml/tst_debuglogoutputview.qml @@ -3,24 +3,17 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. import QtQuick 2.15 -import QtQuick.Window 2.15 import QtTest 1.2 import org.bitcoincore.qt 1.0 import "../../qml/components" TestCase { + id: testCase name: "DebugLogOutputView" when: windowShown width: 440 height: 300 - Window { - id: testWindow - width: 440 - height: 300 - visible: true - } - Component { id: outputViewComponent @@ -38,7 +31,7 @@ TestCase { } function createOutputView() { - const view = createTemporaryObject(outputViewComponent, testWindow.contentItem) + const view = createTemporaryObject(outputViewComponent, testCase.Window.window.contentItem) verify(view !== null) tryCompare(view, "count", testDebugLogModel.count) return view diff --git a/test/qml/tst_send.qml b/test/qml/tst_send.qml index ad29d40882..7236541d1f 100644 --- a/test/qml/tst_send.qml +++ b/test/qml/tst_send.qml @@ -9,18 +9,12 @@ import org.bitcoincore.qt 1.0 import "../../qml/pages/wallet" TestCase { + id: testCase name: "Send" when: windowShown width: 900 height: 700 - Window { - id: testWindow - width: 900 - height: 700 - visible: true - } - Component { id: sendComponent @@ -54,10 +48,10 @@ TestCase { } function test_review_only_psbt_closes_and_discards_on_wallet_change() { - const page = createTemporaryObject(sendComponent, testWindow.contentItem) + const page = createTemporaryObject(sendComponent, testCase.Window.window.contentItem) verify(page !== null) - page.width = testWindow.width - page.height = testWindow.height + page.width = testCase.width + page.height = testCase.height page.visible = true const popup = findChild(page, "reviewOnlyPsbtPopup") @@ -78,10 +72,10 @@ TestCase { } function test_successful_psbt_broadcast_shows_confirmation() { - const page = createTemporaryObject(sendComponent, testWindow.contentItem) + const page = createTemporaryObject(sendComponent, testCase.Window.window.contentItem) verify(page !== null) - page.width = testWindow.width - page.height = testWindow.height + page.width = testCase.width + page.height = testCase.height page.visible = true const reviewPopup = findChild(page, "reviewOnlyPsbtPopup") @@ -133,10 +127,10 @@ TestCase { } function test_send_multiple_recipient_layout_and_total_follow_state() { - const page = createTemporaryObject(sendComponent, testWindow.contentItem) + const page = createTemporaryObject(sendComponent, testCase.Window.window.contentItem) verify(page !== null) - page.width = testWindow.width - page.height = testWindow.height + page.width = testCase.width + page.height = testCase.height page.visible = true const optionsPopup = findChild(page, "sendOptionsPopup") 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