Line data Source code
1 : // Copyright (c) 2011-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 the unique_number class. 21 : * 22 : * This file implements tests to verify that the unique_number implementation 23 : * works properly even when multiple processes use it simultaneously. 24 : */ 25 : 26 : // file being tested 27 : // 28 : #include <snapdev/unique_number.h> 29 : 30 : 31 : // self 32 : // 33 : #include "catch_main.h" 34 : 35 : 36 : // snapdev 37 : // 38 : #include <snapdev/not_used.h> 39 : 40 : 41 : 42 : // C++ 43 : // 44 : #include <random> 45 : #include <thread> 46 : 47 : 48 : // last include 49 : // 50 : #include <snapdev/poison.h> 51 : 52 : 53 : 54 : // turn off pedantic for __int128 55 : // 56 : #pragma GCC diagnostic ignored "-Wpedantic" 57 : 58 : 59 : namespace 60 : { 61 : 62 : 63 : class lockfile_thread 64 : { 65 : public: 66 : lockfile_thread( 67 : std::string const & filename 68 : , snapdev::operation_t operation = snapdev::operation_t::OPERATION_EXCLUSIVE) 69 : : f_filename(filename) 70 : , f_operation(operation) 71 : { 72 : } 73 : 74 : lockfile_thread(lockfile_thread const &) = delete; 75 : lockfile_thread & operator = (lockfile_thread const &) = delete; 76 : 77 : ~lockfile_thread() 78 : { 79 : if(f_thread != nullptr) 80 : { 81 : f_thread->join(); 82 : delete f_thread; 83 : } 84 : } 85 : 86 : void start_thread(int try_lock = 0) 87 : { 88 : f_running = true; 89 : if(try_lock) 90 : { 91 : f_thread = new std::thread(&lockfile_thread::run_try_lock, this, try_lock); 92 : } 93 : else 94 : { 95 : f_thread = new std::thread(&lockfile_thread::run, this); 96 : } 97 : } 98 : 99 : void run() 100 : { 101 : snapdev::lockfile lock(f_filename, f_operation); 102 : CATCH_REQUIRE_FALSE(lock.is_locked()); 103 : lock.lock(); 104 : CATCH_REQUIRE(lock.is_locked()); 105 : 106 : std::lock_guard<std::mutex> guard(f_mutex); 107 : f_running = false; 108 : } 109 : 110 : void run_try_lock(int try_lock) 111 : { 112 : snapdev::lockfile lock(f_filename, f_operation); 113 : CATCH_REQUIRE_FALSE(lock.is_locked()); 114 : lock.try_lock(); 115 : if(try_lock == 1) 116 : { 117 : CATCH_REQUIRE(lock.is_locked()); 118 : } 119 : else 120 : { 121 : CATCH_REQUIRE_FALSE(lock.is_locked()); 122 : } 123 : 124 : std::lock_guard<std::mutex> guard(f_mutex); 125 : f_running = false; 126 : } 127 : 128 : bool is_running() const 129 : { 130 : std::lock_guard<std::mutex> guard(f_mutex); 131 : return f_running; 132 : } 133 : 134 : private: 135 : mutable std::mutex f_mutex = std::mutex(); 136 : std::string f_filename = std::string(); 137 : snapdev::operation_t f_operation = snapdev::operation_t::OPERATION_EXCLUSIVE; 138 : std::thread * f_thread = nullptr; 139 : bool f_running = false; 140 : }; 141 : 142 : 143 : } // no name namespace 144 : 145 : 146 : 147 2 : CATCH_TEST_CASE("unique_number", "[file]") 148 : { 149 2 : CATCH_START_SECTION("unique_number: verify unique number basic counter") 150 : { 151 1 : std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir()); 152 1 : std::string const filename(path + "/test-1.number"); 153 : 154 1 : snapdev::NOT_USED(unlink(filename.c_str())); 155 : 156 101 : for(unsigned int n(1); n <= 100; ++n) 157 : { 158 100 : unsigned __int128 const number(snapdev::unique_number(filename)); 159 100 : CATCH_REQUIRE(n == number); 160 : } 161 1 : } 162 2 : CATCH_END_SECTION() 163 : 164 2 : CATCH_START_SECTION("unique_number: verify unique number indexes") 165 : { 166 1 : std::random_device rd; 167 1 : std::mt19937 g(rd()); 168 : 169 1 : std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir()); 170 1 : std::string const filename(path + "/test-2.number"); 171 1 : std::cerr << "--- file: " << filename << "\n"; 172 : 173 1 : snapdev::NOT_USED(unlink(filename.c_str())); 174 : 175 : // 100 indexes repeated 100 times 176 : // 177 : // that way we can shuffle all the indexes in order to count them 178 : // in different order 179 : // 180 1 : std::vector<unsigned int> indexes; 181 101 : for(unsigned int count(1); count <= 100; ++count) 182 : { 183 10100 : for(unsigned int idx(0); idx < 100; ++idx) 184 : { 185 10000 : indexes.push_back(idx); 186 : } 187 : } 188 1 : std::shuffle(indexes.begin(), indexes.end(), g); 189 : 190 2 : std::vector<unsigned int> counters(100); 191 10001 : for(auto idx : indexes) 192 : { 193 10000 : unsigned __int128 const number(snapdev::unique_number(filename, idx)); 194 10000 : ++counters[idx]; 195 10000 : CATCH_REQUIRE(counters[idx] == number); 196 : } 197 1 : } 198 2 : CATCH_END_SECTION() 199 2 : } 200 : 201 : 202 3 : CATCH_TEST_CASE("unique_number_error", "[file][error]") 203 : { 204 3 : CATCH_START_SECTION("unique_number_error: non-empty filename is required") 205 : { 206 3 : CATCH_REQUIRE_THROWS_MATCHES( 207 : snapdev::unique_number(std::string()) 208 : , snapdev::path_missing 209 : , Catch::Matchers::ExceptionMessage( 210 : "unique_number_error: a counter filename must be specified when calling snapdev::unique_number.")); 211 : } 212 3 : CATCH_END_SECTION() 213 : 214 3 : CATCH_START_SECTION("unique_number_error: index out of range") 215 : { 216 1 : std::string const path(SNAP_CATCH2_NAMESPACE::g_tmp_dir()); 217 1 : std::string const filename(path + "/test-3.number"); 218 : 219 101 : for(int index(-100); index < 0; ++index) 220 : { 221 100 : CATCH_REQUIRE_THROWS_MATCHES( 222 : snapdev::unique_number(filename, index) 223 : , snapdev::out_of_range 224 : , Catch::Matchers::ExceptionMessage( 225 : "unique_number_error: counter index in unique_number must be defined between 0 and " 226 : + std::to_string(snapdev::COUNTER_MAXIMUM_INDEX - 1) 227 : + " inclusive.")); 228 : } 229 : 230 101 : for(int index(snapdev::COUNTER_MAXIMUM_INDEX); index < snapdev::COUNTER_MAXIMUM_INDEX + 100; ++index) 231 : { 232 100 : CATCH_REQUIRE_THROWS_MATCHES( 233 : snapdev::unique_number(filename, index) 234 : , snapdev::out_of_range 235 : , Catch::Matchers::ExceptionMessage( 236 : "unique_number_error: counter index in unique_number must be defined between 0 and " 237 : + std::to_string(snapdev::COUNTER_MAXIMUM_INDEX - 1) 238 : + " inclusive.")); 239 : } 240 1 : } 241 3 : CATCH_END_SECTION() 242 : 243 3 : CATCH_START_SECTION("unique_number_error: file cannot be opened") 244 : { 245 5 : CATCH_REQUIRE_THROWS_MATCHES( 246 : snapdev::unique_number("/this/wont/open/since/it/does/not/exist") 247 : , snapdev::io_error 248 : , Catch::Matchers::ExceptionMessage( 249 : "unique_number_error: could not open unique_number file \"/this/wont/open/since/it/does/not/exist\".")); 250 : } 251 3 : CATCH_END_SECTION() 252 3 : } 253 : 254 : 255 : 256 : // vim: ts=4 sw=4 et