/ src / test / btcsignals_tests.cpp
btcsignals_tests.cpp
  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      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  MoveOnlyData MoveOnlyReturnCallback(int val)
 27  {
 28      return {val};
 29  }
 30  
 31  void IncrementCallback(int& val)
 32  {
 33      val++;
 34  }
 35  void SquareCallback(int& val)
 36  {
 37      val *= val;
 38  }
 39  
 40  bool ReturnTrue()
 41  {
 42      return true;
 43  }
 44  bool ReturnFalse()
 45  {
 46      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  BOOST_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  
 66  BOOST_AUTO_TEST_CASE(disconnects)
 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      {
 92          btcsignals::scoped_connection scope(conn0);
 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   */
102  BOOST_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   */
114  BOOST_AUTO_TEST_CASE(return_value)
115  {
116      btcsignals::signal<bool()> sig0;
117      decltype(sig0)::result_type ret;
118      ret = sig0();
119      BOOST_CHECK(!ret);
120      {
121          btcsignals::scoped_connection conn0 = sig0.connect(ReturnTrue);
122          ret = sig0();
123          BOOST_CHECK(ret && *ret == true);
124      }
125      ret = sig0();
126      BOOST_CHECK(!ret);
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();
137      BOOST_CHECK(!ret);
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   */
148  BOOST_AUTO_TEST_CASE(thread_safety)
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   */
194  BOOST_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   */
254  BOOST_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  
280  BOOST_AUTO_TEST_SUITE_END()