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 class WatchTowerInexistantDirectory: public WatchTowerDirectories { 406 public: 407 WatchTowerInexistantDirectory() { 408 test_dir = createTempDir(); 409 rmdir( test_dir.c_str() ); 410 } 411 412 ~WatchTowerInexistantDirectory() { 413 rmdir( test_dir.c_str() ); 414 } 415 416 string test_dir; 417 }; 418 419 TEST_F( WatchTowerInexistantDirectory, LaterCreatedDirIsFollowed ) { 420 /* Dir (and file) don't exist */ 421 auto file_name = createTempEmptyFileInDir( test_dir ); 422 { 423 auto registration = registerFile( file_name ); 424 425 #ifdef _WIN32 426 mkdir( test_dir.c_str() ); 427 #else 428 mkdir( test_dir.c_str(), 0777 ); 429 #endif 430 createTempEmptyFile( file_name ); 431 } 432 433 auto registration2 = registerFile( file_name ); 434 435 appendDataToFile( file_name ); 436 ASSERT_TRUE( waitNotificationReceived() ); 437 438 remove( file_name.c_str() ); 439 } 440 441 /*****/ 442 443 #ifdef _WIN32 444 class WinNotificationInfoListTest : public testing::Test { 445 public: 446 using Action = WinNotificationInfo::Action; 447 448 struct Buffer { 449 uint32_t next_addr; 450 uint32_t action; 451 uint32_t filename_length; 452 wchar_t filename[13]; 453 }; 454 static struct Buffer buffer[2]; 455 456 WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) }; 457 WinNotificationInfoList::iterator iterator { std::begin( list ) }; 458 }; 459 460 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = 461 { { 40, 1, 26, L"Filename.txt" }, 462 { 0, 3, 18, L"file2.txt" } }; 463 464 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { 465 auto notification = *iterator; 466 ASSERT_THAT( ¬ification, NotNull() ); 467 } 468 469 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { 470 ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); 471 } 472 473 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { 474 ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); 475 } 476 477 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { 478 ++iterator; 479 auto notification = *iterator; 480 ASSERT_THAT( ¬ification, NotNull() ); 481 } 482 483 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { 484 iterator++; 485 ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); 486 ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); 487 } 488 489 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { 490 for ( auto notification : list ) { 491 notification.action(); 492 } 493 } 494 #endif 495