Bitcoin Core 31.99.0
P2P Digital Currency
btcsignals_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 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 <btcsignals.h>
7
8#include <boost/test/unit_test.hpp>
9
10#include <semaphore>
11
12namespace {
13
14
15struct MoveOnlyData {
16 MoveOnlyData(int data) : m_data(data) {}
17 MoveOnlyData(MoveOnlyData&&) = default;
18
19 MoveOnlyData& operator=(MoveOnlyData&&) = delete;
20 MoveOnlyData(const MoveOnlyData&) = delete;
21 MoveOnlyData& operator=(const MoveOnlyData&) = delete;
22
23 int m_data;
24};
25
26MoveOnlyData MoveOnlyReturnCallback(int val)
27{
28 return {val};
29}
30
31void IncrementCallback(int& val)
32{
33 val++;
34}
35void SquareCallback(int& val)
36{
37 val *= val;
38}
39
40bool ReturnTrue()
41{
42 return true;
43}
44bool ReturnFalse()
45{
46 return false;
47}
48
49} // anonymous namespace
50
52
53/* Callbacks should always be executed in the order in which they were added
54 */
55BOOST_AUTO_TEST_CASE(callback_order)
56{
57 btcsignals::signal<void(int&)> sig0;
58 sig0.connect(IncrementCallback);
59 sig0.connect(SquareCallback);
60 int val{3};
61 sig0(val);
62 BOOST_CHECK_EQUAL(val, 16);
63 BOOST_CHECK(!sig0.empty());
64}
65
67{
68 btcsignals::signal<void(int&)> sig0;
69 auto conn0 = sig0.connect(IncrementCallback);
70 auto conn1 = sig0.connect(SquareCallback);
71 conn1.disconnect();
72 BOOST_CHECK(!sig0.empty());
73 int val{3};
74 sig0(val);
75 BOOST_CHECK_EQUAL(val, 4);
76
77 BOOST_CHECK(!sig0.empty());
78 conn0.disconnect();
79 BOOST_CHECK(sig0.empty());
80 sig0(val);
81 BOOST_CHECK_EQUAL(val, 4);
82
83 conn0 = sig0.connect(IncrementCallback);
84 conn1 = sig0.connect(IncrementCallback);
85 BOOST_CHECK(!sig0.empty());
86 sig0(val);
87 BOOST_CHECK_EQUAL(val, 6);
88 conn1.disconnect();
89
90 BOOST_CHECK(conn0.connected());
91 {
93 }
94 BOOST_CHECK(!conn0.connected());
95 BOOST_CHECK(sig0.empty());
96 sig0(val);
97 BOOST_CHECK_EQUAL(val, 6);
98}
99
100/* Check that move-only return types work correctly
101 */
102BOOST_AUTO_TEST_CASE(moveonly_return)
103{
104 btcsignals::signal<MoveOnlyData(int)> sig0;
105 sig0.connect(MoveOnlyReturnCallback);
106 int data{3};
107 auto ret = sig0(data);
108 BOOST_CHECK_EQUAL(ret->m_data, 3);
109}
110
111/* The result of the signal invocation should always be the result of the last
112 * enabled callback.
113 */
115{
116 btcsignals::signal<bool()> sig0;
117 decltype(sig0)::result_type ret;
118 ret = sig0();
120 {
121 btcsignals::scoped_connection conn0 = sig0.connect(ReturnTrue);
122 ret = sig0();
123 BOOST_CHECK(ret && *ret == true);
124 }
125 ret = sig0();
127 {
128 btcsignals::scoped_connection conn1 = sig0.connect(ReturnTrue);
129 btcsignals::scoped_connection conn0 = sig0.connect(ReturnFalse);
130 ret = sig0();
131 BOOST_CHECK(ret && *ret == false);
132 conn0.disconnect();
133 ret = sig0();
134 BOOST_CHECK(ret && *ret == true);
135 }
136 ret = sig0();
138}
139
140/* Test the thread-safety of connect/disconnect/empty/connected/callbacks.
141 * Connect sig0 to an incrementor function and loop in a thread.
142 * Meanwhile, in another thread, inject and call new increment callbacks.
143 * Both threads are constantly calling empty/connected.
144 * Though the end-result is undefined due to a non-deterministic number of
145 * total callbacks executed, this should all be completely threadsafe.
146 * Sanitizers should pick up any buggy data race behavior (if present).
147 */
149{
150 btcsignals::signal<void()> sig0;
151 std::atomic<uint32_t> val{0};
152 auto conn0 = sig0.connect([&val] {
153 val++;
154 });
155
156 std::thread incrementor([&conn0, &sig0] {
157 for (int i = 0; i < 1000; i++) {
158 sig0();
159 }
160 // Because these calls are purposely happening on both threads at the
161 // same time, these must be asserts rather than BOOST_CHECKs to prevent
162 // a race inside of BOOST_CHECK itself (writing to the log).
163 assert(!sig0.empty());
164 assert(conn0.connected());
165 });
166
167 std::thread extra_increment_injector([&conn0, &sig0, &val] {
168 static constexpr size_t num_extra_conns{1000};
169 std::vector<btcsignals::scoped_connection> extra_conns;
170 extra_conns.reserve(num_extra_conns);
171 for (size_t i = 0; i < num_extra_conns; i++) {
172 BOOST_CHECK(!sig0.empty());
173 BOOST_CHECK(conn0.connected());
174 extra_conns.emplace_back(sig0.connect([&val] {
175 val++;
176 }));
177 sig0();
178 }
179 });
180 incrementor.join();
181 extra_increment_injector.join();
182 conn0.disconnect();
183 BOOST_CHECK(sig0.empty());
184
185 // sig will have been called 2000 times, and at least 1000 of those will
186 // have been executing multiple incrementing callbacks. So while val is
187 // probably MUCH bigger, it's guaranteed to be at least 3000.
188 BOOST_CHECK_GE(val.load(), 3000);
189}
190
191/* Test that connection and disconnection works from within signal
192 * callbacks.
193 */
194BOOST_AUTO_TEST_CASE(recursion_safety)
195{
196 btcsignals::connection conn0, conn1, conn2;
197 btcsignals::signal<void()> sig0;
198 bool nonrecursive_callback_ran{false};
199 bool recursive_callback_ran{false};
200
201 conn0 = sig0.connect([&] {
202 BOOST_CHECK(!sig0.empty());
203 nonrecursive_callback_ran = true;
204 });
205 BOOST_CHECK(!nonrecursive_callback_ran);
206 sig0();
207 BOOST_CHECK(nonrecursive_callback_ran);
208 BOOST_CHECK(conn0.connected());
209
210 nonrecursive_callback_ran = false;
211 conn1 = sig0.connect([&] {
212 nonrecursive_callback_ran = true;
213 conn1.disconnect();
214 });
215 BOOST_CHECK(!nonrecursive_callback_ran);
216 BOOST_CHECK(conn0.connected());
217 BOOST_CHECK(conn1.connected());
218 sig0();
219 BOOST_CHECK(nonrecursive_callback_ran);
220 BOOST_CHECK(conn0.connected());
221 BOOST_CHECK(!conn1.connected());
222
223 nonrecursive_callback_ran = false;
224 conn1 = sig0.connect([&] {
225 conn2 = sig0.connect([&] {
226 BOOST_CHECK(conn0.connected());
227 recursive_callback_ran = true;
228 conn0.disconnect();
229 conn2.disconnect();
230 });
231 nonrecursive_callback_ran = true;
232 conn1.disconnect();
233 });
234 BOOST_CHECK(!nonrecursive_callback_ran);
235 BOOST_CHECK(!recursive_callback_ran);
236 BOOST_CHECK(conn0.connected());
237 BOOST_CHECK(conn1.connected());
238 BOOST_CHECK(!conn2.connected());
239 sig0();
240 BOOST_CHECK(nonrecursive_callback_ran);
241 BOOST_CHECK(!recursive_callback_ran);
242 BOOST_CHECK(conn0.connected());
243 BOOST_CHECK(!conn1.connected());
244 BOOST_CHECK(conn2.connected());
245 sig0();
246 BOOST_CHECK(recursive_callback_ran);
247 BOOST_CHECK(!conn0.connected());
248 BOOST_CHECK(!conn1.connected());
249 BOOST_CHECK(!conn2.connected());
250}
251
252/* Test that disconnection from another thread works in real time
253 */
254BOOST_AUTO_TEST_CASE(disconnect_thread_safety)
255{
256 btcsignals::connection conn0, conn1, conn2;
257 btcsignals::signal<void(int&)> sig0;
258 std::binary_semaphore done1{0};
259 std::binary_semaphore done2{0};
260 int val{0};
261
262 conn0 = sig0.connect([&](int&) {
263 conn1.disconnect();
264 done1.release();
265 done2.acquire();
266 });
267 conn1 = sig0.connect(IncrementCallback);
268 conn2 = sig0.connect(IncrementCallback);
269 std::thread thr([&] {
270 done1.acquire();
271 conn2.disconnect();
272 done2.release();
273 });
274 sig0(val);
275 thr.join();
276 BOOST_CHECK_EQUAL(val, 0);
277}
278
279
int ret
BOOST_AUTO_TEST_CASE(callback_order)
void disconnect()
If a callback is associated with this connection, prevent it from being called in the future.
Definition: btcsignals.h:98
bool connected() const
Returns true if this connection was created by a signal and has not been disabled.
Definition: btcsignals.h:109
connection connect(Callable &&func) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: btcsignals.h:232
bool empty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: btcsignals.h:246
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:17
#define BOOST_CHECK(expr)
Definition: object.cpp:16
Basic testing setup.
Definition: setup_common.h:61
assert(!tx.IsCoinBase())