Skip to content

Commit 33c6245

Browse files
committed
Introduce MockableDatabase for wallet unit tests
MockableDatabase is a WalletDatabase that allows us to interact with the records to change them independently from the wallet, as well as changing the return values from within the tests. This will give us greater flexibility in testing the wallet.
1 parent 5394522 commit 33c6245

File tree

4 files changed

+167
-51
lines changed

4 files changed

+167
-51
lines changed

build_msvc/libtest_util/libtest_util.vcxproj.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<ConfigurationType>StaticLibrary</ConfigurationType>
99
</PropertyGroup>
1010
<ItemGroup>
11+
<ClCompile Include="..\..\src\wallet\test\util.cpp" />
1112
@SOURCE_FILES@
1213
</ItemGroup>
1314
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />

src/wallet/test/util.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <chain.h>
88
#include <key.h>
99
#include <key_io.h>
10+
#include <streams.h>
1011
#include <test/util/setup_common.h>
1112
#include <wallet/wallet.h>
1213
#include <wallet/walletdb.h>
@@ -79,4 +80,93 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type)
7980
return *Assert(w.GetNewDestination(output_type, ""));
8081
}
8182

83+
DatabaseCursor::Status MockableCursor::Next(DataStream& key, DataStream& value)
84+
{
85+
if (!m_pass) {
86+
return Status::FAIL;
87+
}
88+
if (m_cursor == m_cursor_end) {
89+
return Status::DONE;
90+
}
91+
const auto& [key_data, value_data] = *m_cursor;
92+
key.write(key_data);
93+
value.write(value_data);
94+
m_cursor++;
95+
return Status::MORE;
96+
}
97+
98+
bool MockableBatch::ReadKey(DataStream&& key, DataStream& value)
99+
{
100+
if (!m_pass) {
101+
return false;
102+
}
103+
SerializeData key_data{key.begin(), key.end()};
104+
const auto& it = m_records.find(key_data);
105+
if (it == m_records.end()) {
106+
return false;
107+
}
108+
value.write(it->second);
109+
return true;
110+
}
111+
112+
bool MockableBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite)
113+
{
114+
if (!m_pass) {
115+
return false;
116+
}
117+
SerializeData key_data{key.begin(), key.end()};
118+
SerializeData value_data{value.begin(), value.end()};
119+
auto [it, inserted] = m_records.emplace(key_data, value_data);
120+
if (!inserted && overwrite) { // Overwrite if requested
121+
it->second = value_data;
122+
inserted = true;
123+
}
124+
return inserted;
125+
}
126+
127+
bool MockableBatch::EraseKey(DataStream&& key)
128+
{
129+
if (!m_pass) {
130+
return false;
131+
}
132+
SerializeData key_data{key.begin(), key.end()};
133+
m_records.erase(key_data);
134+
return true;
135+
}
136+
137+
bool MockableBatch::HasKey(DataStream&& key)
138+
{
139+
if (!m_pass) {
140+
return false;
141+
}
142+
SerializeData key_data{key.begin(), key.end()};
143+
return m_records.count(key_data) > 0;
144+
}
145+
146+
bool MockableBatch::ErasePrefix(Span<const std::byte> prefix)
147+
{
148+
if (!m_pass) {
149+
return false;
150+
}
151+
auto it = m_records.begin();
152+
while (it != m_records.end()) {
153+
auto& key = it->first;
154+
if (key.size() < prefix.size() || std::search(key.begin(), key.end(), prefix.begin(), prefix.end()) != key.begin()) {
155+
it++;
156+
continue;
157+
}
158+
it = m_records.erase(it);
159+
}
160+
return true;
161+
}
162+
163+
std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(std::map<SerializeData, SerializeData> records)
164+
{
165+
return std::make_unique<MockableDatabase>(records);
166+
}
167+
168+
MockableDatabase& GetMockableDatabase(CWallet& wallet)
169+
{
170+
return dynamic_cast<MockableDatabase&>(wallet.GetDatabase());
171+
}
82172
} // namespace wallet

src/wallet/test/util.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#define BITCOIN_WALLET_TEST_UTIL_H
77

88
#include <script/standard.h>
9+
#include <wallet/db.h>
10+
911
#include <memory>
1012

1113
class ArgsManager;
@@ -31,6 +33,78 @@ std::string getnewaddress(CWallet& w);
3133
/** Returns a new destination, of an specific type, from the wallet */
3234
CTxDestination getNewDestination(CWallet& w, OutputType output_type);
3335

