77
88#include < interfaces/wallet.h>
99#include < outputtype.h>
10+ #include < test/mocks/callcounter.h>
1011#include < wallet/coincontrol.h>
1112#include < wallet/types.h>
1213#include < wallet/wallet.h>
1314
1415#include < QtTest/qtestcase.h>
1516
16- #include < atomic>
1717#include < functional>
1818#include < source_location>
1919#include < sstream>
@@ -88,54 +88,63 @@ class StubWallet : public interfaces::Wallet
8888 std::unique_ptr<interfaces::Handler> handleCanGetAddressesChanged (CanGetAddressesChangedFn) override { return {}; }
8989};
9090
91+ /* *
92+ * Configurable wallet test double with permissive defaults.
93+ *
94+ * Unconfigured methods return their StubWallet values. Tests register explicit
95+ * expectations for calls that are part of the behavior under test.
96+ */
9197class MockWallet : public StubWallet
9298{
9399public:
94100 class Verification
95101 {
96102 public:
97- Verification (MockWallet& wallet, std::source_location where )
98- : m_wallet{&wallet}, m_where{where}
103+ explicit Verification (MockWallet& wallet)
104+ : m_wallet{&wallet}
99105 {
100106 }
101107
102108 Verification (const Verification&) = delete ;
103109 Verification& operator =(const Verification&) = delete ;
104110 Verification (Verification&& other) noexcept
105- : m_wallet{std::exchange (other.m_wallet , nullptr )}, m_where{other. m_where }
111+ : m_wallet{std::exchange (other.m_wallet , nullptr )}
106112 {
107113 }
108114
109115 ~Verification ()
110116 {
111117 if (!m_wallet) return ;
112- const std::string failure{m_wallet->CallExpectationFailure ()};
113- if (!failure.empty ()) {
114- QTest::qFail (failure.c_str (), m_where.file_name (), static_cast <int >(m_where.line ()));
118+ for (const auto & failure : m_wallet->VerificationFailures ()) {
119+ QTest::qFail (failure.message .c_str (), failure.where .file_name (), static_cast <int >(failure.where .line ()));
115120 }
116121 }
117122
118123 private:
119124 MockWallet* m_wallet;
120- std::source_location m_where;
121125 };
122126
123- Verification VerifyOnExit (std::source_location where = std::source_location::current() )
127+ [[nodiscard]] Verification VerifyOnExit ()
124128 {
125- return Verification{*this , where };
129+ return Verification{*this };
126130 }
127131
128- void ExpectCallCount (const std::atomic< int >& calls , int expected, const char * name )
132+ void ExpectExactly (const CallCounter& counter , int expected, std::source_location where = std::source_location::current() )
129133 {
130- m_call_expectations.push_back (CallExpectation{&calls, calls .load (), expected, expected, name });
134+ m_call_expectations.push_back (CallExpectation{&counter, counter .load (), expected, expected, where });
131135 }
132136
133- void ExpectAtLeast (const std::atomic< int >& calls, int minimum, const char * name )
137+ void ExpectNoCalls (const CallCounter& counter, std::source_location where = std::source_location::current() )
134138 {
135- m_call_expectations. push_back (CallExpectation{&calls, calls. load (), minimum, std:: nullopt , name} );
139+ ExpectExactly (counter, 0 , where );
136140 }
137141
138- std::function<util::Result<CTransactionRef>(const std::vector<wallet::CRecipient>&, const wallet::CCoinControl&, bool , int &, CAmount&)> createTransactionHandler;
142+ void ExpectAtLeast (const CallCounter& counter, int minimum, std::source_location where = std::source_location::current())
143+ {
144+ m_call_expectations.push_back (CallExpectation{&counter, counter.load (), minimum, std::nullopt , where});
145+ }
146+
147+ std::function<util::Result<CTransactionRef>(const std::vector<wallet::CRecipient>&, const wallet::CCoinControl&, bool , int &, CAmount&)> create_transaction_fn;
139148 std::function<CTxDestination(OutputType, const std::string&)> get_new_destination_fn;
140149 std::function<std::set<interfaces::WalletTx>()> get_wallet_txs_fn;
141150 std::function<CAmount()> get_balance_fn;
@@ -149,22 +158,26 @@ class MockWallet : public StubWallet
149158 std::function<bool (CMutableTransaction&)> sign_bump_transaction_fn;
150159 std::function<bool (const Txid&, CMutableTransaction&&, std::vector<bilingual_str>&, Txid&)> commit_bump_transaction_fn;
151160
152- std::atomic<int > get_new_destination_calls{0 };
153- std::atomic<int > get_wallet_txs_calls{0 };
154- std::atomic<int > get_balance_calls{0 };
155- std::atomic<int > get_available_balance_calls{0 };
156- std::atomic<int > get_required_fee_calls{0 };
157- std::atomic<int > list_coins_calls{0 };
158- std::atomic<int > get_default_address_type_calls{0 };
159- std::atomic<int > handle_transaction_changed_calls{0 };
160- std::atomic<int > transaction_can_be_bumped_calls{0 };
161- std::atomic<int > create_bump_transaction_calls{0 };
162- std::atomic<int > sign_bump_transaction_calls{0 };
163- std::atomic<int > commit_bump_transaction_calls{0 };
161+ struct Calls
162+ {
163+ CallCounter getNewDestination{" getNewDestination" };
164+ CallCounter createTransaction{" createTransaction" };
165+ CallCounter getWalletTxs{" getWalletTxs" };
166+ CallCounter getBalance{" getBalance" };
167+ CallCounter getAvailableBalance{" getAvailableBalance" };
168+ CallCounter getRequiredFee{" getRequiredFee" };
169+ CallCounter listCoins{" listCoins" };
170+ CallCounter getDefaultAddressType{" getDefaultAddressType" };
171+ CallCounter handleTransactionChanged{" handleTransactionChanged" };
172+ CallCounter transactionCanBeBumped{" transactionCanBeBumped" };
173+ CallCounter createBumpTransaction{" createBumpTransaction" };
174+ CallCounter signBumpTransaction{" signBumpTransaction" };
175+ CallCounter commitBumpTransaction{" commitBumpTransaction" };
176+ } calls;
164177
165178 util::Result<CTxDestination> getNewDestination (const OutputType type, const std::string& label) override
166179 {
167- ++get_new_destination_calls ;
180+ ++calls. getNewDestination ;
168181 if (get_new_destination_fn) return get_new_destination_fn (type, label);
169182 return util::Error{Untranslated (" no get_new_destination_fn installed" )};
170183 }
@@ -174,10 +187,11 @@ class MockWallet : public StubWallet
174187 bool sign,
175188 std::optional<unsigned int >) override
176189 {
177- if (createTransactionHandler) {
190+ ++calls.createTransaction ;
191+ if (create_transaction_fn) {
178192 int change_pos{-1 };
179193 CAmount fee{0 };
180- auto result = createTransactionHandler (recipients, coin_control, sign, change_pos, fee);
194+ auto result = create_transaction_fn (recipients, coin_control, sign, change_pos, fee);
181195 if (!result) {
182196 return util::Error{util::ErrorString (result)};
183197 }
@@ -187,94 +201,100 @@ class MockWallet : public StubWallet
187201 change_pos >= 0 ? std::optional<unsigned int >{static_cast <unsigned int >(change_pos)} : std::nullopt ,
188202 FeeCalculation{}};
189203 }
190- return util::Error{Untranslated (" no createTransactionHandler installed" )};
204+ return util::Error{Untranslated (" no create_transaction_fn installed" )};
191205 }
192206
193207 std::set<interfaces::WalletTx> getWalletTxs () override
194208 {
195- ++get_wallet_txs_calls ;
209+ ++calls. getWalletTxs ;
196210 return get_wallet_txs_fn ? get_wallet_txs_fn () : std::set<interfaces::WalletTx>{};
197211 }
198212
199213 CAmount getBalance () override
200214 {
201- ++get_balance_calls ;
215+ ++calls. getBalance ;
202216 return get_balance_fn ? get_balance_fn () : 0 ;
203217 }
204218
205219 CAmount getAvailableBalance (const wallet::CCoinControl& coin_control) override
206220 {
207- ++get_available_balance_calls ;
221+ ++calls. getAvailableBalance ;
208222 return get_available_balance_fn ? get_available_balance_fn (coin_control) : 0 ;
209223 }
210224
211225 CAmount getRequiredFee (unsigned int tx_bytes) override
212226 {
213- ++get_required_fee_calls ;
227+ ++calls. getRequiredFee ;
214228 return get_required_fee_fn ? get_required_fee_fn (tx_bytes) : 0 ;
215229 }
216230
217231 CoinsList listCoins () override
218232 {
219- ++list_coins_calls ;
233+ ++calls. listCoins ;
220234 return list_coins_fn ? list_coins_fn () : CoinsList{};
221235 }
222236
223237 OutputType getDefaultAddressType () override
224238 {
225- ++get_default_address_type_calls ;
239+ ++calls. getDefaultAddressType ;
226240 return get_default_address_type_fn ? get_default_address_type_fn () : OutputType::BECH32 ;
227241 }
228242
229243 std::unique_ptr<interfaces::Handler> handleTransactionChanged (TransactionChangedFn fn) override
230244 {
231- ++handle_transaction_changed_calls ;
245+ ++calls. handleTransactionChanged ;
232246 return handle_transaction_changed_fn ? handle_transaction_changed_fn (std::move (fn)) : nullptr ;
233247 }
234248
235249 bool transactionCanBeBumped (const Txid& txid) override
236250 {
237- ++transaction_can_be_bumped_calls ;
251+ ++calls. transactionCanBeBumped ;
238252 return transaction_can_be_bumped_fn ? transaction_can_be_bumped_fn (txid) : false ;
239253 }
240254
241255 bool createBumpTransaction (const Txid& txid, const wallet::CCoinControl& coin_control, std::vector<bilingual_str>& errors,
242256 CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) override
243257 {
244- ++create_bump_transaction_calls ;
258+ ++calls. createBumpTransaction ;
245259 return create_bump_transaction_fn ? create_bump_transaction_fn (txid, coin_control, errors, old_fee, new_fee, mtx) : false ;
246260 }
247261
248262 bool signBumpTransaction (CMutableTransaction& mtx) override
249263 {
250- ++sign_bump_transaction_calls ;
264+ ++calls. signBumpTransaction ;
251265 return sign_bump_transaction_fn ? sign_bump_transaction_fn (mtx) : false ;
252266 }
253267
254268 bool commitBumpTransaction (const Txid& txid, CMutableTransaction&& mtx, std::vector<bilingual_str>& errors, Txid& bumped_txid) override
255269 {
256- ++commit_bump_transaction_calls ;
270+ ++calls. commitBumpTransaction ;
257271 return commit_bump_transaction_fn ? commit_bump_transaction_fn (txid, std::move (mtx), errors, bumped_txid) : false ;
258272 }
259273
260274private:
261275 struct CallExpectation
262276 {
263- const std::atomic< int >* calls ;
277+ const CallCounter* counter ;
264278 int baseline;
265279 int minimum;
266280 std::optional<int > maximum;
267- std::string name;
281+ std::source_location where;
282+ };
283+
284+ struct VerificationFailure
285+ {
286+ std::string message;
287+ std::source_location where;
268288 };
269289
270- std::string CallExpectationFailure () const
290+ std::vector<VerificationFailure> VerificationFailures () const
271291 {
272- std::ostringstream message ;
292+ std::vector<VerificationFailure> failures ;
273293 for (const CallExpectation& expectation : m_call_expectations) {
274- const int actual{expectation.calls ->load () - expectation.baseline };
294+ const int actual{expectation.counter ->load () - expectation.baseline };
275295 if (actual < expectation.minimum || (expectation.maximum && actual > *expectation.maximum )) {
276- if ( message. tellp () > 0 ) message << ' \n ' ;
277- message << " Expected " << expectation.name << " calls to be " ;
296+ std::ostringstream message;
297+ message << " Expected " << expectation.counter -> Name () << " calls to be " ;
278298 if (expectation.maximum && expectation.minimum == *expectation.maximum ) {
279299 message << expectation.minimum ;
280300 } else if (expectation.maximum ) {
@@ -283,9 +303,10 @@ class MockWallet : public StubWallet
283303 message << " at least " << expectation.minimum ;
284304 }
285305 message << " , actual " << actual;
306+ failures.push_back ({message.str (), expectation.where });
286307 }
287308 }
288- return message. str () ;
309+ return failures ;
289310 }
290311
291312 std::vector<CallExpectation> m_call_expectations;
0 commit comments