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 #ifdef _WIN32 17 # include "winwatchtower.h" 18 #else 19 # include "inotifywatchtowerdriver.h" 20 #endif 21 22 #include "watchtower.h" 23 24 using namespace std; 25 using namespace testing; 26 27 class WatchTowerBehaviour: public testing::Test { 28 public: 29 #ifdef _WIN32 30 shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>(); 31 #else 32 shared_ptr<WatchTower> watch_tower = make_shared<WatchTower>(); 33 #endif 34 35 const char* createTempName() 36 { 37 const char* name; 38 #if _WIN32 39 name = _tempnam( "c:\\temp", "glogg_test" ); 40 #else 41 name = tmpnam( nullptr ); 42 #endif 43 return name; 44 } 45 46 string createTempEmptyFile( string file_name = "" ) { 47 const char* name; 48 49 if ( ! file_name.empty() ) { 50 name = file_name.c_str(); 51 } 52 else { 53 // I know tmpnam is bad but I need control over the file 54 // and it is the only one which exits on Windows. 55 name = createTempName(); 56 } 57 int fd = creat( name, S_IRUSR | S_IWUSR ); 58 close( fd ); 59 60 return string( name ); 61 } 62 63 string getNonExistingFileName() { 64 #if _WIN32 65 return string( _tempnam( "c:\\temp", "inexistant" ) ); 66 #else 67 return string( tmpnam( nullptr ) ); 68 #endif 69 } 70 71 WatchTowerBehaviour() { 72 // Default to quiet, but increase to debug 73 FILELog::setReportingLevel( logERROR ); 74 } 75 }; 76 77 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) { 78 auto file_name = createTempEmptyFile(); 79 auto registration = watch_tower->addFile( file_name, [] (void) { } ); 80 } 81 82 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) { 83 auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } ); 84 } 85 86 /*****/ 87 88 class WatchTowerSingleFile: public WatchTowerBehaviour { 89 public: 90 static const int TIMEOUT; 91 92 string file_name; 93 WatchTower::Registration registration; 94 95 mutex mutex_; 96 condition_variable cv_; 97 int notification_received = 0; 98 99 WatchTower::Registration registerFile( const string& filename ) { 100 weak_ptr<void> weakHeartbeat( heartbeat_ ); 101 102 auto reg = watch_tower->addFile( filename, [this, weakHeartbeat] (void) { 103 // Ensure the fixture object is still alive using the heartbeat 104 if ( auto keep = weakHeartbeat.lock() ) { 105 unique_lock<mutex> lock(mutex_); 106 ++notification_received; 107 cv_.notify_one(); 108 } } ); 109 110 return reg; 111 } 112 113 WatchTowerSingleFile() 114 : heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) ) 115 { 116 file_name = createTempEmptyFile(); 117 registration = registerFile( file_name ); 118 } 119 120 ~WatchTowerSingleFile() { 121 remove( file_name.c_str() ); 122 } 123 124 bool waitNotificationReceived( int number = 1 ) { 125 unique_lock<mutex> lock(mutex_); 126 bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT), 127 [this, number] { return notification_received >= number; } ) ); 128 129 // Reinit the notification 130 notification_received = 0; 131 132 return result; 133 } 134 135 void appendDataToFile( const string& file_name ) { 136 static const char* string = "Test line\n"; 137 int fd = open( file_name.c_str(), O_WRONLY | O_APPEND ); 138 write( fd, (void*) string, strlen( string ) ); 139 close( fd ); 140 } 141 142 private: 143 // Heartbeat ensures the object is still alive 144 shared_ptr<void> heartbeat_; 145 }; 146 147 #ifdef _WIN32 148 const int WatchTowerSingleFile::TIMEOUT = 5000; 149 #else 150 const int WatchTowerSingleFile::TIMEOUT = 20; 151 #endif 152 153 TEST_F( WatchTowerSingleFile, Simple ) { 154 } 155 156 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) { 157 appendDataToFile( file_name ); 158 ASSERT_TRUE( waitNotificationReceived() ); 159 } 160 161 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { 162 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 163 remove( file_name.c_str() ); 164 ASSERT_TRUE( waitNotificationReceived() ); 165 } 166 167 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { 168 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 169 remove( file_name.c_str() ); 170 waitNotificationReceived(); 171 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 172 createTempEmptyFile( file_name ); 173 ASSERT_TRUE( waitNotificationReceived() ); 174 } 175 176 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) { 177 auto second_file_name = createTempEmptyFile(); 178 { 179 auto second_registration = registerFile( second_file_name ); 180 appendDataToFile( second_file_name ); 181 ASSERT_TRUE( waitNotificationReceived() ); 182 } 183 184 // The registration will be removed here. 185 appendDataToFile( second_file_name ); 186 ASSERT_FALSE( waitNotificationReceived() ); 187 188 remove( second_file_name.c_str() ); 189 } 190 191 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) { 192 auto second_registration = registerFile( file_name ); 193 appendDataToFile( file_name ); 194 195 ASSERT_TRUE( waitNotificationReceived( 2 ) ); 196 } 197 198 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) { 199 { 200 auto second_registration = registerFile( file_name ); 201 } 202 203 appendDataToFile( file_name ); 204 ASSERT_TRUE( waitNotificationReceived( 1 ) ); 205 } 206 207 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) { 208 auto new_file_name = createTempName(); 209 210 rename( file_name.c_str(), new_file_name ); 211 ASSERT_TRUE( waitNotificationReceived() ); 212 213 rename( new_file_name, file_name.c_str() ); 214 } 215 216 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) { 217 remove( file_name.c_str() ); 218 waitNotificationReceived(); 219 220 std::string new_file_name = createTempEmptyFile(); 221 appendDataToFile( new_file_name ); 222 223 rename( new_file_name.c_str(), file_name.c_str() ); 224 ASSERT_TRUE( waitNotificationReceived() ); 225 } 226 227 /*****/ 228 229 #ifdef HAVE_SYMLINK 230 class WatchTowerSymlink: public WatchTowerSingleFile { 231 public: 232 string symlink_name; 233 234 void SetUp() override { 235 file_name = createTempEmptyFile(); 236 symlink_name = createTempEmptyFile(); 237 remove( symlink_name.c_str() ); 238 symlink( file_name.c_str(), symlink_name.c_str() ); 239 240 registration = registerFile( symlink_name ); 241 } 242 243 void TearDown() override { 244 remove( symlink_name.c_str() ); 245 remove( file_name.c_str() ); 246 } 247 }; 248 249 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { 250 appendDataToFile( symlink_name ); 251 ASSERT_TRUE( waitNotificationReceived() ); 252 } 253 254 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { 255 appendDataToFile( file_name ); 256 ASSERT_TRUE( waitNotificationReceived() ); 257 } 258 259 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { 260 remove( symlink_name.c_str() ); 261 ASSERT_TRUE( waitNotificationReceived() ); 262 } 263 264 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { 265 remove( file_name.c_str() ); 266 ASSERT_TRUE( waitNotificationReceived() ); 267 } 268 269 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { 270 auto new_target = createTempEmptyFile(); 271 remove( symlink_name.c_str() ); 272 waitNotificationReceived(); 273 274 symlink( new_target.c_str(), symlink_name.c_str() ); 275 ASSERT_TRUE( waitNotificationReceived() ); 276 277 remove( new_target.c_str() ); 278 } 279 #endif //HAVE_SYMLINK 280 281 /*****/ 282 283 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { 284 #if _WIN32 285 auto mortal_watch_tower = new WinWatchTower(); 286 #else 287 auto mortal_watch_tower = new WatchTower(); 288 #endif 289 auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } ); 290 291 delete mortal_watch_tower; 292 // reg will be destroyed after the watch_tower 293 } 294 295 /*****/ 296 297 #ifdef _WIN32 298 class WinNotificationInfoListTest : public testing::Test { 299 public: 300 using Action = WinNotificationInfo::Action; 301 302 struct Buffer { 303 uint32_t next_addr; 304 uint32_t action; 305 uint32_t filename_length; 306 wchar_t filename[13]; 307 }; 308 static struct Buffer buffer[2]; 309 310 WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) }; 311 WinNotificationInfoList::iterator iterator { std::begin( list ) }; 312 }; 313 314 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = 315 { { 40, 1, 26, L"Filename.txt" }, 316 { 0, 3, 18, L"file2.txt" } }; 317 318 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { 319 auto notification = *iterator; 320 ASSERT_THAT( ¬ification, NotNull() ); 321 } 322 323 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { 324 ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); 325 } 326 327 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { 328 ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); 329 } 330 331 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { 332 ++iterator; 333 auto notification = *iterator; 334 ASSERT_THAT( ¬ification, NotNull() ); 335 } 336 337 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { 338 iterator++; 339 ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); 340 ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); 341 } 342 343 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { 344 for ( auto notification : list ) { 345 notification.action(); 346 } 347 } 348 #endif 349