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 % 178 178
Test Date: 2026-06-27 07:07:37 Functions: 100.0 % 23 23
Branches: 50.5 % 838 423

             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 <test/util/setup_common.h>
       6                 :             : #include <util/btcsignals.h>
       7                 :             : 
       8                 :             : #include <boost/test/unit_test.hpp>
       9                 :             : 
      10                 :             : #include <semaphore>
      11                 :             : 
      12                 :             : namespace {
      13                 :             : 
      14                 :             : 
      15                 :           4 : void IncrementCallback(int& val)
      16                 :             : {
      17                 :           4 :     val++;
      18                 :           4 : }
      19                 :           1 : void SquareCallback(int& val)
      20                 :             : {
      21                 :           1 :     val *= val;
      22                 :           1 : }
      23                 :             : 
      24                 :           2 : bool ReturnTrue()
      25                 :             : {
      26                 :           2 :     return true;
      27                 :             : }
      28                 :           2 : bool ReturnFalse()
      29                 :             : {
      30                 :           2 :     return false;
      31                 :             : }
      32                 :             : 
      33                 :             : } // anonymous namespace
      34                 :             : 
      35                 :             : BOOST_FIXTURE_TEST_SUITE(btcsignals_tests, BasicTestingSetup)
      36                 :             : 
      37                 :             : /* Callbacks should always be executed in the order in which they were added
      38                 :             :  */
      39   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(callback_order)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      40                 :             : {
      41                 :           1 :     btcsignals::signal<void(int&)> sig0;
      42         [ +  - ]:           1 :     sig0.connect(IncrementCallback);
      43         [ +  - ]:           1 :     sig0.connect(SquareCallback);
      44                 :           1 :     int val{3};
      45         [ +  - ]:           1 :     sig0(val);
      46   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(val, 16);
      47   [ +  -  +  -  :           2 :     BOOST_CHECK(!sig0.empty());
                   +  - ]
      48                 :           1 : }
      49                 :             : 
      50   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(disconnects)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      51                 :             : {
      52                 :           1 :     btcsignals::signal<void(int&)> sig0;
      53         [ +  - ]:           1 :     auto conn0 = sig0.connect(IncrementCallback);
      54         [ +  - ]:           1 :     auto conn1 = sig0.connect(SquareCallback);
      55                 :           1 :     conn1.disconnect();
      56   [ +  -  +  -  :           2 :     BOOST_CHECK(!sig0.empty());
             +  -  +  - ]
      57                 :           1 :     int val{3};
      58         [ +  - ]:           1 :     sig0(val);
      59   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(val, 4);
      60                 :             : 
      61   [ +  -  +  -  :           2 :     BOOST_CHECK(!sig0.empty());
                   +  - ]
      62                 :           1 :     conn0.disconnect();
      63   [ +  -  +  -  :           2 :     BOOST_CHECK(sig0.empty());
             +  -  +  - ]
      64         [ +  - ]:           1 :     sig0(val);
      65   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(val, 4);
      66                 :             : 
      67   [ +  -  -  + ]:           2 :     conn0 = sig0.connect(IncrementCallback);
      68   [ +  -  -  + ]:           2 :     conn1 = sig0.connect(IncrementCallback);
      69   [ +  -  +  -  :           2 :     BOOST_CHECK(!sig0.empty());
             +  -  +  - ]
      70         [ +  - ]:           1 :     sig0(val);
      71   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(val, 6);
      72                 :           1 :     conn1.disconnect();
      73                 :             : 
      74   [ +  -  +  -  :           2 :     BOOST_CHECK(conn0.connected());
                   +  - ]
      75                 :           1 :     {
      76         [ +  - ]:           2 :         btcsignals::scoped_connection scope(conn0);
      77                 :           1 :     }
      78   [ +  -  +  -  :           2 :     BOOST_CHECK(!conn0.connected());
                   +  - ]
      79   [ +  -  +  -  :           2 :     BOOST_CHECK(sig0.empty());
             +  -  +  - ]
      80         [ +  - ]:           1 :     sig0(val);
      81   [ +  -  +  -  :           1 :     BOOST_CHECK_EQUAL(val, 6);
                   +  - ]
      82         [ +  - ]:           2 : }
      83                 :             : 
      84   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(any_of_combiner)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      85                 :             : {
      86                 :           1 :     btcsignals::signal<bool(), btcsignals::any_of> sig0;
      87                 :           1 :     decltype(sig0)::result_type ret;
      88         [ +  - ]:           1 :     ret = sig0();
      89   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(ret, false);
      90                 :           1 :     {
      91   [ +  -  +  - ]:           1 :         btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
      92         [ +  - ]:           1 :         ret = sig0();
      93   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(ret, true);
      94                 :           1 :     }
      95         [ +  - ]:           1 :     ret = sig0();
      96   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(ret, false);
      97                 :           1 :     {
      98   [ +  -  +  - ]:           1 :         btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
      99   [ +  -  +  - ]:           1 :         btcsignals::scoped_connection conn1{sig0.connect(ReturnFalse)};
     100         [ +  - ]:           1 :         ret = sig0();
     101   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(ret, true);
     102                 :           1 :         conn0.disconnect();
     103         [ +  - ]:           1 :         ret = sig0();
     104   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(ret, false);
     105                 :           1 :     }
     106         [ +  - ]:           1 :     ret = sig0();
     107   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(ret, false);
     108                 :           1 : }
     109                 :             : 
     110                 :             : /* Test the thread-safety of connect/disconnect/empty/connected/callbacks.
     111                 :             :  * Connect sig0 to an incrementor function and loop in a thread.
     112                 :             :  * Meanwhile, in another thread, inject and call new increment callbacks.
     113                 :             :  * Both threads are constantly calling empty/connected.
     114                 :             :  * The end-result must be deterministic for the atomic modified by conn0.
     115                 :             :  * Though, the end-result for the atomic modified by the extra connections is
     116                 :             :  * undefined due to a non-deterministic number of total callbacks executed.
     117                 :             :  * In any case, this should all be completely threadsafe.
     118                 :             :  * Sanitizers should pick up any buggy data race behavior (if present).
     119                 :             :  */
     120   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(thread_safety)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     121                 :             : {
     122                 :           1 :     btcsignals::signal<void()> sig0;
     123                 :           1 :     std::atomic<uint32_t> val_det{0};
     124                 :           1 :     std::atomic<uint32_t> val_non_det{0};
     125         [ +  - ]:        2001 :     auto conn0 = sig0.connect([&val_det] { val_det++; });
     126                 :             : 
     127                 :           2 :     std::thread incrementor([&conn0, &sig0] {
     128         [ +  + ]:        1001 :         for (int i = 0; i < 1000; i++) {
     129                 :        1000 :             sig0();
     130                 :             :         }
     131                 :             :         // Because these calls are purposely happening on both threads at the
     132                 :             :         // same time, these must be asserts rather than BOOST_CHECKs to prevent
     133                 :             :         // a race inside of BOOST_CHECK itself (writing to the log).
     134         [ -  + ]:           1 :         assert(!sig0.empty());
     135         [ -  + ]:           1 :         assert(conn0.connected());
     136         [ +  - ]:           2 :     });
     137                 :             : 
     138                 :           2 :     std::thread extra_increment_injector([&conn0, &sig0, &val_non_det] {
     139                 :           1 :         static constexpr size_t num_extra_conns{1000};
     140                 :           1 :         std::vector<btcsignals::scoped_connection> extra_conns;
     141         [ +  - ]:           1 :         extra_conns.reserve(num_extra_conns);
     142         [ +  + ]:        1001 :         for (size_t i = 0; i < num_extra_conns; i++) {
     143   [ +  -  +  -  :        2000 :             BOOST_CHECK(!sig0.empty());
             +  -  +  - ]
     144   [ +  -  +  -  :        2000 :             BOOST_CHECK(conn0.connected());
                   +  - ]
     145   [ +  -  +  + ]:        1000 :             btcsignals::scoped_connection extra{sig0.connect([&val_non_det] { val_non_det++; })};
     146         [ +  + ]:        1000 :             if (i % 2 == 0) {
     147         [ +  - ]:         500 :                 extra_conns.emplace_back(std::move(extra));
     148                 :             :             }
     149         [ +  - ]:        1000 :             sig0();
     150                 :        1000 :         }
     151         [ +  - ]:           2 :     });
     152         [ +  - ]:           1 :     incrementor.join();
     153         [ +  - ]:           1 :     extra_increment_injector.join();
     154                 :           1 :     conn0.disconnect();
     155   [ +  -  +  -  :           2 :     BOOST_CHECK(sig0.empty());
             +  -  +  - ]
     156                 :             : 
     157                 :             :     // sig0 will have been called 2000 times, and only the first connection did
     158                 :             :     // increment val_det, so it must be 2000.
     159   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(val_det.load(), 2000);
     160                 :             :     // The number of connections that increment val_non_det is growing from 1
     161                 :             :     // to 500, where 500 are disconnected immediately again after the step.
     162                 :             :     // Before the end of each step the connections are called at least once.
     163                 :             :     // However, it is unknown how often the connections have been called
     164                 :             :     // exactly. The 500th Triangular Number gives a lower estimate.
     165                 :             :     // T_n=n(n+1)/2
     166   [ +  -  +  - ]:           1 :     BOOST_CHECK_GE(val_non_det.load(), 500 * 501 / 2);
     167         [ +  - ]:           2 : }
     168                 :             : 
     169                 :             : /* Test that connection and disconnection works from within signal
     170                 :             :  * callbacks.
     171                 :             :  */
     172   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(recursion_safety)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     173                 :             : {
     174                 :           1 :     btcsignals::connection conn0, conn1, conn2;
     175                 :           1 :     btcsignals::signal<void()> sig0;
     176                 :           1 :     bool nonrecursive_callback_ran{false};
     177                 :           1 :     bool recursive_callback_ran{false};
     178                 :             : 
     179         [ +  - ]:           5 :     conn0 = sig0.connect([&] {
     180   [ +  -  +  - ]:           8 :         BOOST_CHECK(!sig0.empty());
     181                 :           4 :         nonrecursive_callback_ran = true;
     182         [ -  + ]:           5 :     });
     183   [ +  -  +  -  :           2 :     BOOST_CHECK(!nonrecursive_callback_ran);
                   +  - ]
     184         [ +  - ]:           1 :     sig0();
     185   [ +  -  +  -  :           2 :     BOOST_CHECK(nonrecursive_callback_ran);
                   +  - ]
     186   [ +  -  +  -  :           2 :     BOOST_CHECK(conn0.connected());
                   +  - ]
     187                 :             : 
     188                 :           1 :     nonrecursive_callback_ran = false;
     189         [ +  - ]:           2 :     conn1 = sig0.connect([&] {
     190                 :           1 :         nonrecursive_callback_ran = true;
     191                 :           1 :         conn1.disconnect();
     192         [ -  + ]:           1 :     });
     193   [ +  -  +  -  :           2 :     BOOST_CHECK(!nonrecursive_callback_ran);
                   +  - ]
     194   [ +  -  +  -  :           2 :     BOOST_CHECK(conn0.connected());
                   +  - ]
     195   [ +  -  +  -  :           2 :     BOOST_CHECK(conn1.connected());
                   +  - ]
     196         [ +  - ]:           1 :     sig0();
     197   [ +  -  +  -  :           2 :     BOOST_CHECK(nonrecursive_callback_ran);
                   +  - ]
     198   [ +  -  +  -  :           2 :     BOOST_CHECK(conn0.connected());
                   +  - ]
     199   [ +  -  +  -  :           2 :     BOOST_CHECK(!conn1.connected());
                   +  - ]
     200                 :             : 
     201                 :           1 :     nonrecursive_callback_ran = false;
     202         [ +  - ]:           2 :     conn1 = sig0.connect([&] {
     203                 :           1 :         conn2 = sig0.connect([&] {
     204         [ +  - ]:           2 :             BOOST_CHECK(conn0.connected());
     205                 :           1 :             recursive_callback_ran = true;
     206                 :           1 :             conn0.disconnect();
     207                 :           1 :             conn2.disconnect();
     208         [ -  + ]:           2 :         });
     209                 :           1 :         nonrecursive_callback_ran = true;
     210                 :           1 :         conn1.disconnect();
     211         [ -  + ]:           2 :     });
     212   [ +  -  +  -  :           2 :     BOOST_CHECK(!nonrecursive_callback_ran);
                   +  - ]
     213   [ +  -  +  -  :           2 :     BOOST_CHECK(!recursive_callback_ran);
                   +  - ]
     214   [ +  -  +  -  :           2 :     BOOST_CHECK(conn0.connected());
                   +  - ]
     215   [ +  -  +  -  :           2 :     BOOST_CHECK(conn1.connected());
                   +  - ]
     216   [ +  -  +  -  :           2 :     BOOST_CHECK(!conn2.connected());
                   +  - ]
     217         [ +  - ]:           1 :     sig0();
     218   [ +  -  +  -  :           2 :     BOOST_CHECK(nonrecursive_callback_ran);
                   +  - ]
     219   [ +  -  +  -  :           2 :     BOOST_CHECK(!recursive_callback_ran);
                   +  - ]
     220   [ +  -  +  -  :           2 :     BOOST_CHECK(conn0.connected());
                   +  - ]
     221   [ +  -  +  -  :           2 :     BOOST_CHECK(!conn1.connected());
                   +  - ]
     222   [ +  -  +  -  :           2 :     BOOST_CHECK(conn2.connected());
                   +  - ]
     223         [ +  - ]:           1 :     sig0();
     224   [ +  -  +  -  :           2 :     BOOST_CHECK(recursive_callback_ran);
                   +  - ]
     225   [ +  -  +  -  :           2 :     BOOST_CHECK(!conn0.connected());
                   +  - ]
     226   [ +  -  +  -  :           2 :     BOOST_CHECK(!conn1.connected());
                   +  - ]
     227   [ +  -  +  - ]:           2 :     BOOST_CHECK(!conn2.connected());
     228   [ +  -  +  -  :           4 : }
                   +  - ]
     229                 :             : 
     230                 :             : /* Test that disconnection from another thread works in real time
     231                 :             :  */
     232   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(disconnect_thread_safety)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     233                 :             : {
     234                 :           1 :     btcsignals::connection conn0, conn1, conn2;
     235                 :           1 :     btcsignals::signal<void(int&)> sig0;
     236         [ +  - ]:           1 :     std::binary_semaphore done1{0};
     237                 :           1 :     std::binary_semaphore done2{0};
     238                 :           1 :     int val{0};
     239                 :             : 
     240         [ +  - ]:           2 :     conn0 = sig0.connect([&](int&) {
     241                 :           1 :         conn1.disconnect();
     242                 :           1 :         done1.release();
     243                 :           1 :         done2.acquire();
     244         [ -  + ]:           2 :     });
     245   [ +  -  -  + ]:           2 :     conn1 = sig0.connect(IncrementCallback);
     246   [ +  -  -  + ]:           2 :     conn2 = sig0.connect(IncrementCallback);
     247                 :           2 :     std::thread thr([&] {
     248                 :           1 :         done1.acquire();
     249                 :           1 :         conn2.disconnect();
     250                 :           1 :         done2.release();
     251         [ +  - ]:           2 :     });
     252         [ +  - ]:           1 :     sig0(val);
     253         [ +  - ]:           1 :     thr.join();
     254   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(val, 0);
     255   [ +  -  +  -  :           4 : }
                   +  - ]
     256                 :             : 
     257                 :             : 
     258                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1