36+
class MockableCursor: public DatabaseCursor
37+
{
38+
public:
39+
std::map<SerializeData, SerializeData>::const_iterator m_cursor;
40+
std::map<SerializeData, SerializeData>::const_iterator m_cursor_end;
41+
bool m_pass;
42+
43+
explicit MockableCursor(const std::map<SerializeData, SerializeData>& records, bool pass) : m_cursor(records.begin()), m_cursor_end(records.end()), m_pass(pass) {}
44+
~MockableCursor() {}
45+
46+
Status Next(DataStream& key, DataStream& value) override;
47+
};
48+
49+
class MockableBatch : public DatabaseBatch
50+
{
51+
private:
52+
std::map<SerializeData, SerializeData>& m_records;
53+
bool m_pass;
54+
55+
bool ReadKey(DataStream&& key, DataStream& value) override;
56+
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override;
57+
bool EraseKey(DataStream&& key) override;
58+
bool HasKey(DataStream&& key) override;
59+
bool ErasePrefix(Span<const std::byte> prefix) override;
60+
61+
public:
62+
explicit MockableBatch(std::map<SerializeData, SerializeData>& records, bool pass) : m_records(records), m_pass(pass) {}
63+
~MockableBatch() {}
64+
65+
void Flush() override {}
66+
void Close() override {}
67+
68+
std::unique_ptr<DatabaseCursor> GetNewCursor() override
69+
{
70+
return std::make_unique<MockableCursor>(m_records, m_pass);
71+
}
72+
bool TxnBegin() override { return m_pass; }
73+
bool TxnCommit() override { return m_pass; }
74+
bool TxnAbort() override { return m_pass; }
75+
};
76+
77+
/** A WalletDatabase whose contents and return values can be modified as needed for testing
78+
**/
79+
class MockableDatabase : public WalletDatabase
80+
{
81+
public:
82+
std::map<SerializeData, SerializeData> m_records;
83+
bool m_pass{true};
84+
85+
MockableDatabase(std::map<SerializeData, SerializeData> records = {}) : WalletDatabase(), m_records(records) {}
86+
~MockableDatabase() {};
87+
88+
void Open() override {}
89+
void AddRef() override {}
90+
void RemoveRef() override {}
91+
92+
bool Rewrite(const char* pszSkip=nullptr) override { return m_pass; }
93+
bool Backup(const std::string& strDest) const override { return m_pass; }
94+
void Flush() override {}
95+
void Close() override {}
96+
bool PeriodicFlush() override { return m_pass; }
97+
void IncrementUpdateCounter() override {}
98+
void ReloadDbEnv() override {}
99+
100+
std::string Filename() override { return "mockable"; }
101+
std::string Format() override { return "mock"; }
102+
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<MockableBatch>(m_records, m_pass); }
103+
};
104+
105+
std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(std::map<SerializeData, SerializeData> records = {});
106+
107+
MockableDatabase& GetMockableDatabase(CWallet& wallet);
34108
} // namespace wallet
35109

36110
#endif // BITCOIN_WALLET_TEST_UTIL_H

src/wallet/test/wallet_tests.cpp

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -951,62 +951,13 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
951951
TestUnloadWallet(std::move(wallet));
952952
}
953953

954-
class FailCursor : public DatabaseCursor
955-
{
956-
public:
957-
Status Next(DataStream& key, DataStream& value) override { return Status::FAIL; }
958-
};
959-
960-
/** RAII class that provides access to a FailDatabase. Which fails if needed. */
961-
class FailBatch : public DatabaseBatch
962-
{
963-
private:
964-
bool m_pass{true};
965-
bool ReadKey(DataStream&& key, DataStream& value) override { return m_pass; }
966-
bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override { return m_pass; }
967-
bool EraseKey(DataStream&& key) override { return m_pass; }
968-
bool HasKey(DataStream&& key) override { return m_pass; }
969-
bool ErasePrefix(Span<const std::byte> prefix) override { return m_pass; }
970-
971-
public:
972-
explicit FailBatch(bool pass) : m_pass(pass) {}
973-
void Flush() override {}
974-
void Close() override {}
975-
976-
std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<FailCursor>(); }
977-
bool TxnBegin() override { return false; }
978-
bool TxnCommit() override { return false; }
979-
bool TxnAbort() override { return false; }
980-
};
981-
982-
/** A dummy WalletDatabase that does nothing, only fails if needed.**/
983-
class FailDatabase : public WalletDatabase
984-
{
985-
public:
986-
bool m_pass{true}; // false when this db should fail
987-
988-
void Open() override {};
989-
void AddRef() override {}
990-
void RemoveRef() override {}
991-
bool Rewrite(const char* pszSkip=nullptr) override { return true; }
992-
bool Backup(const std::string& strDest) const override { return true; }
993-
void Close() override {}
994-
void Flush() override {}
995-
bool PeriodicFlush() override { return true; }
996-
void IncrementUpdateCounter() override { ++nUpdateCounter; }
997-
void ReloadDbEnv() override {}
998-
std::string Filename() override { return "faildb"; }
999-
std::string Format() override { return "faildb"; }
1000-
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<FailBatch>(m_pass); }
1001-
};
1002-
1003954
/**
1004955
* Checks a wallet invalid state where the inputs (prev-txs) of a new arriving transaction are not marked dirty,
1005956
* while the transaction that spends them exist inside the in-memory wallet tx map (not stored on db due a db write failure).
1006957
*/
1007958
BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup)
1008959
{
1009-
CWallet wallet(m_node.chain.get(), "", std::make_unique<FailDatabase>());
960+
CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
1010961
{
1011962
LOCK(wallet.cs_wallet);
1012963
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@@ -1053,7 +1004,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup)
10531004
// 1) Make db always fail
10541005
// 2) Try to add a transaction that spends the previously created transaction and
10551006
// verify that we are not moving forward if the wallet cannot store it
1056-
static_cast<FailDatabase&>(wallet.GetDatabase()).m_pass = false;
1007+
GetMockableDatabase(wallet).m_pass = false;
10571008
mtx.vin.clear();
10581009
mtx.vin.push_back(CTxIn(good_tx_id, 0));
10591010
BOOST_CHECK_EXCEPTION(wallet.transactionAddedToMempool(MakeTransactionRef(mtx)),

0 commit comments

Comments
 (0)