Branch data Line data Source code
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>
6 : : #include <test/util/setup_common.h>
7 : :
8 : : #include <boost/test/unit_test.hpp>
9 : :
10 : : #include <semaphore>
11 : :
12 : : namespace {
13 : :
14 : :
15 : : struct MoveOnlyData {
16 : 1 : 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 : :
26 : 1 : MoveOnlyData MoveOnlyReturnCallback(int val)
27 : : {
28 : 1 : return {val};
29 : : }
30 : :
31 : 4 : void IncrementCallback(int& val)
32 : : {
33 : 4 : val++;
34 : 4 : }
35 : 1 : void SquareCallback(int& val)
36 : : {
37 : 1 : val *= val;
38 : 1 : }
39 : :
40 : 3 : bool ReturnTrue()
41 : : {
42 : 3 : return true;
43 : : }
44 : 1 : bool ReturnFalse()
45 : : {
46 : 1 : return false;
47 : : }
48 : :
49 : : } // anonymous namespace
50 : :
51 : : BOOST_FIXTURE_TEST_SUITE(btcsignals_tests, BasicTestingSetup)
52 : :
53 : : /* Callbacks should always be executed in the order in which they were added
54 : : */
55 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(callback_order)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
56 : : {
57 : 1 : btcsignals::signal<void(int&)> sig0;
58 [ + - ]: 1 : sig0.connect(IncrementCallback);
59 [ + - ]: 1 : sig0.connect(SquareCallback);
60 : 1 : int val{3};
61 [ + - ]: 1 : sig0(val);
62 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 16);
63 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - ]
64 : 1 : }
65 : :
66 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(disconnects)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
67 : : {
68 : 1 : btcsignals::signal<void(int&)> sig0;
69 [ + - ]: 1 : auto conn0 = sig0.connect(IncrementCallback);
70 [ + - ]: 1 : auto conn1 = sig0.connect(SquareCallback);
71 : 1 : conn1.disconnect();
72 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - + - ]
73 : 1 : int val{3};
74 [ + - ]: 1 : sig0(val);
75 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 4);
76 : :
77 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - ]
78 : 1 : conn0.disconnect();
79 [ + - + - : 2 : BOOST_CHECK(sig0.empty());
+ - + - ]
80 [ + - ]: 1 : sig0(val);
81 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 4);
82 : :
83 [ + - - + ]: 2 : conn0 = sig0.connect(IncrementCallback);
84 [ + - - + ]: 2 : conn1 = sig0.connect(IncrementCallback);
85 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - + - ]
86 [ + - ]: 1 : sig0(val);
87 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 6);
88 : 1 : conn1.disconnect();
89 : :
90 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
91 : 1 : {
92 [ + - ]: 2 : btcsignals::scoped_connection scope(conn0);
93 : 1 : }
94 [ + - + - : 2 : BOOST_CHECK(!conn0.connected());
+ - ]
95 [ + - + - : 2 : BOOST_CHECK(sig0.empty());
+ - + - ]
96 [ + - ]: 1 : sig0(val);
97 [ + - + - : 1 : BOOST_CHECK_EQUAL(val, 6);
+ - ]
98 [ + - ]: 2 : }
99 : :
100 : : /* Check that move-only return types work correctly
101 : : */
102 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(moveonly_return)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
103 : : {
104 : 1 : btcsignals::signal<MoveOnlyData(int)> sig0;
105 [ + - ]: 1 : sig0.connect(MoveOnlyReturnCallback);
106 : 1 : int data{3};
107 [ + - ]: 1 : auto ret = sig0(data);
108 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret->m_data, 3);
109 : 1 : }
110 : :
111 : : /* The result of the signal invocation should always be the result of the last
112 : : * enabled callback.
113 : : */
114 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(return_value)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
115 : : {
116 : 1 : btcsignals::signal<bool()> sig0;
117 : 1 : decltype(sig0)::result_type ret;
118 [ + - ]: 1 : ret = sig0();
119 [ + - + - : 2 : BOOST_CHECK(!ret);
+ - ]
120 : 1 : {
121 [ + - + - ]: 1 : btcsignals::scoped_connection conn0 = sig0.connect(ReturnTrue);
122 [ + - ]: 1 : ret = sig0();
123 [ + - + - : 2 : BOOST_CHECK(ret && *ret == true);
- + + - ]
124 : 1 : }
125 [ + - ]: 1 : ret = sig0();
126 [ + - + - : 2 : BOOST_CHECK(!ret);
+ - ]
127 : 1 : {
128 [ + - + - ]: 1 : btcsignals::scoped_connection conn1 = sig0.connect(ReturnTrue);
129 [ + - + - ]: 1 : btcsignals::scoped_connection conn0 = sig0.connect(ReturnFalse);
130 [ + - ]: 1 : ret = sig0();
131 [ + - + - : 2 : BOOST_CHECK(ret && *ret == false);
- + + - ]
132 : 1 : conn0.disconnect();
133 [ + - ]: 1 : ret = sig0();
134 [ + - + - : 2 : BOOST_CHECK(ret && *ret == true);
- + + - ]
135 : 1 : }
136 [ + - ]: 1 : ret = sig0();
137 [ + - + - ]: 2 : BOOST_CHECK(!ret);
138 : 1 : }
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 : : */
148 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(thread_safety)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
149 : : {
150 : 1 : btcsignals::signal<void()> sig0;
151 : 1 : std::atomic<uint32_t> val{0};
152 : 2001 : auto conn0 = sig0.connect([&val] {
153 : 2000 : val++;
154 [ + - ]: 1 : });
155 : :
156 : 2 : std::thread incrementor([&conn0, &sig0] {
157 [ + + ]: 1001 : for (int i = 0; i < 1000; i++) {
158 : 1000 : 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 [ - + ]: 1 : assert(!sig0.empty());
164 [ - + ]: 1 : assert(conn0.connected());
165 [ + - ]: 2 : });
166 : :
167 : 2 : std::thread extra_increment_injector([&conn0, &sig0, &val] {
168 : 1 : static constexpr size_t num_extra_conns{1000};
169 : 1 : std::vector<btcsignals::scoped_connection> extra_conns;
170 [ + - ]: 1 : extra_conns.reserve(num_extra_conns);
171 [ + + ]: 1001 : for (size_t i = 0; i < num_extra_conns; i++) {
172 [ + - + - : 2000 : BOOST_CHECK(!sig0.empty());
+ - + - ]
173 [ + - + - : 2000 : BOOST_CHECK(conn0.connected());
+ - ]
174 [ + - + - ]: 1000 : extra_conns.emplace_back(sig0.connect([&val] {
175 : 512951 : val++;
176 : : }));
177 [ + - ]: 1000 : sig0();
178 : : }
179 [ + - ]: 2 : });
180 [ + - ]: 1 : incrementor.join();
181 [ + - ]: 1 : extra_increment_injector.join();
182 : 1 : conn0.disconnect();
183 [ + - + - : 2 : 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 [ + - + - ]: 1 : BOOST_CHECK_GE(val.load(), 3000);
189 [ + - ]: 2 : }
190 : :
191 : : /* Test that connection and disconnection works from within signal
192 : : * callbacks.
193 : : */
194 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(recursion_safety)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
195 : : {
196 : 1 : btcsignals::connection conn0, conn1, conn2;
197 : 1 : btcsignals::signal<void()> sig0;
198 : 1 : bool nonrecursive_callback_ran{false};
199 : 1 : bool recursive_callback_ran{false};
200 : :
201 [ + - ]: 5 : conn0 = sig0.connect([&] {
202 [ + - + - ]: 8 : BOOST_CHECK(!sig0.empty());
203 : 4 : nonrecursive_callback_ran = true;
204 [ - + ]: 5 : });
205 [ + - + - : 2 : BOOST_CHECK(!nonrecursive_callback_ran);
+ - ]
206 [ + - ]: 1 : sig0();
207 [ + - + - : 2 : BOOST_CHECK(nonrecursive_callback_ran);
+ - ]
208 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
209 : :
210 : 1 : nonrecursive_callback_ran = false;
211 [ + - ]: 2 : conn1 = sig0.connect([&] {
212 : 1 : nonrecursive_callback_ran = true;
213 : 1 : conn1.disconnect();
214 [ - + ]: 1 : });
215 [ + - + - : 2 : BOOST_CHECK(!nonrecursive_callback_ran);
+ - ]
216 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
217 [ + - + - : 2 : BOOST_CHECK(conn1.connected());
+ - ]
218 [ + - ]: 1 : sig0();
219 [ + - + - : 2 : BOOST_CHECK(nonrecursive_callback_ran);
+ - ]
220 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
221 [ + - + - : 2 : BOOST_CHECK(!conn1.connected());
+ - ]
222 : :
223 : 1 : nonrecursive_callback_ran = false;
224 [ + - ]: 2 : conn1 = sig0.connect([&] {
225 : 1 : conn2 = sig0.connect([&] {
226 [ + - ]: 2 : BOOST_CHECK(conn0.connected());
227 : 1 : recursive_callback_ran = true;
228 : 1 : conn0.disconnect();
229 : 1 : conn2.disconnect();
230 [ - + ]: 2 : });
231 : 1 : nonrecursive_callback_ran = true;
232 : 1 : conn1.disconnect();
233 [ - + ]: 2 : });
234 [ + - + - : 2 : BOOST_CHECK(!nonrecursive_callback_ran);
+ - ]
235 [ + - + - : 2 : BOOST_CHECK(!recursive_callback_ran);
+ - ]
236 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
237 [ + - + - : 2 : BOOST_CHECK(conn1.connected());
+ - ]
238 [ + - + - : 2 : BOOST_CHECK(!conn2.connected());
+ - ]
239 [ + - ]: 1 : sig0();
240 [ + - + - : 2 : BOOST_CHECK(nonrecursive_callback_ran);
+ - ]
241 [ + - + - : 2 : BOOST_CHECK(!recursive_callback_ran);
+ - ]
242 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
243 [ + - + - : 2 : BOOST_CHECK(!conn1.connected());
+ - ]
244 [ + - + - : 2 : BOOST_CHECK(conn2.connected());
+ - ]
245 [ + - ]: 1 : sig0();
246 [ + - + - : 2 : BOOST_CHECK(recursive_callback_ran);
+ - ]
247 [ + - + - : 2 : BOOST_CHECK(!conn0.connected());
+ - ]
248 [ + - + - : 2 : BOOST_CHECK(!conn1.connected());
+ - ]
249 [ + - + - ]: 2 : BOOST_CHECK(!conn2.connected());
250 [ + - + - : 4 : }
+ - ]
251 : :
252 : : /* Test that disconnection from another thread works in real time
253 : : */
254 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(disconnect_thread_safety)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
255 : : {
256 : 1 : btcsignals::connection conn0, conn1, conn2;
257 : 1 : btcsignals::signal<void(int&)> sig0;
258 [ + - ]: 1 : std::binary_semaphore done1{0};
259 : 1 : std::binary_semaphore done2{0};
260 : 1 : int val{0};
261 : :
262 [ + - ]: 2 : conn0 = sig0.connect([&](int&) {
263 : 1 : conn1.disconnect();
264 : 1 : done1.release();
265 : 1 : done2.acquire();
266 [ - + ]: 2 : });
267 [ + - - + ]: 2 : conn1 = sig0.connect(IncrementCallback);
268 [ + - - + ]: 2 : conn2 = sig0.connect(IncrementCallback);
269 : 2 : std::thread thr([&] {
270 : 1 : done1.acquire();
271 : 1 : conn2.disconnect();
272 : 1 : done2.release();
273 [ + - ]: 2 : });
274 [ + - ]: 1 : sig0(val);
275 [ + - ]: 1 : thr.join();
276 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 0);
277 [ + - + - : 4 : }
+ - ]
278 : :
279 : :
280 : : BOOST_AUTO_TEST_SUITE_END()
|