LCOV - code coverage report
Current view: top level - src/test - btcsignals_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 100.0 % 186 186
Test Date: 2026-04-27 06:44:50 Functions: 100.0 % 26 26
Branches: 50.2 % 924 464

             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()
        

Generated by: LCOV version 2.0-1