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 string file_name; 91 Registration registration; 92 93 mutex mutex_; 94 condition_variable cv_; 95 int notification_received = 0; 96 97 Registration registerFile( const string& filename ) { 98 weak_ptr<void> weakHeartbeat( heartbeat_ ); 99 100 auto reg = watch_tower->addFile( filename, [this, weakHeartbeat] (void) { 101 // Ensure the fixture object is still alive using the heartbeat 102 if ( auto keep = weakHeartbeat.lock() ) { 103 unique_lock<mutex> lock(mutex_); 104 ++notification_received; 105 cv_.notify_one(); 106 } } ); 107 108 return reg; 109 } 110 111 WatchTowerSingleFile() 112 : heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) ) 113 { 114 file_name = createTempEmptyFile(); 115 registration = registerFile( file_name ); 116 } 117 118 ~WatchTowerSingleFile() { 119 remove( file_name.c_str() ); 120 } 121 122 bool waitNotificationReceived( int number = 1 ) { 123 unique_lock<mutex> lock(mutex_); 124 bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT), 125 [this, number] { return notification_received >= number; } ) ); 126 127 // Reinit the notification 128 notification_received = 0; 129 130 return result; 131 } 132 133 void appendDataToFile( const string& file_name ) { 134 static const char* string = "Test line\n"; 135 int fd = open( file_name.c_str(), O_WRONLY | O_APPEND ); 136 write( fd, (void*) string, strlen( string ) ); 137 close( fd ); 138 } 139 140 private: 141 // Heartbeat ensures the object is still alive 142 shared_ptr<void> heartbeat_; 143 }; 144 145 #ifdef _WIN32 146 const int WatchTowerSingleFile::TIMEOUT = 1000; 147 #else 148 const int WatchTowerSingleFile::TIMEOUT = 20; 149 #endif 150 151 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) { 152 appendDataToFile( file_name ); 153 ASSERT_TRUE( waitNotificationReceived() ); 154 } 155 156 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { 157 remove( file_name.c_str() ); 158 ASSERT_TRUE( waitNotificationReceived() ); 159 } 160 161 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { 162 remove( file_name.c_str() ); 163 waitNotificationReceived(); 164 createTempEmptyFile( file_name ); 165 ASSERT_TRUE( waitNotificationReceived() ); 166 } 167 168 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) { 169 auto second_file_name = createTempEmptyFile(); 170 { 171 auto second_registration = registerFile( second_file_name ); 172 appendDataToFile( second_file_name ); 173 ASSERT_TRUE( waitNotificationReceived() ); 174 } 175 176 // The registration will be removed here. 177 appendDataToFile( second_file_name ); 178 ASSERT_FALSE( waitNotificationReceived() ); 179 180 remove( second_file_name.c_str() ); 181 } 182 183 TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) { 184 auto second_file_name = createTempEmptyFile(); 185 186 for ( int i = 0; i < 1000; i++ ) 187 { 188 std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); 189 auto second_registration = registerFile( second_file_name ); 190 appendDataToFile( second_file_name ); 191 ASSERT_TRUE( waitNotificationReceived() ); 192 std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) ); 193 appendDataToFile( second_file_name ); 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 #ifdef _WIN32 306 class WinNotificationInfoListTest : public testing::Test { 307 public: 308 using Action = WinNotificationInfo::Action; 309 310 struct Buffer { 311 uint32_t next_addr; 312 uint32_t action; 313 uint32_t filename_length; 314 wchar_t filename[13]; 315 }; 316 static struct Buffer buffer[2]; 317 318 WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) }; 319 WinNotificationInfoList::iterator iterator { std::begin( list ) }; 320 }; 321 322 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = 323 { { 40, 1, 26, L"Filename.txt" }, 324 { 0, 3, 18, L"file2.txt" } }; 325 326 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { 327 auto notification = *iterator; 328 ASSERT_THAT( ¬ification, NotNull() ); 329 } 330 331 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { 332 ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); 333 } 334 335 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { 336 ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); 337 } 338 339 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { 340 ++iterator; 341 auto notification = *iterator; 342 ASSERT_THAT( ¬ification, NotNull() ); 343 } 344 345 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { 346 iterator++; 347 ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); 348 ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); 349 } 350 351 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { 352 for ( auto notification : list ) { 353 notification.action(); 354 } 355 } 356 #endif 357