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 #ifdef _WIN32 15 # include "winwatchtower.h" 16 #else 17 # include "inotifywatchtower.h" 18 #endif 19 20 using namespace std; 21 using namespace testing; 22 23 #ifdef _WIN32 24 #if 0 25 TEST( WatchTowerBase, NotifyAFile ) { 26 int fd = creat( "c:\\test", S_IRUSR | S_IWUSR ); 27 close( fd ); 28 29 auto watch_tower = make_shared<WinWatchTower>(); 30 std::this_thread::sleep_for( std::chrono::milliseconds(1000) ); 31 { 32 auto registration = watch_tower->addFile( "c:\\test", [] {} ); 33 34 std::this_thread::sleep_for( std::chrono::milliseconds(5000) ); 35 36 static const char* string = "Test line\n"; 37 fd = open( "c:\\test", O_WRONLY | O_APPEND ); 38 write( fd, (void*) string, strlen( string ) ); 39 close( fd ); 40 41 std::this_thread::sleep_for( std::chrono::milliseconds(10000) ); 42 } 43 } 44 #endif 45 #endif 46 47 class WatchTowerBehaviour: public testing::Test { 48 public: 49 #ifdef _WIN32 50 shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>(); 51 #else 52 shared_ptr<WatchTower> watch_tower = make_shared<INotifyWatchTower>(); 53 #endif 54 55 string createTempEmptyFile( string file_name = "" ) { 56 const char* name; 57 58 if ( ! file_name.empty() ) { 59 name = file_name.c_str(); 60 } 61 else { 62 // I know tmpnam is bad but I need control over the file 63 // and it is the only one which exits on Windows. 64 #if _WIN32 65 name = _tempnam( "c:\\temp", "glogg_test" ); 66 #else 67 name = tmpnam( nullptr ); 68 #endif 69 } 70 int fd = creat( name, S_IRUSR | S_IWUSR ); 71 close( fd ); 72 73 return string( name ); 74 } 75 76 string getNonExistingFileName() { 77 return string( tmpnam( nullptr ) ); 78 } 79 }; 80 81 #if 0 82 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) { 83 auto file_name = createTempEmptyFile(); 84 auto registration = watch_tower->addFile( file_name, [] (void) { } ); 85 } 86 87 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) { 88 auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } ); 89 } 90 #endif 91 92 /*****/ 93 94 class WatchTowerSingleFile: public WatchTowerBehaviour { 95 public: 96 static const int TIMEOUT; 97 98 string file_name; 99 WatchTower::Registration registration; 100 101 mutex mutex_; 102 condition_variable cv_; 103 int notification_received = 0; 104 105 WatchTower::Registration registerFile( const string& file_name ) { 106 weak_ptr<void> weakHeartbeat( heartbeat_ ); 107 108 auto reg = watch_tower->addFile( file_name, [this, weakHeartbeat] (void) { 109 // Ensure the fixture object is still alive using the heartbeat 110 if ( auto keep = weakHeartbeat.lock() ) { 111 unique_lock<mutex> lock(mutex_); 112 ++notification_received; 113 cv_.notify_one(); 114 } } ); 115 116 return reg; 117 } 118 119 WatchTowerSingleFile() : WatchTowerBehaviour(), 120 heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) ) 121 { } 122 123 void SetUp() override { 124 file_name = createTempEmptyFile(); 125 registration = registerFile( file_name ); 126 } 127 128 bool waitNotificationReceived( int number = 1 ) { 129 unique_lock<mutex> lock(mutex_); 130 bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT), 131 [this, number] { return notification_received >= number; } ) ); 132 133 // Reinit the notification 134 notification_received = 0; 135 136 return result; 137 } 138 139 void appendDataToFile( const string& file_name ) { 140 static const char* string = "Test line\n"; 141 int fd = open( file_name.c_str(), O_WRONLY | O_APPEND ); 142 write( fd, (void*) string, strlen( string ) ); 143 close( fd ); 144 } 145 146 void TearDown() override { 147 remove( file_name.c_str() ); 148 } 149 150 private: 151 // Heartbeat ensures the object is still alive 152 shared_ptr<void> heartbeat_; 153 }; 154 155 #ifdef _WIN32 156 const int WatchTowerSingleFile::TIMEOUT = 20000; 157 #else 158 const int WatchTowerSingleFile::TIMEOUT = 20; 159 #endif 160 161 #include "log.h" 162 163 TEST_F( WatchTowerSingleFile, DISABLED_SignalsWhenAWatchedFileIsAppended ) { 164 appendDataToFile( file_name ); 165 ASSERT_TRUE( waitNotificationReceived() ); 166 } 167 168 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { 169 remove( file_name.c_str() ); 170 ASSERT_TRUE( waitNotificationReceived() ); 171 } 172 173 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { 174 remove( file_name.c_str() ); 175 waitNotificationReceived(); 176 createTempEmptyFile( file_name ); 177 ASSERT_TRUE( waitNotificationReceived() ); 178 } 179 180 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) { 181 auto second_file_name = createTempEmptyFile(); 182 { 183 auto second_registration = registerFile( second_file_name ); 184 appendDataToFile( second_file_name ); 185 ASSERT_TRUE( waitNotificationReceived() ); 186 } 187 188 // The registration will be removed here. 189 appendDataToFile( second_file_name ); 190 ASSERT_FALSE( waitNotificationReceived() ); 191 192 remove( second_file_name.c_str() ); 193 } 194 195 TEST_F( WatchTowerSingleFile, DISABLED_TwoWatchesOnSameFileYieldsTwoNotifications ) { 196 auto second_registration = registerFile( file_name ); 197 appendDataToFile( file_name ); 198 199 ASSERT_TRUE( waitNotificationReceived( 2 ) ); 200 } 201 202 TEST_F( WatchTowerSingleFile, DISABLED_RemovingOneWatchOfTwoStillYieldsOneNotification ) { 203 { 204 auto second_registration = registerFile( file_name ); 205 } 206 207 appendDataToFile( file_name ); 208 ASSERT_TRUE( waitNotificationReceived( 1 ) ); 209 } 210 211 TEST_F( WatchTowerSingleFile, DISABLED_RenamingTheFileYieldsANotification ) { 212 auto new_file_name = createTempEmptyFile(); 213 remove( new_file_name.c_str() ); 214 215 rename( file_name.c_str(), new_file_name.c_str() ); 216 ASSERT_TRUE( waitNotificationReceived() ); 217 218 rename( new_file_name.c_str(), file_name.c_str() ); 219 } 220 221 TEST_F( WatchTowerSingleFile, DISABLED_RenamingAFileToTheWatchedNameYieldsANotification ) { 222 remove( file_name.c_str() ); 223 waitNotificationReceived(); 224 225 auto new_file_name = createTempEmptyFile(); 226 appendDataToFile( new_file_name ); 227 228 rename( new_file_name.c_str(), file_name.c_str() ); 229 ASSERT_TRUE( waitNotificationReceived() ); 230 } 231 232 /*****/ 233 234 #ifdef HAVE_SYMLINK 235 class WatchTowerSymlink: public WatchTowerSingleFile { 236 public: 237 string symlink_name; 238 239 void SetUp() override { 240 file_name = createTempEmptyFile(); 241 symlink_name = createTempEmptyFile(); 242 remove( symlink_name.c_str() ); 243 symlink( file_name.c_str(), symlink_name.c_str() ); 244 245 registration = registerFile( symlink_name ); 246 } 247 248 void TearDown() override { 249 remove( symlink_name.c_str() ); 250 remove( file_name.c_str() ); 251 } 252 }; 253 254 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { 255 appendDataToFile( symlink_name ); 256 ASSERT_TRUE( waitNotificationReceived() ); 257 } 258 259 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { 260 appendDataToFile( file_name ); 261 ASSERT_TRUE( waitNotificationReceived() ); 262 } 263 264 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { 265 remove( symlink_name.c_str() ); 266 ASSERT_TRUE( waitNotificationReceived() ); 267 } 268 269 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { 270 remove( file_name.c_str() ); 271 ASSERT_TRUE( waitNotificationReceived() ); 272 } 273 274 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { 275 auto new_target = createTempEmptyFile(); 276 remove( symlink_name.c_str() ); 277 waitNotificationReceived(); 278 279 symlink( new_target.c_str(), symlink_name.c_str() ); 280 ASSERT_TRUE( waitNotificationReceived() ); 281 282 remove( new_target.c_str() ); 283 } 284 #endif //HAVE_SYMLINK 285 286 /*****/ 287 288 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { 289 #if 0 290 auto mortal_watch_tower = new INotifyWatchTower(); 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 #endif 296 } 297 298 /*****/ 299 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