LCOV - code coverage report
Current view: top level - tests - catch_lockfile.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.4 % 279 269
Test Date: 2025-07-03 19:05:49 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           16 :     bool is_running() const
     121              :     {
     122           16 :         std::lock_guard<std::mutex> guard(f_mutex);
     123           16 :         return f_running;
     124           16 :     }
     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 wait(0.001); // 1ms
     239            1 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     240            1 :         }
     241            1 :     }
     242            9 :     CATCH_END_SECTION()
     243              : 
     244            9 :     CATCH_START_SECTION("lockfile: a shared lock does not prevent the thread from finishing")
     245              :     {
     246            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     247            1 :         std::string const filename(path + "/test-4.lock");
     248              : 
     249            1 :         snapdev::lockfile lock(filename, snapdev::operation_t::OPERATION_SHARED);
     250            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     251            1 :         lock.lock();
     252            1 :         CATCH_REQUIRE(lock.is_locked());
     253              : 
     254            1 :         lockfile_thread t(filename, snapdev::operation_t::OPERATION_SHARED);
     255            1 :         t.start_thread();
     256              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     257              : 
     258              :         // still locked
     259              :         //
     260            1 :         CATCH_REQUIRE(lock.is_locked());
     261              : 
     262              :         // sleep a bit to make sure things stay locked
     263              :         //
     264            1 :         sleep(2);
     265            1 :         CATCH_REQUIRE(lock.is_locked());
     266              : 
     267              :         // the thread will die on its own without us having to unlock()
     268              :         //
     269              :         for(;;)
     270              :         {
     271            1 :             if(!t.is_running())
     272              :             {
     273            1 :                 break;
     274              :             }
     275              :             //snapdev::timespec_ex wait(0.001); // 1ms
     276              :             //CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     277            0 :             sleep(1);
     278              :         }
     279              : 
     280              :         // still locked and the thread returned as expected
     281              :         //
     282            1 :         CATCH_REQUIRE(lock.is_locked());
     283            1 :     }
     284            9 :     CATCH_END_SECTION()
     285              : 
     286            9 :     CATCH_START_SECTION("lockfile: test a try_lock() with a thread")
     287              :     {
     288            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     289            1 :         std::string const filename(path + "/test-5.lock");
     290              : 
     291            1 :         snapdev::lockfile lock(filename);
     292            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     293            1 :         lock.lock();
     294            1 :         CATCH_REQUIRE(lock.is_locked());
     295              : 
     296            1 :         lockfile_thread t(filename);
     297            1 :         t.start_thread(2);  // the try_lock will fail
     298              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     299              : 
     300              :         // still locked
     301              :         //
     302            1 :         CATCH_REQUIRE(lock.is_locked());
     303              : 
     304              :         // sleep a bit to make sure things stay locked
     305              :         //
     306            1 :         sleep(2);
     307            1 :         CATCH_REQUIRE(lock.is_locked());
     308              : 
     309              :         for(;;)
     310              :         {
     311            1 :             if(!t.is_running())
     312              :             {
     313            1 :                 break;
     314              :             }
     315            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     316            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     317            0 :         }
     318              : 
     319              :         // still locked and the thread returned as expected
     320              :         //
     321            1 :         CATCH_REQUIRE(lock.is_locked());
     322            1 :     }
     323            9 :     CATCH_END_SECTION()
     324              : 
     325            9 :     CATCH_START_SECTION("lockfile: test a shared lock and try_lock() with a thread")
     326              :     {
     327            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     328            1 :         std::string const filename(path + "/test-6.lock");
     329              : 
     330            1 :         snapdev::lockfile lock(filename, snapdev::operation_t::OPERATION_SHARED);
     331            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     332            1 :         lock.lock();
     333            1 :         CATCH_REQUIRE(lock.is_locked());
     334              : 
     335            1 :         lockfile_thread t(filename, snapdev::operation_t::OPERATION_SHARED);
     336            1 :         t.start_thread(1);  // the try_lock will succeed
     337              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     338              : 
     339              :         // still locked
     340              :         //
     341            1 :         CATCH_REQUIRE(lock.is_locked());
     342              : 
     343              :         // sleep a bit to make sure things stay locked
     344              :         //
     345            1 :         sleep(2);
     346            1 :         CATCH_REQUIRE(lock.is_locked());
     347              : 
     348              :         for(;;)
     349              :         {
     350            1 :             if(!t.is_running())
     351              :             {
     352            1 :                 break;
     353              :             }
     354            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     355            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     356            0 :         }
     357              : 
     358              :         // still locked and the thread returned as expected
     359              :         //
     360            1 :         CATCH_REQUIRE(lock.is_locked());
     361            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_SHARED);
     362            1 :     }
     363            9 :     CATCH_END_SECTION()
     364              : 
     365            9 :     CATCH_START_SECTION("lockfile: test an exclusive lock and a shared try_lock() lock with a thread")
     366              :     {
     367            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     368            1 :         std::string const filename(path + "/test-7.lock");
     369              : 
     370            1 :         snapdev::lockfile lock(filename);
     371            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     372            1 :         lock.lock();
     373            1 :         CATCH_REQUIRE(lock.is_locked());
     374              : 
     375            1 :         lockfile_thread t(filename, snapdev::operation_t::OPERATION_SHARED);
     376            1 :         t.start_thread(2);  // the try_lock will fail anyway
     377              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     378              : 
     379              :         // still locked
     380              :         //
     381            1 :         CATCH_REQUIRE(lock.is_locked());
     382              : 
     383              :         // sleep a bit to make sure things stay locked
     384              :         //
     385            1 :         sleep(2);
     386            1 :         CATCH_REQUIRE(lock.is_locked());
     387              : 
     388              :         for(;;)
     389              :         {
     390            1 :             if(!t.is_running())
     391              :             {
     392            1 :                 break;
     393              :             }
     394            0 :             snapdev::timespec_ex wait(0.001); // 1ms
     395            0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     396            0 :         }
     397              : 
     398              :         // still locked and the thread returned as expected
     399              :         //
     400            1 :         CATCH_REQUIRE(lock.is_locked());
     401            1 :     }
     402            9 :     CATCH_END_SECTION()
     403              : 
     404            9 :     CATCH_START_SECTION("lockfile: test a shared lock and an exclusive try_lock() lock with a thread")
     405              :     {
     406            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     407            1 :         std::string const filename(path + "/test-8.lock");
     408              : 
     409            1 :         snapdev::lockfile lock(filename, snapdev::operation_t::OPERATION_SHARED);
     410            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     411            1 :         lock.lock();
     412            1 :         CATCH_REQUIRE(lock.is_locked());
     413              : 
     414            1 :         lockfile_thread t(filename);
     415            1 :         t.start_thread(2);  // the try_lock will fail anyway
     416              :         //CATCH_REQUIRE(t.is_running()); -- the thread may return at any time, so we cannot check whether it is running
     417              : 
     418              :         // still locked
     419              :         //
     420            1 :         CATCH_REQUIRE(lock.is_locked());
     421              : 
     422              :         // sleep a bit to make sure things stay locked
     423              :         //
     424            1 :         sleep(2);
     425            1 :         CATCH_REQUIRE(lock.is_locked());
     426              : 
     427              :         for(;;)
     428              :         {
     429            2 :             if(!t.is_running())
     430              :             {
     431            1 :                 break;
     432              :             }
     433            1 :             snapdev::timespec_ex wait(0.001); // 1ms
     434            1 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     435            1 :         }
     436              : 
     437              :         // still locked and the thread returned as expected
     438              :         //
     439            1 :         CATCH_REQUIRE(lock.is_locked());
     440            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_SHARED);
     441            1 :     }
     442            9 :     CATCH_END_SECTION()
     443              : 
     444            9 :     CATCH_START_SECTION("lockfile: test copying a lockfile")
     445              :     {
     446            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     447            1 :         std::string const filename(path + "/test-9.lock");
     448            1 :         snapdev::lockfile lock(filename);
     449            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     450            1 :         lock.lock();
     451            1 :         CATCH_REQUIRE(lock.is_locked());
     452            1 :         lock.lock();
     453            1 :         CATCH_REQUIRE(lock.is_locked());
     454            1 :         lock.unlock();
     455            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     456            1 :         lock.unlock();
     457            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     458              : 
     459              :         {
     460            1 :             snapdev::lockfile copy(lock);
     461              : 
     462            1 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     463            1 :             CATCH_REQUIRE_FALSE(copy.is_locked());
     464            1 :             lock.lock();
     465            1 :             CATCH_REQUIRE(lock.is_locked());
     466            1 :             CATCH_REQUIRE(copy.is_locked());
     467              : 
     468            1 :             CATCH_REQUIRE(copy.get_path() == filename);
     469            1 :             CATCH_REQUIRE(copy.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     470            1 :         }
     471              : 
     472              :         {
     473            1 :             snapdev::lockfile copy(lock);
     474              : 
     475            1 :             CATCH_REQUIRE(lock.is_locked());
     476            1 :             CATCH_REQUIRE(copy.is_locked());
     477            1 :             lock.unlock();
     478            1 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     479            1 :             CATCH_REQUIRE_FALSE(copy.is_locked());
     480              : 
     481            1 :             CATCH_REQUIRE(copy.get_path() == filename);
     482            1 :             CATCH_REQUIRE(copy.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     483            1 :         }
     484              : 
     485            1 :         CATCH_REQUIRE(lock.get_path() == filename);
     486            1 :         CATCH_REQUIRE(lock.get_operation() == snapdev::operation_t::OPERATION_EXCLUSIVE);
     487            1 :     }
     488            9 :     CATCH_END_SECTION()
     489            9 : }
     490              : 
     491              : 
     492            1 : CATCH_TEST_CASE("lockfile_error", "[lock][file]")
     493              : {
     494            1 :     CATCH_START_SECTION("lockfile_error: no path generates an error trying to create the lock file")
     495              :     {
     496            1 :         int const e(ENOENT);
     497            4 :         CATCH_REQUIRE_THROWS_MATCHES(
     498              :                   snapdev::lockfile(std::string())
     499              :                 , snapdev::file_error
     500              :                 , Catch::Matchers::ExceptionMessage(
     501              :                           "lockfile_error: Error creating lock file \"\" (errno: "
     502              :                         + std::to_string(e)
     503              :                         + ", "
     504              :                         + strerror(e)
     505              :                         + ")."));
     506              :     }
     507            1 :     CATCH_END_SECTION()
     508            1 : }
     509              : 
     510              : 
     511            3 : CATCH_TEST_CASE("lockfd", "[lock][file]")
     512              : {
     513            3 :     CATCH_START_SECTION("lockfd: test exclusive lock with -1, nothing happens")
     514              :     {
     515            1 :         snapdev::lockfd lock(-1);
     516            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     517            1 :         CATCH_REQUIRE_FALSE(lock.lock());
     518            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     519            1 :         lock.unlock();
     520            1 :         CATCH_REQUIRE_FALSE(lock.is_locked());
     521            1 :     }
     522            3 :     CATCH_END_SECTION()
     523              : 
     524            3 :     CATCH_START_SECTION("lockfd: test shared lock with -1, nothing happens")
     525              :     {
     526            1 :         snapdev::lockfd lock(-1, snapdev::operation_t::OPERATION_SHARED);
     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 actual file")
     536              :     {
     537            1 :         std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir());
     538            1 :         std::string const filename(path + "/fd-test-1.lock");
     539            1 :         int const fd(::open(filename.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH));
     540              :         {
     541            1 :             snapdev::lockfd lock(fd, snapdev::operation_t::OPERATION_SHARED);
     542            1 :             CATCH_REQUIRE(lock.is_locked());
     543            1 :             lock.unlock();
     544            1 :             CATCH_REQUIRE_FALSE(lock.is_locked());
     545            1 :             CATCH_REQUIRE(lock.lock());
     546            1 :             CATCH_REQUIRE(lock.is_locked());
     547            1 :         }
     548            1 :         close(fd);
     549            1 :     }
     550            3 :     CATCH_END_SECTION()
     551            3 : }
     552              : 
     553              : 
     554              : 
     555              : // vim: ts=4 sw=4 et
        

Generated by: LCOV version 2.0-1

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