Skip to content

Commit a14edad

Browse files
andrewtothl0rinc
andcommitted
test: add cache entry linked list tests
Co-Authored-By: l0rinc <[email protected]>
1 parent 24ce37c commit a14edad

File tree

3 files changed

+227
-3
lines changed

3 files changed

+227
-3
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ BITCOIN_TESTS =\
8585
test/checkqueue_tests.cpp \
8686
test/cluster_linearize_tests.cpp \
8787
test/coins_tests.cpp \
88+
test/coinscachepair_tests.cpp \
8889
test/coinstatsindex_tests.cpp \
8990
test/common_url_tests.cpp \
9091
test/compilerbug_tests.cpp \

src/test/coins_tests.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class CCoinsViewCacheTest : public CCoinsViewCache
7878
public:
7979
explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}
8080

81-
void SelfTest() const
81+
void SelfTest(bool sanity_check = true) const
8282
{
8383
// Manually recompute the dynamic usage of the whole data, and compare it.
8484
size_t ret = memusage::DynamicUsage(cacheCoins);
@@ -89,6 +89,9 @@ class CCoinsViewCacheTest : public CCoinsViewCache
8989
}
9090
BOOST_CHECK_EQUAL(GetCacheSize(), count);
9191
BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
92+
if (sanity_check) {
93+
SanityCheck();
94+
}
9295
}
9396

