LCOV - code coverage report
Current view: top level - tests - catch_lockfile.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 266 279 95.3 %
Date: 2024-01-13 16:46:58 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2018-2024  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 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           1 :             if(!t.is_running())
     430             :             {
     431           1 :                 break;
     432             :             }
     433           0 :             snapdev::timespec_ex wait(0.001); // 1ms
     434           0 :             CATCH_REQUIRE(nanosleep(&wait, nullptr) == 0);
     435           0 :         }
     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           3 :         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 1.14

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