55#ifndef BITCOIN_QML_TEST_MOCKS_MOCKNODE_H
66#define BITCOIN_QML_TEST_MOCKS_MOCKNODE_H
77
8+ #include < test/mocks/callcounter.h>
89#include < test/mocks/stubnode.h>
910
1011#include < QtTest/qtestcase.h>
1112
12- #include < atomic>
1313#include < functional>
1414#include < mutex>
1515#include < source_location>
@@ -39,9 +39,8 @@ class MockNode : public StubNode
3939 ~Verification ()
4040 {
4141 if (!m_node) return ;
42- const std::string failure{m_node->UnexpectedCallMessage ()};
43- if (!failure.empty ()) {
44- QTest::qFail (failure.c_str (), m_where.file_name (), static_cast <int >(m_where.line ()));
42+ for (const auto & failure : m_node->VerificationFailures (m_where)) {
43+ QTest::qFail (failure.message .c_str (), failure.where .file_name (), static_cast <int >(failure.where .line ()));
4544 }
4645 }
4746
@@ -52,45 +51,24 @@ class MockNode : public StubNode
5251
5352 explicit MockNode (bool strict = false ) : m_strict{strict} {}
5453
55- Verification VerifyOnExit (std::source_location where = std::source_location::current())
54+ [[nodiscard]] Verification VerifyOnExit (std::source_location where = std::source_location::current())
5655 {
5756 return Verification{*this , where};
5857 }
5958
60- std::string UnexpectedCallMessage () const
59+ void ExpectExactly ( const CallCounter& calls, int expected, std::source_location where = std::source_location::current())
6160 {
62- std::lock_guard<std::mutex> lock{m_unexpected_mutex};
63- std::ostringstream message;
64- if (!m_unexpected_calls.empty ()) {
65- message << " Unexpected Node call(s):" ;
66- for (const std::string& call : m_unexpected_calls) message << " \n " << call;
67- }
68- for (const CallExpectation& expectation : m_call_expectations) {
69- const int actual{expectation.calls ->load () - expectation.baseline };
70- if (actual < expectation.minimum || (expectation.maximum && actual > *expectation.maximum )) {
71- if (message.tellp () > 0 ) message << ' \n ' ;
72- message << " Expected " << expectation.name << " calls to be " ;
73- if (expectation.maximum && expectation.minimum == *expectation.maximum ) {
74- message << expectation.minimum ;
75- } else if (expectation.maximum ) {
76- message << " between " << expectation.minimum << " and " << *expectation.maximum ;
77- } else {
78- message << " at least " << expectation.minimum ;
79- }
80- message << " , actual " << actual;
81- }
82- }
83- return message.str ();
61+ m_call_expectations.push_back (CallExpectation{&calls, calls.load (), expected, expected, where});
8462 }
8563
86- void ExpectCallCount (const std::atomic< int > & calls, int expected, const char * name )
64+ void ExpectNoCalls (const CallCounter & calls, std::source_location where = std::source_location::current() )
8765 {
88- m_call_expectations. push_back (CallExpectation{& calls, calls. load (), expected, expected, name} );
66+ ExpectExactly ( calls, 0 , where );
8967 }
9068
91- void ExpectAtLeast (const std::atomic< int > & calls, int minimum, const char * name )
69+ void ExpectAtLeast (const CallCounter & calls, int minimum, std::source_location where = std::source_location::current() )
9270 {
93- m_call_expectations.push_back (CallExpectation{&calls, calls.load (), minimum, std::nullopt , name });
71+ m_call_expectations.push_back (CallExpectation{&calls, calls.load (), minimum, std::nullopt , where });
9472 }
9573
9674 void SetPersistentSetting (std::string name, common::SettingsValue value)
@@ -142,45 +120,45 @@ class MockNode : public StubNode
142120 std::function<std::unique_ptr<interfaces::Handler>(NotifyBlockTipFn)> handle_notify_block_tip_fn;
143121 std::function<std::unique_ptr<interfaces::Handler>(NotifyHeaderTipFn)> handle_notify_header_tip_fn;
144122
145- std::atomic< int > get_warnings_calls{0 };
146- std::atomic< int > app_init_main_calls{0 };
147- std::atomic< int > app_shutdown_calls{0 };
148- std::atomic< int > start_shutdown_calls{0 };
149- std::atomic< int > shutdown_requested_calls{0 };
150- std::atomic< int > get_persistent_setting_calls{0 };
151- std::atomic< int > update_rw_setting_calls{0 };
152- std::atomic< int > force_setting_calls{0 };
153- std::atomic< int > map_port_calls{0 };
154- std::atomic< int > get_node_count_calls{0 };
155- std::atomic< int > get_nodes_stats_calls{0 };
156- std::atomic< int > get_banned_calls{0 };
157- std::atomic< int > ban_calls{0 };
158- std::atomic< int > unban_calls{0 };
159- std::atomic< int > disconnect_by_address_calls{0 };
160- std::atomic< int > disconnect_by_id_calls{0 };
161- std::atomic< int > list_external_signers_calls{0 };
162- std::atomic< int > get_total_bytes_recv_calls{0 };
163- std::atomic< int > get_total_bytes_sent_calls{0 };
164- std::atomic< int > get_mempool_size_calls{0 };
165- std::atomic< int > get_mempool_dynamic_usage_calls{0 };
166- std::atomic< int > get_mempool_max_usage_calls{0 };
167- std::atomic< int > get_header_tip_calls{0 };
168- std::atomic< int > get_num_blocks_calls{0 };
169- std::atomic< int > get_net_local_addresses_calls{0 };
170- std::atomic< int > get_last_block_time_calls{0 };
171- std::atomic< int > is_initial_block_download_calls{0 };
172- std::atomic< int > get_network_active_calls{0 };
173- std::atomic< int > get_dust_relay_fee_calls{0 };
174- std::atomic< int > broadcast_transaction_calls{0 };
175- std::atomic< int > wallet_loader_calls{0 };
176- std::atomic< int > handle_message_box_calls{0 };
177- std::atomic< int > handle_question_calls{0 };
178- std::atomic< int > handle_notify_num_connections_changed_calls{0 };
179- std::atomic< int > handle_notify_network_active_changed_calls{0 };
180- std::atomic< int > handle_notify_alert_changed_calls{0 };
181- std::atomic< int > handle_banned_list_changed_calls{0 };
182- std::atomic< int > handle_notify_block_tip_calls{0 };
183- std::atomic< int > handle_notify_header_tip_calls{0 };
123+ CallCounter get_warnings_calls{" getWarnings " };
124+ CallCounter app_init_main_calls{" appInitMain " };
125+ CallCounter app_shutdown_calls{" appShutdown " };
126+ CallCounter start_shutdown_calls{" startShutdown " };
127+ CallCounter shutdown_requested_calls{" shutdownRequested " };
128+ CallCounter get_persistent_setting_calls{" getPersistentSetting " };
129+ CallCounter update_rw_setting_calls{" updateRwSetting " };
130+ CallCounter force_setting_calls{" forceSetting " };
131+ CallCounter map_port_calls{" mapPort " };
132+ CallCounter get_node_count_calls{" getNodeCount " };
133+ CallCounter get_nodes_stats_calls{" getNodesStats " };
134+ CallCounter get_banned_calls{" getBanned " };
135+ CallCounter ban_calls{" ban " };
136+ CallCounter unban_calls{" unban " };
137+ CallCounter disconnect_by_address_calls{" disconnectByAddress " };
138+ CallCounter disconnect_by_id_calls{" disconnectById " };
139+ CallCounter list_external_signers_calls{" listExternalSigners " };
140+ CallCounter get_total_bytes_recv_calls{" getTotalBytesRecv " };
141+ CallCounter get_total_bytes_sent_calls{" getTotalBytesSent " };
142+ CallCounter get_mempool_size_calls{" getMempoolSize " };
143+ CallCounter get_mempool_dynamic_usage_calls{" getMempoolDynamicUsage " };
144+ CallCounter get_mempool_max_usage_calls{" getMempoolMaxUsage " };
145+ CallCounter get_header_tip_calls{" getHeaderTip " };
146+ CallCounter get_num_blocks_calls{" getNumBlocks " };
147+ CallCounter get_net_local_addresses_calls{" getNetLocalAddresses " };
148+ CallCounter get_last_block_time_calls{" getLastBlockTime " };
149+ CallCounter is_initial_block_download_calls{" isInitialBlockDownload " };
150+ CallCounter get_network_active_calls{" getNetworkActive " };
151+ CallCounter get_dust_relay_fee_calls{" getDustRelayFee " };
152+ CallCounter broadcast_transaction_calls{" broadcastTransaction " };
153+ CallCounter wallet_loader_calls{" walletLoader " };
154+ CallCounter handle_message_box_calls{" handleMessageBox " };
155+ CallCounter handle_question_calls{" handleQuestion " };
156+ CallCounter handle_notify_num_connections_changed_calls{" handleNotifyNumConnectionsChanged " };
157+ CallCounter handle_notify_network_active_changed_calls{" handleNotifyNetworkActiveChanged " };
158+ CallCounter handle_notify_alert_changed_calls{" handleNotifyAlertChanged " };
159+ CallCounter handle_banned_list_changed_calls{" handleBannedListChanged " };
160+ CallCounter handle_notify_block_tip_calls{" handleNotifyBlockTip " };
161+ CallCounter handle_notify_header_tip_calls{" handleNotifyHeaderTip " };
184162
185163 std::vector<std::pair<std::string, common::SettingsValue>> update_rw_setting_arguments;
186164 std::vector<std::pair<std::string, common::SettingsValue>> force_setting_arguments;
@@ -233,7 +211,7 @@ class MockNode : public StubNode
233211 ++wallet_loader_calls;
234212 if (wallet_loader_fn) return wallet_loader_fn ();
235213 UnexpectedCall (" walletLoader" );
236- return StubNode::walletLoader ();
214+ return FallbackWalletLoader ();
237215 }
238216 std::unique_ptr<interfaces::Handler> handleMessageBox (MessageBoxFn fn) override { return Invoke (handle_message_box_calls, handle_message_box_fn, " handleMessageBox" , std::unique_ptr<interfaces::Handler>{}, std::move (fn)); }
239217 std::unique_ptr<interfaces::Handler> handleQuestion (QuestionFn fn) override { return Invoke (handle_question_calls, handle_question_fn, " handleQuestion" , std::unique_ptr<interfaces::Handler>{}, std::move (fn)); }
@@ -250,15 +228,52 @@ class MockNode : public StubNode
250228private:
251229 struct CallExpectation
252230 {
253- const std::atomic< int > * calls;
231+ const CallCounter * calls;
254232 int baseline;
255233 int minimum;
256234 std::optional<int > maximum;
257- std::string name ;
235+ std::source_location where ;
258236 };
259237
238+ struct VerificationFailure
239+ {
240+ std::string message;
241+ std::source_location where;
242+ };
243+
244+ std::vector<VerificationFailure> VerificationFailures (std::source_location unexpected_where) const
245+ {
246+ std::vector<VerificationFailure> failures;
247+ {
248+ std::lock_guard<std::mutex> lock{m_unexpected_mutex};
249+ if (!m_unexpected_calls.empty ()) {
250+ std::ostringstream message;
251+ message << " Unexpected Node call(s):" ;
252+ for (const std::string& call : m_unexpected_calls) message << " \n " << call;
253+ failures.push_back ({message.str (), unexpected_where});
254+ }
255+ }
256+ for (const CallExpectation& expectation : m_call_expectations) {
257+ const int actual{expectation.calls ->load () - expectation.baseline };
258+ if (actual < expectation.minimum || (expectation.maximum && actual > *expectation.maximum )) {
259+ std::ostringstream message;
260+ message << " Expected " << expectation.calls ->Name () << " calls to be " ;
261+ if (expectation.maximum && expectation.minimum == *expectation.maximum ) {
262+ message << expectation.minimum ;
263+ } else if (expectation.maximum ) {
264+ message << " between " << expectation.minimum << " and " << *expectation.maximum ;
265+ } else {
266+ message << " at least " << expectation.minimum ;
267+ }
268+ message << " , actual " << actual;
269+ failures.push_back ({message.str (), expectation.where });
270+ }
271+ }
272+ return failures;
273+ }
274+
260275 template <typename R, typename ... FnArgs, typename ... CallArgs>
261- R Invoke (std::atomic< int > & calls, const std::function<R(FnArgs...)>& fn, const char* name, R fallback, CallArgs&&... args)
276+ R Invoke (CallCounter & calls, const std::function<R(FnArgs...)>& fn, const char* name, R fallback, CallArgs&&... args)
262277 {
263278 ++calls;
264279 if (fn) return fn (std::forward<CallArgs>(args)...);
@@ -267,7 +282,7 @@ class MockNode : public StubNode
267282 }
268283
269284 template <typename ... FnArgs, typename ... CallArgs>
270- void InvokeVoid (std::atomic< int > & calls, const std::function<void (FnArgs...)>& fn, const char* name, CallArgs&&... args)
285+ void InvokeVoid (CallCounter & calls, const std::function<void (FnArgs...)>& fn, const char* name, CallArgs&&... args)
271286 {
272287 ++calls;
273288 if (fn) {
0 commit comments