9497
CCoinsMap& map() const { return cacheCoins; }
@@ -637,7 +640,7 @@ static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount exp
637640
{
638641
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
639642
test.cache.AccessCoin(OUTPOINT);
640-
test.cache.SelfTest();
643+
test.cache.SelfTest(/*sanity_check=*/false);
641644

642645
CAmount result_value;
643646
char result_flags;
@@ -806,7 +809,7 @@ void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected
806809
char result_flags;
807810
try {
808811
WriteCoinsViewEntry(test.cache, child_value, child_flags);
809-
test.cache.SelfTest();
812+
test.cache.SelfTest(/*sanity_check=*/false);
810813
GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
811814
} catch (std::logic_error&) {
812815
result_value = FAIL;
@@ -919,6 +922,7 @@ void TestFlushBehavior(
919922
// Flush in reverse order to ensure that flushes happen from children up.
920923
for (auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
921924
auto& cache = *i;
925+
cache->SanityCheck();
922926
// hashBlock must be filled before flushing to disk; value is
923927
// unimportant here. This is normally done during connect/disconnect block.
924928
cache->SetBestBlock(InsecureRand256());

src/test/coinscachepair_tests.cpp

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright (c) 2024-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <coins.h>
6+
7+
#include <boost/test/unit_test.hpp>
8+
9+
#include <list>
10+
11+
BOOST_AUTO_TEST_SUITE(coinscachepair_tests)
12+
13+
static constexpr auto NUM_NODES{4};
14+
15+
std::list<CoinsCachePair> CreatePairs(CoinsCachePair& sentinel)
16+
{
17+
std::list<CoinsCachePair> nodes;
18+
for (auto i{0}; i < NUM_NODES; ++i) {
19+
nodes.emplace_back();
20+
21+
auto node{std::prev(nodes.end())};
22+
node->second.AddFlags(CCoinsCacheEntry::DIRTY, *node, sentinel);
23+
24+
BOOST_CHECK_EQUAL(node->second.GetFlags(), CCoinsCacheEntry::DIRTY);
25+
BOOST_CHECK_EQUAL(node->second.Next(), &sentinel);
26+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &(*node));
27+
28+
if (i > 0) {
29+
BOOST_CHECK_EQUAL(std::prev(node)->second.Next(), &(*node));
30+
BOOST_CHECK_EQUAL(node->second.Prev(), &(*std::prev(node)));
31+
}
32+
}
33+
return nodes;
34+
}
35+
36+
BOOST_AUTO_TEST_CASE(linked_list_iteration)
37+
{
38+
CoinsCachePair sentinel;
39+
sentinel.second.SelfRef(sentinel);
40+
auto nodes{CreatePairs(sentinel)};
41+
42+
// Check iterating through pairs is identical to iterating through a list
43+
auto node{sentinel.second.Next()};
44+
for (const auto& expected : nodes) {
45+
BOOST_CHECK_EQUAL(&expected, node);
46+
node = node->second.Next();
47+
}
48+
BOOST_CHECK_EQUAL(node, &sentinel);
49+
50+
// Check iterating through pairs is identical to iterating through a list
51+
// Clear the flags during iteration
52+
node = sentinel.second.Next();
53+
for (const auto& expected : nodes) {
54+
BOOST_CHECK_EQUAL(&expected, node);
55+
auto next = node->second.Next();
56+
node->second.ClearFlags();
57+
node = next;
58+
}
59+
BOOST_CHECK_EQUAL(node, &sentinel);
60+
// Check that sentinel's next and prev are itself
61+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &sentinel);
62+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel);
63+
64+
// Delete the nodes from the list to make sure there are no dangling pointers
65+
for (auto it{nodes.begin()}; it != nodes.end(); it = nodes.erase(it)) {
66+
BOOST_CHECK_EQUAL(it->second.GetFlags(), 0);
67+
}
68+
}
69+
70+
BOOST_AUTO_TEST_CASE(linked_list_iterate_erase)
71+
{
72+
CoinsCachePair sentinel;
73+
sentinel.second.SelfRef(sentinel);
74+
auto nodes{CreatePairs(sentinel)};
75+
76+
// Check iterating through pairs is identical to iterating through a list
77+
// Erase the nodes as we iterate through, but don't clear flags
78+
// The flags will be cleared by the CCoinsCacheEntry's destructor
79+
auto node{sentinel.second.Next()};
80+
for (auto expected{nodes.begin()}; expected != nodes.end(); expected = nodes.erase(expected)) {
81+
BOOST_CHECK_EQUAL(&(*expected), node);
82+
node = node->second.Next();
83+
}
84+
BOOST_CHECK_EQUAL(node, &sentinel);
85+
86+
// Check that sentinel's next and prev are itself
87+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &sentinel);
88+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel);
89+
}
90+
91+
BOOST_AUTO_TEST_CASE(linked_list_random_deletion)
92+
{
93+
CoinsCachePair sentinel;
94+
sentinel.second.SelfRef(sentinel);
95+
auto nodes{CreatePairs(sentinel)};
96+
97+
// Create linked list sentinel->n1->n2->n3->n4->sentinel
98+
auto n1{nodes.begin()};
99+
auto n2{std::next(n1)};
100+
auto n3{std::next(n2)};
101+
auto n4{std::next(n3)};
102+
103+
// Delete n2
104+
// sentinel->n1->n3->n4->sentinel
105+
nodes.erase(n2);
106+
// Check that n1 now points to n3, and n3 still points to n4
107+
// Also check that flags were not altered
108+
BOOST_CHECK_EQUAL(n1->second.GetFlags(), CCoinsCacheEntry::DIRTY);
109+
BOOST_CHECK_EQUAL(n1->second.Next(), &(*n3));
110+
BOOST_CHECK_EQUAL(n3->second.GetFlags(), CCoinsCacheEntry::DIRTY);
111+
BOOST_CHECK_EQUAL(n3->second.Next(), &(*n4));
112+
BOOST_CHECK_EQUAL(n3->second.Prev(), &(*n1));
113+
114+
// Delete n1
115+
// sentinel->n3->n4->sentinel
116+
nodes.erase(n1);
117+
// Check that sentinel now points to n3, and n3 still points to n4
118+
// Also check that flags were not altered
119+
BOOST_CHECK_EQUAL(n3->second.GetFlags(), CCoinsCacheEntry::DIRTY);
120+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &(*n3));
121+
BOOST_CHECK_EQUAL(n3->second.Next(), &(*n4));
122+
BOOST_CHECK_EQUAL(n3->second.Prev(), &sentinel);
123+
124+
// Delete n4
125+
// sentinel->n3->sentinel
126+
nodes.erase(n4);
127+
// Check that sentinel still points to n3, and n3 points to sentinel
128+
// Also check that flags were not altered
129+
BOOST_CHECK_EQUAL(n3->second.GetFlags(), CCoinsCacheEntry::DIRTY);
130+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &(*n3));
131+
BOOST_CHECK_EQUAL(n3->second.Next(), &sentinel);
132+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &(*n3));
133+
134+
// Delete n3
135+
// sentinel->sentinel
136+
nodes.erase(n3);
137+
// Check that sentinel's next and prev are itself
138+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &sentinel);
139+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel);
140+
}
141+
142+
BOOST_AUTO_TEST_CASE(linked_list_add_flags)
143+
{
144+
CoinsCachePair sentinel;
145+
sentinel.second.SelfRef(sentinel);
146+
CoinsCachePair n1;
147+
CoinsCachePair n2;
148+
149+
// Check that adding 0 flag has no effect
150+
n1.second.AddFlags(0, n1, sentinel);
151+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), 0);
152+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &sentinel);
153+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &sentinel);
154+
155+
// Check that adding DIRTY flag inserts it into linked list and sets flags
156+
n1.second.AddFlags(CCoinsCacheEntry::DIRTY, n1, sentinel);
157+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY);
158+
BOOST_CHECK_EQUAL(n1.second.Next(), &sentinel);
159+
BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel);
160+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1);
161+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n1);
162+
163+
// Check that adding FRESH flag on new node inserts it after n1
164+
n2.second.AddFlags(CCoinsCacheEntry::FRESH, n2, sentinel);
165+
BOOST_CHECK_EQUAL(n2.second.GetFlags(), CCoinsCacheEntry::FRESH);
166+
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
167+
BOOST_CHECK_EQUAL(n2.second.Prev(), &n1);
168+
BOOST_CHECK_EQUAL(n1.second.Next(), &n2);
169+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
170+
171+
// Check that adding 0 flag has no effect, and doesn't change position
172+
n1.second.AddFlags(0, n1, sentinel);
173+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY);
174+
BOOST_CHECK_EQUAL(n1.second.Next(), &n2);
175+
BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel);
176+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1);
177+
BOOST_CHECK_EQUAL(n2.second.Prev(), &n1);
178+
179+
// Check that we can add extra flags, but they don't change our position
180+
n1.second.AddFlags(CCoinsCacheEntry::FRESH, n1, sentinel);
181+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH);
182+
BOOST_CHECK_EQUAL(n1.second.Next(), &n2);
183+
BOOST_CHECK_EQUAL(n1.second.Prev(), &sentinel);
184+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n1);
185+
BOOST_CHECK_EQUAL(n2.second.Prev(), &n1);
186+
187+
// Check that we can clear flags then re-add them
188+
n1.second.ClearFlags();
189+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), 0);
190+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2);
191+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
192+
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
193+
BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel);
194+
195+
// Check that calling `ClearFlags` with 0 flags has no effect
196+
n1.second.ClearFlags();
197+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), 0);
198+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2);
199+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
200+
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
201+
BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel);
202+
203+
// Adding 0 still has no effect
204+
n1.second.AddFlags(0, n1, sentinel);
205+
BOOST_CHECK_EQUAL(sentinel.second.Next(), &n2);
206+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n2);
207+
BOOST_CHECK_EQUAL(n2.second.Next(), &sentinel);
208+
BOOST_CHECK_EQUAL(n2.second.Prev(), &sentinel);
209+
210+
// But adding DIRTY re-inserts it after n2
211+
n1.second.AddFlags(CCoinsCacheEntry::DIRTY, n1, sentinel);
212+
BOOST_CHECK_EQUAL(n1.second.GetFlags(), CCoinsCacheEntry::DIRTY);
213+
BOOST_CHECK_EQUAL(n2.second.Next(), &n1);
214+
BOOST_CHECK_EQUAL(n1.second.Prev(), &n2);
215+
BOOST_CHECK_EQUAL(n1.second.Next(), &sentinel);
216+
BOOST_CHECK_EQUAL(sentinel.second.Prev(), &n1);
217+
}
218+
219+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)