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 "inotifywatchtower.h" 20 #endif 21 22 using namespace std; 23 using namespace testing; 24 25 class WatchTowerBehaviour: public testing::Test { 26 public: 27 #ifdef _WIN32 28 shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>(); 29 #else 30 shared_ptr<WatchTower> watch_tower = make_shared<INotifyWatchTower>(); 31 #endif 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 WatchTower::Registration registration; 92 93 mutex mutex_; 94 condition_variable cv_; 95 int notification_received = 0; 96 97 WatchTower::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 = 5000; 147 #else 148 const int WatchTowerSingleFile::TIMEOUT = 20; 149 #endif 150 151 TEST_F( WatchTowerSingleFile, Simple ) { 152 } 153 154 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) { 155 appendDataToFile( file_name ); 156 ASSERT_TRUE( waitNotificationReceived() ); 157 } 158 159 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { 160 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 161 remove( file_name.c_str() ); 162 ASSERT_TRUE( waitNotificationReceived() ); 163 } 164 165 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { 166 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 167 remove( file_name.c_str() ); 168 waitNotificationReceived(); 169 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 170 createTempEmptyFile( file_name ); 171 ASSERT_TRUE( waitNotificationReceived() ); 172 } 173 174 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) { 175 auto second_file_name = createTempEmptyFile(); 176 { 177 auto second_registration = registerFile( second_file_name ); 178 appendDataToFile( second_file_name ); 179 ASSERT_TRUE( waitNotificationReceived() ); 180 } 181 182 // The registration will be removed here. 183 appendDataToFile( second_file_name ); 184 ASSERT_FALSE( waitNotificationReceived() ); 185 186 remove( second_file_name.c_str() ); 187 } 188 189 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) { 190 auto second_registration = registerFile( file_name ); 191 appendDataToFile( file_name ); 192 193 ASSERT_TRUE( waitNotificationReceived( 2 ) ); 194 } 195 196 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) { 197 { 198 auto second_registration = registerFile( file_name ); 199 } 200 201 appendDataToFile( file_name ); 202 ASSERT_TRUE( waitNotificationReceived( 1 ) ); 203 } 204 205 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) { 206 auto new_file_name = createTempName(); 207 208 rename( file_name.c_str(), new_file_name ); 209 ASSERT_TRUE( waitNotificationReceived() ); 210 211 rename( new_file_name, file_name.c_str() ); 212 } 213 214 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) { 215 remove( file_name.c_str() ); 216 waitNotificationReceived(); 217 218 std::string new_file_name = createTempEmptyFile(); 219 appendDataToFile( new_file_name ); 220 221 rename( new_file_name.c_str(), file_name.c_str() ); 222 ASSERT_TRUE( waitNotificationReceived() ); 223 } 224 225 /*****/ 226 227 #ifdef HAVE_SYMLINK 228 class WatchTowerSymlink: public WatchTowerSingleFile { 229 public: 230 string symlink_name; 231 232 void SetUp() override { 233 file_name = createTempEmptyFile(); 234 symlink_name = createTempEmptyFile(); 235 remove( symlink_name.c_str() ); 236 symlink( file_name.c_str(), symlink_name.c_str() ); 237 238 registration = registerFile( symlink_name ); 239 } 240 241 void TearDown() override { 242 remove( symlink_name.c_str() ); 243 remove( file_name.c_str() ); 244 } 245 }; 246 247 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { 248 appendDataToFile( symlink_name ); 249 ASSERT_TRUE( waitNotificationReceived() ); 250 } 251 252 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { 253 appendDataToFile( file_name ); 254 ASSERT_TRUE( waitNotificationReceived() ); 255 } 256 257 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { 258 remove( symlink_name.c_str() ); 259 ASSERT_TRUE( waitNotificationReceived() ); 260 } 261 262 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { 263 remove( file_name.c_str() ); 264 ASSERT_TRUE( waitNotificationReceived() ); 265 } 266 267 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { 268 auto new_target = createTempEmptyFile(); 269 remove( symlink_name.c_str() ); 270 waitNotificationReceived(); 271 272 symlink( new_target.c_str(), symlink_name.c_str() ); 273 ASSERT_TRUE( waitNotificationReceived() ); 274 275 remove( new_target.c_str() ); 276 } 277 #endif //HAVE_SYMLINK 278 279 /*****/ 280 281 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { 282 #if _WIN32 283 auto mortal_watch_tower = new WinWatchTower(); 284 #else 285 auto mortal_watch_tower = new INotifyWatchTower(); 286 #endif 287 auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } ); 288 289 delete mortal_watch_tower; 290 // reg will be destroyed after the watch_tower 291 } 292 293 /*****/ 294 295 #ifdef _WIN32 296 class WinNotificationInfoListTest : public testing::Test { 297 public: 298 using Action = WinNotificationInfo::Action; 299 300 struct Buffer { 301 uint32_t next_addr; 302 uint32_t action; 303 uint32_t filename_length; 304 wchar_t filename[13]; 305 }; 306 static struct Buffer buffer[2]; 307 308 WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) }; 309 WinNotificationInfoList::iterator iterator { std::begin( list ) }; 310 }; 311 312 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] = 313 { { 40, 1, 26, L"Filename.txt" }, 314 { 0, 3, 18, L"file2.txt" } }; 315 316 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) { 317 auto notification = *iterator; 318 ASSERT_THAT( ¬ification, NotNull() ); 319 } 320 321 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) { 322 ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) ); 323 } 324 325 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) { 326 ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) ); 327 } 328 329 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) { 330 ++iterator; 331 auto notification = *iterator; 332 ASSERT_THAT( ¬ification, NotNull() ); 333 } 334 335 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) { 336 iterator++; 337 ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) ); 338 ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) ); 339 } 340 341 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) { 342 for ( auto notification : list ) { 343 notification.action(); 344 } 345 } 346 #endif 347