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, StopSignalingWhenWatchDeleted ) { 170 auto second_file_name = createTempEmptyFile(); 171 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); 172 // Ensure file creation has been 'digested' 173 { 174 auto second_registration = registerFile( second_file_name ); 175 appendDataToFile( second_file_name ); 176 ASSERT_TRUE( waitNotificationReceived() ); 177 } 178 179 // The registration will be removed here. 180 appendDataToFile( second_file_name ); 181 ASSERT_FALSE( waitNotificationReceived() ); 182 183 remove( second_file_name.c_str() ); 184 } 185 186 TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) { 187 auto second_file_name = createTempEmptyFile(); 188 189 for ( int i = 0; i < 100; i++ ) 190 { 191 auto second_registration = registerFile( second_file_name ); 192 appendDataToFile( second_file_name ); 193 ASSERT_TRUE( waitNotificationReceived() ); 194 } 195 196 // The registration will be removed here. 197 appendDataToFile( second_file_name ); 198 ASSERT_FALSE( waitNotificationReceived() ); 199 200 remove( second_file_name.c_str() ); 201 } 202 203 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) { 204 auto second_registration = registerFile( file_name ); 205 appendDataToFile( file_name ); 206 207 ASSERT_TRUE( waitNotificationReceived( 2 ) ); 208 } 209 210 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) { 211 { 212 auto second_registration = registerFile( file_name ); 213 } 214 215 appendDataToFile( file_name ); 216 ASSERT_TRUE( waitNotificationReceived( 1 ) ); 217 } 218 219 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) { 220 auto new_file_name = createTempName(); 221 222 rename( file_name.c_str(), new_file_name ); 223 ASSERT_TRUE( waitNotificationReceived() ); 224 225 rename( new_file_name, file_name.c_str() ); 226 } 227 228 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) { 229 remove( file_name.c_str() ); 230 waitNotificationReceived(); 231 232 std::string new_file_name = createTempEmptyFile(); 233 appendDataToFile( new_file_name ); 234 235 rename( new_file_name.c_str(), file_name.c_str() ); 236 ASSERT_TRUE( waitNotificationReceived() ); 237 } 238 239 /*****/ 240 241 #ifdef HAVE_SYMLINK 242 class WatchTowerSymlink: public WatchTowerSingleFile { 243 public: 244 string symlink_name; 245 246 void SetUp() override { 247 file_name = createTempEmptyFile(); 248 symlink_name = createTempEmptyFile(); 249 remove( symlink_name.c_str() ); 250 symlink( file_name.c_str(), symlink_name.c_str() ); 251 252 registration = registerFile( symlink_name ); 253 } 254 255 void TearDown() override { 256 remove( symlink_name.c_str() ); 257 remove( file_name.c_str() ); 258 } 259 }; 260 261 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { 262 appendDataToFile( symlink_name ); 263 ASSERT_TRUE( waitNotificationReceived() ); 264 } 265 266 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { 267 appendDataToFile( file_name ); 268 ASSERT_TRUE( waitNotificationReceived() ); 269 } 270 271 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { 272 remove( symlink_name.c_str() ); 273 ASSERT_TRUE( waitNotificationReceived() ); 274 } 275 276 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { 277 remove( file_name.c_str() ); 278 ASSERT_TRUE( waitNotificationReceived() ); 279 } 280 281 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { 282 auto new_target = createTempEmptyFile(); 283 remove( symlink_name.c_str() ); 284 waitNotificationReceived(); 285 286 symlink( new_target.c_str(), symlink_name.c_str() ); 287 ASSERT_TRUE( waitNotificationReceived() ); 288 289 remove( new_target.c_str() ); 290 } 291 #endif //HAVE_SYMLINK 292 293 /*****/ 294 295 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { 296 auto mortal_watch_tower = new PlatformWatchTower(); 297 auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } ); 298 299 delete mortal_watch_tower; 300 // reg will be destroyed after the watch_tower 301 } 302 303 /*****/ 304 305 class WatchTowerDirectories: public WatchTowerSingleFile { 306 public: 307 string second_dir_name; 308 string second_file_name; 309 string third_file_name; 310 311 Registration registration_two; 312 Registration registration_three; 313 314 WatchTowerDirectories() { 315 second_dir_name = createTempDir(); 316 second_file_name = createTempEmptyFileInDir( second_dir_name ); 317 third_file_name = createTempEmptyFileInDir( second_dir_name ); 318 } 319 320 ~WatchTowerDirectories() { 321 remove( third_file_name.c_str() ); 322 remove( second_file_name.c_str() ); 323 324 removeDir( second_dir_name ); 325 } 326 327 string createTempDir() { 328 #ifdef _WIN32 329 static int counter = 1; 330 char temp_dir[255]; 331 332 GetTempPath( sizeof temp_dir, temp_dir ); 333 334 string dir_name = string { temp_dir } + string { "\\test" } + to_string( counter++ ); 335 mkdir( dir_name.c_str() ); 336 return dir_name; 337 #else 338 char dir_template[] = "/tmp/XXXXXX"; 339 return { mkdtemp( dir_template ) }; 340 #endif 341 } 342 343 string createTempEmptyFileInDir( const string& dir ) { 344 static int counter = 1; 345 return createTempEmptyFile( dir + std::string { "/temp" } + to_string( counter++ ) ); 346 } 347 348 void removeDir( const string& name ) { 349 rmdir( name.c_str() ); 350 } 351 }; 352 353 TEST_F( WatchTowerDirectories, FollowThreeFilesInTwoDirs ) { 354 registration_two = registerFile( second_file_name ); 355 registration_three = registerFile( third_file_name ); 356 357 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); 358 } 359 360 TEST_F( WatchTowerDirectories, FollowTwoFilesInTwoDirs ) { 361 registration_two = registerFile( second_file_name ); 362 { 363 auto temp_registration_three = registerFile( third_file_name ); 364 } 365 366 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); 367 } 368 369 TEST_F( WatchTowerDirectories, FollowOneFileInOneDir ) { 370 { 371 auto temp_registration_two = registerFile( second_file_name ); 372 auto temp_registration_three = registerFile( third_file_name ); 373 374 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) ); 375 } 376 377 ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 1 ) ); 378 } 379 380 /*****/ 381 382 #ifdef _WIN32 383 class WinNotificationInfoListTest : public testing::Test { 384 public: 385 using Action = WinNotificationInfo::Action; 386 387 struct Buffer { 388 uint32_t next_addr; 389 uint32_t action; 390 uint32_t filename_length; 391 wchar_t filename[13]; 392 }; 393 static struct Buffer buffer[2]; 394 395 WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) }; 396 WinNotificationInfoList::iterator iterator { std::begin( list ) }; 397 }; 398 399 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = 400 { { 40, 1, 26, L"Filename.txt" }, 401 { 0, 3, 18, L"file2.txt" } }; 402 403 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { 404 auto notification = *iterator; 405 ASSERT_THAT( ¬ification, NotNull() ); 406 } 407 408 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { 409 ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); 410 } 411 412 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { 413 ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); 414 } 415 416 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { 417 ++iterator; 418 auto notification = *iterator; 419 ASSERT_THAT( ¬ification, NotNull() ); 420 } 421 422 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { 423 iterator++; 424 ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); 425 ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); 426 } 427 428 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { 429 for ( auto notification : list ) { 430 notification.action(); 431 } 432 } 433 #endif 434