LCOV - code coverage report
Current view: top level - tests - catch_thread.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 160 170 94.1 %
Date: 2024-11-29 21:35:41 Functions: 12 12 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2006-2022  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/cppthread
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      19             : 
      20             : // cppthread
      21             : //
      22             : #include    <cppthread/exception.h>
      23             : #include    <cppthread/fifo.h>
      24             : #include    <cppthread/guard.h>
      25             : #include    <cppthread/life.h>
      26             : #include    <cppthread/log.h>
      27             : #include    <cppthread/mutex.h>
      28             : #include    <cppthread/pool.h>
      29             : #include    <cppthread/runner.h>
      30             : #include    <cppthread/thread.h>
      31             : #include    <cppthread/worker.h>
      32             : 
      33             : 
      34             : // self
      35             : //
      36             : #include    "catch_main.h"
      37             : 
      38             : 
      39             : // snapdev
      40             : //
      41             : #include    <snapdev/not_reached.h>
      42             : 
      43             : 
      44             : // C++
      45             : //
      46             : #include    <deque>
      47             : 
      48             : 
      49             : 
      50             : namespace
      51             : {
      52             : 
      53             : 
      54             : struct log_message_t
      55             : {
      56             :     typedef std::deque<log_message_t>   queue_t;
      57             : 
      58             :     cppthread::log_level_t  f_level = static_cast<cppthread::log_level_t>(-1);      // an invalid level by default
      59             :     std::string             f_message = std::string();
      60             : };
      61             : 
      62             : 
      63             : log_message_t::queue_t g_message_queue = log_message_t::queue_t();
      64             : int g_empty_log_queue = 0;
      65             : int g_unexpected_log_message = 0;
      66             : int g_unexpected_log_level = 0;
      67             : 
      68             : 
      69           4 : void cppthread_log_callback(cppthread::log_level_t level, std::string const & message)
      70             : {
      71           4 :     cppthread::guard lock(*cppthread::g_system_mutex);
      72             : 
      73           4 :     if(g_message_queue.empty())
      74             :     {
      75           2 :         ++g_empty_log_queue;
      76             :     }
      77             :     else
      78             :     {
      79           2 :         if(g_message_queue.front().f_level != level)
      80             :         {
      81           0 :             ++g_unexpected_log_level;
      82             :         }
      83           2 :         if(g_message_queue.front().f_message != message)
      84             :         {
      85           0 :             ++g_unexpected_log_message;
      86             :         }
      87           2 :         g_message_queue.pop_front();
      88             :     }
      89           8 : }
      90             : 
      91             : 
      92             : constexpr int EXIT_THREAD = -1;
      93             : constexpr int EXIT_THREAD_WITH_EXCEPTION = -2;
      94             : 
      95             : 
      96           2 : DECLARE_EXCEPTION(cppthread::cppthread_exception, exit_with_exception);
      97             : 
      98             : 
      99             : struct data_t
     100             : {
     101             :     typedef std::deque<data_t>      queue_t;
     102             : 
     103             :     int     f_value = 0;
     104             : 
     105          12 :     bool operator == (data_t const & rhs)
     106             :     {
     107          12 :         return f_value == rhs.f_value;
     108             :     }
     109             : };
     110             : 
     111             : 
     112             : typedef cppthread::fifo<data_t>     fifo_t;
     113             : 
     114             : 
     115             : class test_runner
     116             :     : public cppthread::runner
     117             : {
     118             : public:
     119           4 :     test_runner()
     120           4 :         : runner("test-runner")
     121             :     {
     122           4 :     }
     123             : 
     124           4 :     virtual bool is_ready() const override
     125             :     {
     126           4 :         return f_ready;
     127             :     }
     128             : 
     129        2137 :     virtual bool continue_running() const override
     130             :     {
     131        2137 :         if(!runner::continue_running())
     132             :         {
     133           0 :             return false;
     134             :         }
     135             : 
     136        2137 :         return f_continue_running;
     137             :     }
     138             : 
     139           3 :     virtual void enter() override
     140             :     {
     141           3 :         f_entered = true;
     142           3 :     }
     143             : 
     144           3 :     virtual void run() override
     145             :     {
     146        2137 :         while(continue_running())
     147             :         {
     148        2137 :             data_t data;
     149        2137 :             if(!f_fifo.pop_front(data, 500))
     150             :             {
     151        2125 :                 ++f_cycles;
     152        2125 :                 continue;
     153             :             }
     154          12 :             if(f_expected.empty())
     155             :             {
     156           0 :                 ++f_unexpected_content;
     157             :             }
     158             :             else
     159             :             {
     160             : //std::cerr << "--- got data: " << data.f_value << " (expects: " << f_expected.front().f_value << ")\n";
     161          12 :                 if(data != f_expected.front())
     162             :                 {
     163           0 :                     ++f_invalid_content;
     164             :                 }
     165             :                 else
     166             :                 {
     167          12 :                     switch(data.f_value)
     168             :                     {
     169           1 :                     case EXIT_THREAD:
     170           1 :                         return;
     171             : 
     172           2 :                     case EXIT_THREAD_WITH_EXCEPTION:
     173           2 :                         throw exit_with_exception("testing thread exiting with exception.");
     174             : 
     175             :                     }
     176             :                 }
     177           9 :                 f_expected.pop_front();
     178             :             }
     179             :         }
     180             : 
     181           0 :         f_stopped_running = true;
     182             :     }
     183             : 
     184           3 :     virtual void leave(cppthread::leave_status_t status) override
     185             :     {
     186           3 :         f_leave_status = status;
     187           3 :     }
     188             : 
     189             :     fifo_t                      f_fifo = fifo_t();
     190             :     data_t::queue_t             f_expected = data_t::queue_t();
     191             :     bool                        f_ready = true;
     192             :     bool                        f_continue_running = true;
     193             :     bool                        f_entered = false;
     194             :     bool                        f_stopped_running = false; // if true, the continue_running() function returned false
     195             :     int                         f_cycles = 0;
     196             :     int                         f_unexpected_content = 0;
     197             :     int                         f_invalid_content = 0;
     198             :     cppthread::leave_status_t   f_leave_status = cppthread::leave_status_t();
     199             : };
     200             : 
     201             : 
     202             : cppthread::thread * g_stop_callback_thread = nullptr;
     203             : 
     204           1 : void stop_callback(cppthread::thread * t)
     205             : {
     206           1 :     g_stop_callback_thread = t;
     207           1 : }
     208             : 
     209             : 
     210             : 
     211             : } // no name namespace
     212             : 
     213             : 
     214           3 : CATCH_TEST_CASE("cppthread", "[thread][valid]")
     215             : {
     216           3 :     CATCH_START_SECTION("cppthread: simple threading")
     217             :     {
     218           1 :         cppthread::set_log_callback(cppthread_log_callback);
     219             : 
     220           1 :         test_runner r;
     221           3 :         cppthread::thread t("test-thread", &r);
     222           1 :         CATCH_REQUIRE(t.get_runner() == &r);
     223           1 :         CATCH_REQUIRE(t.get_name() == "test-thread");
     224           1 :         CATCH_REQUIRE_FALSE(t.is_running());
     225             : 
     226             :         // first check that the is_ready() works as expected
     227             :         //
     228           1 :         r.f_ready = false;
     229           1 :         g_message_queue.push_back({
     230             :                 cppthread::log_level_t::warning,
     231             :                 "the thread runner is not ready.",
     232             :             });
     233           1 :         CATCH_REQUIRE_FALSE(t.start());
     234           1 :         CATCH_REQUIRE(g_message_queue.empty());
     235           1 :         CATCH_REQUIRE_FALSE(t.is_running());
     236           1 :         r.f_ready = true;
     237             : 
     238             :         // WARNING: at the moment this queue is not protected so create it
     239             :         //          at the start and then check once the thread is done that
     240             :         //          it is indeed empty
     241             :         //
     242           1 :         r.f_expected.push_back({1});
     243           1 :         r.f_expected.push_back({2});
     244           1 :         r.f_expected.push_back({3});
     245           1 :         r.f_expected.push_back({EXIT_THREAD});
     246             : 
     247             :         // now really start the runner
     248             :         //
     249           1 :         CATCH_REQUIRE(t.start());
     250           1 :         CATCH_REQUIRE(t.is_running());
     251             : 
     252             :         // now another attempt at calling start() fails with a log message
     253             :         //
     254           1 :         g_message_queue.push_back({
     255             :                 cppthread::log_level_t::warning,
     256             :                 "the thread is already running.",
     257             :             });
     258           1 :         CATCH_REQUIRE_FALSE(t.start());
     259           1 :         CATCH_REQUIRE(g_message_queue.empty());
     260             : 
     261           1 :         timespec wait = {0, 100'000'000};
     262           1 :         timespec rem = timespec();
     263             : 
     264           1 :         nanosleep(&wait, &rem);
     265           1 :         r.f_fifo.push_back({1});
     266           1 :         nanosleep(&wait, &rem);
     267           1 :         r.f_fifo.push_back({2});
     268           1 :         nanosleep(&wait, &rem);
     269           1 :         r.f_fifo.push_back({3});
     270           1 :         nanosleep(&wait, &rem);
     271           1 :         CATCH_REQUIRE(t.is_running()); // after this point, we pushed the EXIT_THREAD so the thread may quit any time now
     272           1 :         r.f_fifo.push_back({EXIT_THREAD});
     273           1 :         nanosleep(&wait, &rem);
     274             : 
     275             :         // wait for the runner to stop (join with the thread)
     276             :         //
     277           1 :         t.stop(&stop_callback);
     278           1 :         CATCH_REQUIRE(g_stop_callback_thread == &t);
     279           1 :         CATCH_REQUIRE_FALSE(t.is_running());
     280             : 
     281           1 :         CATCH_REQUIRE(r.f_cycles > 0);
     282           1 :         CATCH_REQUIRE_FALSE(r.f_stopped_running);
     283             : 
     284           1 :     }
     285           3 :     CATCH_END_SECTION()
     286             : 
     287           3 :     CATCH_START_SECTION("cppthread: create runner and throw an exception")
     288             :     {
     289           1 :         cppthread::set_log_callback(cppthread_log_callback);
     290             : 
     291           1 :         test_runner r;
     292           3 :         cppthread::thread t("test-thread", &r);
     293             : 
     294           1 :         bool got_exception(false);
     295             :         try
     296             :         {
     297             :             // WARNING: at the moment this queue is not protected so create it
     298             :             //          at the start and then check once the thread is done that
     299             :             //          it is indeed empty
     300             :             //
     301           1 :             r.f_expected.push_back({1});
     302           1 :             r.f_expected.push_back({2});
     303           1 :             r.f_expected.push_back({3});
     304           1 :             r.f_expected.push_back({EXIT_THREAD_WITH_EXCEPTION});
     305             : 
     306             :             // now really start the runner
     307             :             //
     308           1 :             CATCH_REQUIRE(t.start());
     309             : 
     310           1 :             timespec wait = {0, 100'000'000};
     311           1 :             timespec rem = timespec();
     312             : 
     313           1 :             nanosleep(&wait, &rem);
     314           1 :             r.f_fifo.push_back({1});
     315           1 :             nanosleep(&wait, &rem);
     316           1 :             r.f_fifo.push_back({2});
     317           1 :             nanosleep(&wait, &rem);
     318           1 :             r.f_fifo.push_back({3});
     319           1 :             nanosleep(&wait, &rem);
     320           1 :             r.f_fifo.push_back({EXIT_THREAD_WITH_EXCEPTION});
     321             :             //wait.tv_nsec = 500'000'000;
     322           1 :             nanosleep(&wait, &rem);
     323             : 
     324             :             // now stop the thread
     325             :             //
     326           2 :             t.stop();
     327             :         }
     328           1 :         catch(exit_with_exception const & e)
     329             :         {
     330           1 :             CATCH_REQUIRE(std::string(e.what()) == "cppthread_exception: testing thread exiting with exception.");
     331           1 :             got_exception = true;
     332           1 :         }
     333             : 
     334           1 :         CATCH_REQUIRE(got_exception);
     335           1 :         CATCH_REQUIRE(t.get_exception() == std::exception_ptr()); // the stop() clears the exception in the thread
     336           1 :         CATCH_REQUIRE(r.f_cycles > 0);
     337           1 :         CATCH_REQUIRE_FALSE(r.f_stopped_running);
     338             : 
     339           1 :         CATCH_REQUIRE(g_message_queue.empty());
     340           1 :     }
     341           3 :     CATCH_END_SECTION()
     342             : 
     343           3 :     CATCH_START_SECTION("cppthread: create runner and throw an exception, but auto-destruction drops the exception")
     344             :     {
     345           1 :         cppthread::set_log_callback(cppthread_log_callback);
     346             : 
     347           1 :         test_runner r;
     348             : 
     349           1 :         bool got_exception(false);
     350             :         try
     351             :         {
     352           3 :             cppthread::thread t("test-thread", &r);
     353             : 
     354             :             // WARNING: at the moment this queue is not protected so create it
     355             :             //          at the start and then check once the thread is done that
     356             :             //          it is indeed empty
     357             :             //
     358           1 :             r.f_expected.push_back({1});
     359           1 :             r.f_expected.push_back({2});
     360           1 :             r.f_expected.push_back({3});
     361           1 :             r.f_expected.push_back({EXIT_THREAD_WITH_EXCEPTION});
     362             : 
     363             :             // now really start the runner
     364             :             //
     365           1 :             CATCH_REQUIRE(t.start());
     366             : 
     367           1 :             timespec wait = {0, 100'000'000};
     368           1 :             timespec rem = timespec();
     369             : 
     370           1 :             nanosleep(&wait, &rem);
     371           1 :             r.f_fifo.push_back({1});
     372           1 :             nanosleep(&wait, &rem);
     373           1 :             r.f_fifo.push_back({2});
     374           1 :             nanosleep(&wait, &rem);
     375           1 :             r.f_fifo.push_back({3});
     376           1 :             nanosleep(&wait, &rem);
     377           1 :             r.f_fifo.push_back({EXIT_THREAD_WITH_EXCEPTION});
     378             :             //wait.tv_nsec = 500'000'000;
     379           1 :             nanosleep(&wait, &rem);
     380             : 
     381             :             // delete the thread using RAII, we lose the exception...
     382             :             // (the destructor cannot re-throw, only std::terminate()...)
     383           1 :         }
     384           0 :         catch(exit_with_exception const & e)
     385             :         {
     386           0 :             CATCH_REQUIRE(std::string(e.what()) == "cppthread_exception: testing thread exiting with exception.");
     387           0 :             got_exception = true;
     388           0 :         }
     389             : 
     390           1 :         CATCH_REQUIRE_FALSE(got_exception);
     391           1 :         CATCH_REQUIRE(r.f_cycles > 0);
     392           1 :         CATCH_REQUIRE_FALSE(r.f_stopped_running);
     393             : 
     394           1 :         CATCH_REQUIRE(g_message_queue.empty());
     395           1 :     }
     396           3 :     CATCH_END_SECTION()
     397           3 : }
     398             : 
     399             : 
     400           2 : CATCH_TEST_CASE("cppthread_errors", "[thread][invalid]")
     401             : {
     402           2 :     CATCH_START_SECTION("cppthread: runner cannot be a null pointer")
     403             :     {
     404           5 :         CATCH_REQUIRE_THROWS_MATCHES(
     405             :                   cppthread::thread("test-thread", nullptr)
     406             :                 , cppthread::invalid_error
     407             :                 , Catch::Matchers::ExceptionMessage("cppthread_exception: runner missing in thread() constructor."));
     408             : 
     409             :         // null smart pointer has the same effect
     410             :         //
     411           1 :         cppthread::runner::pointer_t r;
     412           7 :         CATCH_REQUIRE_THROWS_MATCHES(
     413             :                   cppthread::thread("test-thread", r)
     414             :                 , cppthread::invalid_error
     415             :                 , Catch::Matchers::ExceptionMessage("cppthread_exception: runner missing in thread() constructor."));
     416           1 :     }
     417           2 :     CATCH_END_SECTION()
     418             : 
     419           2 :     CATCH_START_SECTION("cppthread: one runner cannot simultaneously be used with multiple threads")
     420             :     {
     421           1 :         test_runner r;
     422           3 :         cppthread::thread t("okay", &r);
     423             : 
     424           5 :         CATCH_REQUIRE_THROWS_MATCHES(
     425             :                   cppthread::thread("breaks", &r)
     426             :                 , cppthread::in_use_error
     427             :                 , Catch::Matchers::ExceptionMessage("cppthread_exception: this runner (test-runner) is already in use."));
     428           1 :     }
     429           2 :     CATCH_END_SECTION()
     430           2 : }
     431             : 
     432             : 
     433             : 
     434             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions