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