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