1 #include "gmock/gmock.h" 2 3 #include "config.h" 4 5 #include <cstdio> 6 #include <fcntl.h> 7 8 #include <memory> 9 #include <condition_variable> 10 #include <thread> 11 #include <mutex> 12 #include <chrono> 13 14 #include "log.h" 15 16 #include "watchtower.h" 17 18 #ifdef _WIN32 19 # include "winwatchtowerdriver.h" 20 using PlatformWatchTower = WatchTower<WinWatchTowerDriver>; 21 #else 22 # include "inotifywatchtowerdriver.h" 23 using PlatformWatchTower = WatchTower<INotifyWatchTowerDriver>; 24 #endif 25 26 using namespace std; 27 using namespace testing; 28 29 class WatchTowerBehaviour: public testing::Test { 30 public: 31 shared_ptr<PlatformWatchTower> watch_tower = make_shared<PlatformWatchTower>(); 32 33 const char* createTempName() 34 { 35 const char* name; 36 #if _WIN32 37 name = _tempnam( "c:\\temp", "glogg_test" ); 38 #else 39 name = tmpnam( nullptr ); 40 #endif 41 return name; 42 } 43 44 string createTempEmptyFile( string file_name = "" ) { 45 const char* name; 46 47 if ( ! file_name.empty() ) { 48 name = file_name.c_str(); 49 } 50 else { 51 // I know tmpnam is bad but I need control over the file 52 // and it is the only one which exits on Windows. 53 name = createTempName(); 54 } 55 int fd = creat( name, S_IRUSR | S_IWUSR ); 56 close( fd ); 57 58 return string( name ); 59 } 60 61 string getNonExistingFileName() { 62 #if _WIN32 63 return string( _tempnam( "c:\\temp", "inexistant" ) ); 64 #else 65 return string( tmpnam( nullptr ) ); 66 #endif 67 } 68 69 WatchTowerBehaviour() { 70 // Default to quiet, but increase to debug 71 FILELog::setReportingLevel( logERROR ); 72 } 73 }; 74 75 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) { 76 auto file_name = createTempEmptyFile(); 77 auto registration = watch_tower->addFile( file_name, [] (void) { } ); 78 } 79 80 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) { 81 auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } ); 82 } 83 84 /*****/ 85 86 class WatchTowerSingleFile: public WatchTowerBehaviour { 87 public: 88 static const int TIMEOUT; 89 90 mutex mutex_; 91 condition_variable cv_; 92 93 string file_name; 94 Registration registration; 95 96 int notification_received = 0; 97 98 Registration registerFile( const string& filename ) { 99 weak_ptr<void> weakHeartbeat( heartbeat_ ); 100 101 auto reg = watch_tower->addFile( filename, [this, weakHeartbeat] (void) { 102 // Ensure the fixture object is still alive using the heartbeat 103 if ( auto keep = weakHeartbeat.lock() ) { 104 unique_lock<mutex> lock(mutex_); 105 ++notification_received; 106 cv_.notify_one(); 107 } } ); 108 109 return reg; 110 } 111 112 WatchTowerSingleFile() 113 : heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) ) 114 { 115 file_name = createTempEmptyFile(); 116 registration = registerFile( file_name ); 117 } 118 119 ~WatchTowerSingleFile() { 120 remove( file_name.c_str() ); 121 } 122 123 bool waitNotificationReceived( int number = 1 ) { 124 unique_lock<mutex> lock(mutex_); 125 bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT), 126 [this, number] { return notification_received >= number; } ) ); 127 128 // Reinit the notification 129 notification_received = 0; 130 131 return result; 132 } 133 134 void appendDataToFile( const string& file_name ) { 135 static const char* string = "Test line\n"; 136 int fd = open( file_name.c_str(), O_WRONLY | O_APPEND ); 137 write( fd, (void*) string, strlen( string ) ); 138 close( fd ); 139 } 140 141 private: 142 // Heartbeat ensures the object is still alive 143 shared_ptr<void> heartbeat_; 144 }; 145 146 #ifdef _WIN32 147 const int WatchTowerSingleFile::TIMEOUT = 2000; 148 #else 149 const int WatchTowerSingleFile::TIMEOUT = 20; 150 #endif 151 152 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) { 153 appendDataToFile( file_name ); 154 ASSERT_TRUE( waitNotificationReceived() ); 155 } 156 157 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { 158 remove( file_name.c_str() ); 159 ASSERT_TRUE( waitNotificationReceived() ); 160 } 161 162 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { 163 remove( file_name.c_str() ); 164 waitNotificationReceived(); 165 createTempEmptyFile( file_name ); 166 ASSERT_TRUE( waitNotificationReceived() ); 167 } 168 169 TEST_F( WatchTowerSingleFile, SignalsWhenAReappearedFileIsAppended ) { 170 remove( file_name.c_str() ); 171 waitNotificationReceived(); 172 createTempEmptyFile( file_name ); 173 waitNotificationReceived(); 174 175 appendDataToFile( file_name ); 176 ASSERT_TRUE( waitNotificationReceived() ); 177 } 178 179 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) { 180 auto second_file_name = createTempEmptyFile(); 181 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); 182 // Ensure file creation has been 'digested' 183 { 184 auto second_registration = registerFile( second_file_name ); 185 appendDataToFile( second_file_name ); 186 ASSERT_TRUE( waitNotificationReceived() ); 187 } 188 189 // The registration will be removed here. 190 appendDataToFile( second_file_name ); 191 ASSERT_FALSE( waitNotificationReceived() ); 192 193 remove( second_file_name.c_str() ); 194 } 195 196 TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) { 197 auto second_file_name = createTempEmptyFile(); 198 199 for ( int i = 0; i < 100; i++ ) 200 { 201 auto second_registration = registerFile( second_file_name ); 202 appendDataToFile( second_file_name ); 203 ASSERT_TRUE( waitNotificationReceived() ); 204 } 205 206 // The registration will be removed here. 207 appendDataToFile( second_file_name ); 208 ASSERT_FALSE( waitNotificationReceived() ); 209 210 remove( second_file_name.c_str() ); 211 } 212 213 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) { 214 auto second_registration = registerFile( file_name ); 215 appendDataToFile( file_name ); 216 217 ASSERT_TRUE( waitNotificationReceived( 2 ) ); 218 } 219 220 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) { 221 { 222 auto second_registration = registerFile( file_name ); 223 } 224 225 appendDataToFile( file_name ); 226 ASSERT_TRUE( waitNotificationReceived( 1 ) ); 227 } 228 229 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) { 230 auto new_file_name = createTempName(); 231 232 rename( file_name.c_str(), new_file_name ); 233 ASSERT_TRUE( waitNotificationReceived() ); 234 235 rename( new_file_name, file_name.c_str() ); 236 } 237 238 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) { 239 remove( file_name.c_str() ); 240 waitNotificationReceived(); 241 242 std::string new_file_name = createTempEmptyFile(); 243 appendDataToFile( new_file_name ); 244 245 rename( new_file_name.c_str(), file_name.c_str() ); 246 ASSERT_TRUE( waitNotificationReceived() ); 247 } 248 249 /*****/ 250 251 #ifdef HAVE_SYMLINK 252 class WatchTowerSymlink: public WatchTowerSingleFile { 253 public: 254 string symlink_name; 255 256 void SetUp() override { 257 file_name = createTempEmptyFile(); 258 symlink_name = createTempEmptyFile(); 259 remove( symlink_name.c_str() ); 260 symlink( file_name.c_str(), symlink_name.c_str() ); 261 262 registration = registerFile( symlink_name ); 263 } 264 265 void TearDown() override { 266 remove( symlink_name.c_str() ); 267 remove( file_name.c_str() ); 268 } 269 }; 270 271 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { 272 appendDataToFile( symlink_name ); 273 ASSERT_TRUE( waitNotificationReceived() ); 274 } 275 276 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { 277 appendDataToFile( file_name ); 278 ASSERT_TRUE( waitNotificationReceived() ); 279 } 280 281 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { 282 remove( symlink_name.c_str() ); 283 ASSERT_TRUE( waitNotificationReceived() ); 284 } 285 286 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { 287 remove( file_name.c_str() ); 288 ASSERT_TRUE( waitNotificationReceived() ); 289 } 290 291 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { 292 auto new_target = createTempEmptyFile(); 293 remove( symlink_name.c_str() ); 294 waitNotificationReceived(); 295 296 symlink( new_target.c_str(), symlink_name.c_str() ); 297 ASSERT_TRUE( waitNotificationReceived() ); 298 299 remove( new_target.c_str() ); 300 } 301 302 TEST_F( WatchTowerSymlink, DataAddedInAReappearingSymlinkYieldsANotification ) { 303 auto new_target = createTempEmptyFile(); 304 remove( symlink_name.c_str() ); 305 waitNotificationReceived(); 306 symlink( new_target.c_str(), symlink_name.c_str() ); 307 waitNotificationReceived(); 308 309 appendDataToFile( new_target ); 310 ASSERT_TRUE( waitNotificationReceived() ); 311 312 remove( new_target.c_str() ); 313 } 314 #endif //HAVE_SYMLINK 315 316 /*****/ 317 318 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { 319 auto mortal_watch_tower = new PlatformWatchTower(); 320 auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } ); 321 322 delete mortal_watch_tower; 323 // reg will be destroyed after the watch_tower 324 } 325 326 /*****/ 327 328 class WatchTowerDirectories: public WatchTowerSingleFile { 329 public: 330 string second_dir_name; 331 string second_file_name; 332 string third_file_name; 333 334 Registration registration_two; 335 Registration registration_three; 336 337 WatchTowerDirectories() { 338 second_dir_name = createTempDir(); 339 second_file_name = createTempEmptyFileInDir( second_dir_name ); 340 third_file_name = createTempEmptyFileInDir( second_dir_name ); 341 } 342 343 ~WatchTowerDirectories() { 344 remove( third_file_name.c_str() ); 345 remove( second_file_name.c_str() ); 346 347 removeDir( second_dir_name ); 348 } 349 350 string createTempDir() { 351 #ifdef _WIN32 352 static int counter = 1; 353 char temp_dir[255]; 354 355 GetTempPath( sizeof temp_dir, temp_dir ); 356 357 string dir_name = string { temp_dir } + string { "\\test" } + to_string( counter++ ); 358 mkdir( dir_name.c_str() ); 359 return dir_name; 360 #else 361 char dir_template[] = "/tmp/XXXXXX"; 362 return { mkdtemp( dir_template ) }; 363 #endif 364 } 365 366 string createTempEmptyFileInDir( const string& dir ) { 367 static int counter = 1; 368 return createTempEmptyFile( dir + std::string { "/temp" } + to_string( counter++ ) ); 369 } 370 371 void removeDir( const string& name ) { 372 rmdir( name.c_str() ); 373 } 374 }; 375 376 TEST_F( WatchTowerDirectories, FollowThreeFilesInTwoDirs ) { 377 registration_two = registerFile( second_file_name ); 378 registration_three = registerFile( third_file_name ); 379 380 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); 381 } 382 383 TEST_F( WatchTowerDirectories, FollowTwoFilesInTwoDirs ) { 384 registration_two = registerFile( second_file_name ); 385 { 386 auto temp_registration_three = registerFile( third_file_name ); 387 } 388 389 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); 390 } 391 392 TEST_F( WatchTowerDirectories, FollowOneFileInOneDir ) { 393 { 394 auto temp_registration_two = registerFile( second_file_name ); 395 auto temp_registration_three = registerFile( third_file_name ); 396 397 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); 398 } 399 400 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 1 ) ); 401 } 402 403 /*****/ 404 405 #ifdef _WIN32 406 class WinNotificationInfoListTest : public testing::Test { 407 public: 408 using Action = WinNotificationInfo::Action; 409 410 struct Buffer { 411 uint32_t next_addr; 412 uint32_t action; 413 uint32_t filename_length; 414 wchar_t filename[13]; 415 }; 416 static struct Buffer buffer[2]; 417 418 WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) }; 419 WinNotificationInfoList::iterator iterator { std::begin( list ) }; 420 }; 421 422 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = 423 { { 40, 1, 26, L"Filename.txt" }, 424 { 0, 3, 18, L"file2.txt" } }; 425 426 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { 427 auto notification = *iterator; 428 ASSERT_THAT( ¬ification, NotNull() ); 429 } 430 431 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { 432 ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); 433 } 434 435 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { 436 ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); 437 } 438 439 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { 440 ++iterator; 441 auto notification = *iterator; 442 ASSERT_THAT( ¬ification, NotNull() ); 443 } 444 445 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { 446 iterator++; 447 ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); 448 ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); 449 } 450 451 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { 452 for ( auto notification : list ) { 453 notification.action(); 454 } 455 } 456 #endif 457