LCOV - code coverage report
Current view: top level - tests - catch_lockfile.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 94.0 % 285 268
Test Date: 2025-08-31 07:54:46 Functions: 100.0 % 9 9
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright (c) 2018-2025  Made to Order Software Corp.  All Rights Reserved
       2              : //
       3              : // https://snapwebsites.org/project/snapdev
       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 3 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
      17              : // along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18              : 
      19              : /** \file
      20              :  * \brief Verify that the lockfile classes work as expected.
      21              :  *
      22              :  * This file implements tests to verify the lockfile and lockfd functionality.
      23              :  */
      24              : 
      25              : // file being tested
      26              : //
      27              : #include    <snapdev/lockfile.h>
      28              : 
      29              : 
      30              : // self
      31              : //
      32              : #include    "catch_main.h"
      33              : 
      34              : 
      35              : // snapdev
      36              : //
      37              : #include    <snapdev/timespec_ex.h>
      38              : 
      39              : 
      40              : // C++
      41              : //
      42              : #include    <thread>
      43              : 
      44              : 
      45              : // last include
      46              : //
      47              : #include    <snapdev/poison.h>
      48              : 
      49              : 
      50              : 
      51              : namespace
      52              : {
      53              : 
      54              : 
      55              : class lockfile_thread
      56              : {
      57              : public:
      58            7 :     lockfile_thread(
      59              :               std::string const & filename
      60              :             , snapdev::operation_t operation = snapdev::operation_t::OPERATION_EXCLUSIVE)
      61            7 :         : f_filename(filename)
      62            7 :         , f_operation(operation)
      63              :     {
      64            7 :     }
      65              : 
      66              :     lockfile_thread(lockfile_thread const &) = delete;
      67              :     lockfile_thread & operator = (lockfile_thread const &) = delete;
      68              : 
      69            7 :     ~lockfile_thread()
      70              :     {
      71            7 :         if(f_thread != nullptr)
      72              :         {
      73            7 :             f_thread->join();
      74            7 :             delete f_thread;
      75              :         }
      76            7 :     }
      77              : 
      78            7 :     void start_thread(int try_lock = 0)
      79              :     {
      80            7 :         f_running = true;
      81            7 :         if(try_lock)
      82              :         {
      83            4 :             f_thread = new std::thread(&lockfile_thread::run_try_lock, this, try_lock);
      84              :         }
      85              :         else
      86              :         {
      87            3 :             f_thread = new std::thread(&lockfile_thread::run, this);
      88              :         }
      89            7 :     }
      90              : 
      91            3 :     void run()
      92              :     {
      93            3 :         snapdev::lockfile lock(f_filename, f_operation);
      94            3 :         CATCH_REQUIRE_FALSE(lock.is_locked());
      95            3 :         lock.lock();
      96            3 :         CATCH_REQUIRE(lock.is_locked());
      97              : 
      98            3 :         std::lock_guard<std::mutex> guard(f_mutex);
      99            3 :         f_running = false;
     100            6 :     }
     101              : 
     102            4 :     void run_try_lock(int try_lock)
     103              :     {
     104            4 :         snapdev::lockfile lock(f_filename, f_operation);
     105            4 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     106            4 :         lock.try_lock();
     107            4 :         if(try_lock == 1)
     108              :         {
     109            1 :             CATCH_REQUIRE(lock.is_locked());
     110              :         }
     111              :         else
     112              :         {
     113            3 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     114              :         }
     115              : 
     116            4 :         std::lock_guard<std::mutex> guard(f_mutex);
     117            4 :         f_running = false;
     118            8 :     }
     119              : 
     120           15 :     bool is_running() const
     121              :     {
     122           15 :         std::lock_guard<std::mutex> guard(f_mutex);
     123           15 :         return f_running;
     124           15 :     }
     125              : 
     126              : private:
     127              :     mutable std::mutex      f_mutex = std::mutex();
     128              :     std::string             f_filename = std::string();
     129              :     snapdev::operation_t    f_operation = snapdev::operation_t::OPERATION_EXCLUSIVE;
     130              :     std::thread *           f_thread = nullptr;
     131              :     bool                    f_running = false;
     132              : };
     133              : 
     134              : 
     135              : } // no name namespace
     136              : 
     137              : 
     138              : 
     139            9 : CATCH_TEST_CASE("lockfile", "[lock][file]")
     140              : {
     141            9 :     CATCH_START_SECTION("lockfile: simple lock/unlock test")
     142              :     {
     143            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     144            1 :         std::string const filename(path + "/test-1.lock");
     145            1 :         snapdev::lockfile lock(filename);
     146            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     147            1 :         lock.lock();
     148            1 :         CATCH_REQUIRE(lock.is_locked());
     149            1 :         lock.lock();
     150            1 :         CATCH_REQUIRE(lock.is_locked());
     151            1 :         lock.unlock();
     152            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     153            1 :         lock.unlock();
     154            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     155              : 
     156            1 :         CATCH_REQUIRE(lock.get_path() == filename);
     157            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     158            1 :     }
     159            9 :     CATCH_END_SECTION()
     160              : 
     161            9 :     CATCH_START_SECTION("lockfile: test a lock with a thread")
     162              :     {
     163            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     164            1 :         std::string const filename(path + "/test-2.lock");
     165              : 
     166            1 :         snapdev::lockfile lock(filename);
     167            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     168            1 :         lock.lock();
     169            1 :         CATCH_REQUIRE(lock.is_locked());
     170              : 
     171            1 :         lockfile_thread t(filename);
     172            1 :         t.start_thread();
     173            1 :         CATCH_REQUIRE(t.is_running());
     174              : 
     175              :         // still locked
     176              :         //
     177            1 :         CATCH_REQUIRE(lock.is_locked());
     178            1 :         CATCH_REQUIRE(t.is_running());
     179              : 
     180              :         // sleep a bit to make sure things stay locked
     181              :         //
     182            1 :         sleep(2);
     183            1 :         CATCH_REQUIRE(lock.is_locked());
     184            1 :         CATCH_REQUIRE(t.is_running());
     185              : 
     186            1 :         lock.unlock();
     187            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     188              : 
     189              :         for(;;)
     190              :         {
     191            2 :             if(!t.is_running())
     192              :             {
     193            1 :                 break;
     194              :             }
     195            1 :             snapdev::timespec_ex wait(0.001); // 1ms
     196            1 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     197            1 :         }
     198              : 
     199            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     200            1 :     }
     201            9 :     CATCH_END_SECTION()
     202              : 
     203            9 :     CATCH_START_SECTION("lockfile: verify the automatic unlock with a thread")
     204              :     {
     205            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     206            1 :         std::string const filename(path + "/test-3.lock");
     207              : 
     208            1 :         snapdev::lockfile * lock(new snapdev::lockfile(filename));
     209            1 :         CATCH_REQUIRE_FALSE(lock->is_locked());
     210            1 :         lock->lock();
     211            1 :         CATCH_REQUIRE(lock->is_locked());
     212              : 
     213            1 :         lockfile_thread t(filename);
     214            1 :         t.start_thread();
     215            1 :         CATCH_REQUIRE(t.is_running());
     216              : 
     217              :         // still locked
     218              :         //
     219            1 :         CATCH_REQUIRE(lock->is_locked());
     220            1 :         CATCH_REQUIRE(t.is_running());
     221              : 
     222              :         // sleep a bit to make sure things stay locked
     223              :         //
     224            1 :         sleep(2);
     225            1 :         CATCH_REQUIRE(lock->is_locked());
     226            1 :         CATCH_REQUIRE(t.is_running());
     227              : 
     228              :         // verify that the delete removes the lock
     229              :         //
     230            1 :         delete lock;
     231              : 
     232              :         for(;;)
     233              :         {
     234            2 :             if(!t.is_running())
     235              :             {
     236            1 :                 break;
     237              :             }
     238            1 :             snapdev::timespec_ex const wait(0.001); // 1ms
     239            1 :             int const r(nanosleep(&wait, nullptr));
     240            1 :             if(r != 0)
     241              :             {
     242            0 :                 int const e(errno);
     243              :                 std::cerr
     244            0 :                     << "error: nanosleep() returned an error ("
     245              :                     << e
     246              :                     << ", "
     247            0 :                     << strerror(e)
     248            0 :                     << ")\n";
     249              :             }
     250            1 :             CATCH_REQUIRE(r == 0);
     251            1 :         }
     252            1 :     }
     253            9 :     CATCH_END_SECTION()
     254              : 
     255            9 :     CATCH_START_SECTION("lockfile: a shared lock does not prevent the thread from finishing")
     256              :     {
     257            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     258            1 :         std::string const filename(path + "/test-4.lock");
     259              : 
     260            1 :         snapdev::lockfile lock(filename, snapdev::operation_t::OPERATION_SHARED);
     261            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     262            1 :         lock.lock();
     263            1 :         CATCH_REQUIRE(lock.is_locked());
     264              : 
     265            1 :         lockfile_thread t(filename, snapdev::operation_t::OPERATION_SHARED);
     266            1 :         t.start_thread();
     267              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     268              : 
     269              :         // still locked
     270              :         //
     271            1 :         CATCH_REQUIRE(lock.is_locked());
     272              : 
     273              :         // sleep a bit to make sure things stay locked
     274              :         //
     275            1 :         sleep(2);
     276            1 :         CATCH_REQUIRE(lock.is_locked());
     277              : 
     278              :         // the thread will die on its own without us having to unlock()
     279              :         //
     280              :         for(;;)
     281              :         {
     282            1 :             if(!t.is_running())
     283              :             {
     284            1 :                 break;
     285              :             }
     286              :             //snapdev::timespec_ex wait(0.001); // 1ms
     287              :             //CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     288            0 :             sleep(1);
     289              :         }
     290              : 
     291              :         // still locked and the thread returned as expected
     292              :         //
     293            1 :         CATCH_REQUIRE(lock.is_locked());
     294            1 :     }
     295            9 :     CATCH_END_SECTION()
     296              : 
     297            9 :     CATCH_START_SECTION("lockfile: test a try_lock() with a thread")
     298              :     {
     299            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     300            1 :         std::string const filename(path + "/test-5.lock");
     301              : 
     302            1 :         snapdev::lockfile lock(filename);
     303            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     304            1 :         lock.lock();
     305            1 :         CATCH_REQUIRE(lock.is_locked());
     306              : 
     307            1 :         lockfile_thread t(filename);
     308            1 :         t.start_thread(2);  // the try_lock will fail
     309              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     310              : 
     311              :         // still locked
     312              :         //
     313            1 :         CATCH_REQUIRE(lock.is_locked());
     314              : 
     315              :         // sleep a bit to make sure things stay locked
     316              :         //
     317            1 :         sleep(2);
     318            1 :         CATCH_REQUIRE(lock.is_locked());
     319              : 
     320              :         for(;;)
     321              :         {
     322            1 :             if(!t.is_running())
     323              :             {
     324            1 :                 break;
     325              :             }
     326            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     327            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     328            0 :         }
     329              : 
     330              :         // still locked and the thread returned as expected
     331              :         //
     332            1 :         CATCH_REQUIRE(lock.is_locked());
     333            1 :     }
     334            9 :     CATCH_END_SECTION()
     335              : 
     336            9 :     CATCH_START_SECTION("lockfile: test a shared lock and try_lock() with a thread")
     337              :     {
     338            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     339            1 :         std::string const filename(path + "/test-6.lock");
     340              : 
     341            1 :         snapdev::lockfile lock(filename, snapdev::operation_t::OPERATION_SHARED);
     342            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     343            1 :         lock.lock();
     344            1 :         CATCH_REQUIRE(lock.is_locked());
     345              : 
     346            1 :         lockfile_thread t(filename, snapdev::operation_t::OPERATION_SHARED);
     347            1 :         t.start_thread(1);  // the try_lock will succeed
     348              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     349              : 
     350              :         // still locked
     351              :         //
     352            1 :         CATCH_REQUIRE(lock.is_locked());
     353              : 
     354              :         // sleep a bit to make sure things stay locked
     355              :         //
     356            1 :         sleep(2);
     357            1 :         CATCH_REQUIRE(lock.is_locked());
     358              : 
     359              :         for(;;)
     360              :         {
     361            1 :             if(!t.is_running())
     362              :             {
     363            1 :                 break;
     364              :             }
     365            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     366            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     367            0 :         }
     368              : 
     369              :         // still locked and the thread returned as expected
     370              :         //
     371            1 :         CATCH_REQUIRE(lock.is_locked());
     372            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_SHARED);
     373            1 :     }
     374            9 :     CATCH_END_SECTION()
     375              : 
     376            9 :     CATCH_START_SECTION("lockfile: test an exclusive lock and a shared try_lock() lock with a thread")
     377              :     {
     378            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     379            1 :         std::string const filename(path + "/test-7.lock");
     380              : 
     381            1 :         snapdev::lockfile lock(filename);
     382            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     383            1 :         lock.lock();
     384            1 :         CATCH_REQUIRE(lock.is_locked());
     385              : 
     386            1 :         lockfile_thread t(filename, snapdev::operation_t::OPERATION_SHARED);
     387            1 :         t.start_thread(2);  // the try_lock will fail anyway
     388              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     389              : 
     390              :         // still locked
     391              :         //
     392            1 :         CATCH_REQUIRE(lock.is_locked());
     393              : 
     394              :         // sleep a bit to make sure things stay locked
     395              :         //
     396            1 :         sleep(2);
     397            1 :         CATCH_REQUIRE(lock.is_locked());
     398              : 
     399              :         for(;;)
     400              :         {
     401            1 :             if(!t.is_running())
     402              :             {
     403            1 :                 break;
     404              :             }
     405            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     406            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     407            0 :         }
     408              : 
     409              :         // still locked and the thread returned as expected
     410              :         //
     411            1 :         CATCH_REQUIRE(lock.is_locked());
     412            1 :     }
     413            9 :     CATCH_END_SECTION()
     414              : 
     415            9 :     CATCH_START_SECTION("lockfile: test a shared lock and an exclusive try_lock() lock with a thread")
     416              :     {
     417            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     418            1 :         std::string const filename(path + "/test-8.lock");
     419              : 
     420            1 :         snapdev::lockfile lock(filename, snapdev::operation_t::OPERATION_SHARED);
     421            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     422            1 :         lock.lock();
     423            1 :         CATCH_REQUIRE(lock.is_locked());
     424              : 
     425            1 :         lockfile_thread t(filename);
     426            1 :         t.start_thread(2);  // the try_lock will fail anyway
     427              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     428              : 
     429              :         // still locked
     430              :         //
     431            1 :         CATCH_REQUIRE(lock.is_locked());
     432              : 
     433              :         // sleep a bit to make sure things stay locked
     434              :         //
     435            1 :         sleep(2);
     436            1 :         CATCH_REQUIRE(lock.is_locked());
     437              : 
     438              :         for(;;)
     439              :         {
     440            1 :             if(!t.is_running())
     441              :             {
     442            1 :                 break;
     443              :             }
     444            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     445            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     446            0 :         }
     447              : 
     448              :         // still locked and the thread returned as expected
     449              :         //
     450            1 :         CATCH_REQUIRE(lock.is_locked());
     451            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_SHARED);
     452            1 :     }
     453            9 :     CATCH_END_SECTION()
     454              : 
     455            9 :     CATCH_START_SECTION("lockfile: test copying a lockfile")
     456              :     {
     457            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     458            1 :         std::string const filename(path + "/test-9.lock");
     459            1 :         snapdev::lockfile lock(filename);
     460            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     461            1 :         lock.lock();
     462            1 :         CATCH_REQUIRE(lock.is_locked());
     463            1 :         lock.lock();
     464            1 :         CATCH_REQUIRE(lock.is_locked());
     465            1 :         lock.unlock();
     466            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     467            1 :         lock.unlock();
     468            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     469              : 
     470              :         {
     471            1 :             snapdev::lockfile copy(lock);
     472              : 
     473            1 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     474            1 :             CATCH_REQUIRE_FALSE(copy.is_locked());
     475            1 :             lock.lock();
     476            1 :             CATCH_REQUIRE(lock.is_locked());
     477            1 :             CATCH_REQUIRE(copy.is_locked());
     478              : 
     479            1 :             CATCH_REQUIRE(copy.get_path() == filename);
     480            1 :             CATCH_REQUIRE(copy.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     481            1 :         }
     482              : 
     483              :         {
     484            1 :             snapdev::lockfile copy(lock);
     485              : 
     486            1 :             CATCH_REQUIRE(lock.is_locked());
     487            1 :             CATCH_REQUIRE(copy.is_locked());
     488            1 :             lock.unlock();
     489            1 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     490            1 :             CATCH_REQUIRE_FALSE(copy.is_locked());
     491              : 
     492            1 :             CATCH_REQUIRE(copy.get_path() == filename);
     493            1 :             CATCH_REQUIRE(copy.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     494            1 :         }
     495              : 
     496            1 :         CATCH_REQUIRE(lock.get_path() == filename);
     497            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     498            1 :     }
     499            9 :     CATCH_END_SECTION()
     500            9 : }
     501              : 
     502              : 
     503            1 : CATCH_TEST_CASE("lockfile_error", "[lock][file]")
     504              : {
     505            1 :     CATCH_START_SECTION("lockfile_error: no path generates an error trying to create the lock file")
     506              :     {
     507            1 :         int const e(ENOENT);
     508            4 :         CATCH_REQUIRE_THROWS_MATCHES(
     509              :                   snapdev::lockfile(std::string())
     510              :                 , snapdev::file_error
     511              :                 , Catch::Matchers::ExceptionMessage(
     512              :                           "lockfile_error: Error creating lock file \"\" (errno: "
     513              :                         + std::to_string(e)
     514              :                         + ", "
     515              :                         + strerror(e)
     516              :                         + ")."));
     517              :     }
     518            1 :     CATCH_END_SECTION()
     519            1 : }
     520              : 
     521              : 
     522            3 : CATCH_TEST_CASE("lockfd", "[lock][file]")
     523              : {
     524            3 :     CATCH_START_SECTION("lockfd: test exclusive lock with -1, nothing happens")
     525              :     {
     526            1 :         snapdev::lockfd lock(-1);
     527            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     528            1 :         CATCH_REQUIRE_FALSE(lock.lock());
     529            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     530            1 :         lock.unlock();
     531            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     532            1 :     }
     533            3 :     CATCH_END_SECTION()
     534              : 
     535            3 :     CATCH_START_SECTION("lockfd: test shared lock with -1, nothing happens")
     536              :     {
     537            1 :         snapdev::lockfd lock(-1, snapdev::operation_t::OPERATION_SHARED);
     538            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     539            1 :         CATCH_REQUIRE_FALSE(lock.lock());
     540            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     541            1 :         lock.unlock();
     542            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     543            1 :     }
     544            3 :     CATCH_END_SECTION()
     545              : 
     546            3 :     CATCH_START_SECTION("lockfd: test shared lock with actual file")
     547              :     {
     548            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     549            1 :         std::string const filename(path + "/fd-test-1.lock");
     550            1 :         int const fd(::open(filename.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH));
     551              :         {
     552            1 :             snapdev::lockfd lock(fd, snapdev::operation_t::OPERATION_SHARED);
     553            1 :             CATCH_REQUIRE(lock.is_locked());
     554            1 :             lock.unlock();
     555            1 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     556            1 :             CATCH_REQUIRE(lock.lock());
     557            1 :             CATCH_REQUIRE(lock.is_locked());
     558            1 :         }
     559            1 :         close(fd);
     560            1 :     }
     561            3 :     CATCH_END_SECTION()
     562            3 : }
     563              : 
     564              : 
     565              : 
     566              : // vim: ts=4 sw=4 et
        

Generated by: LCOV version 2.0-1

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