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 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) { 82 auto file_name = createTempEmptyFile(); 83 auto registration = watch_tower->addFile( file_name, [] (void) { } ); 84 } 85 86 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) { 87 auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } ); 88 } 89 90 /*****/ 91 92 class WatchTowerSingleFile: public WatchTowerBehaviour { 93 public: 94 static const int TIMEOUT; 95 96 string file_name; 97 WatchTower::Registration registration; 98 99 mutex mutex_; 100 condition_variable cv_; 101 int notification_received = 0; 102 103 WatchTower::Registration registerFile( const string& file_name ) { 104 weak_ptr<void> weakHeartbeat( heartbeat_ ); 105 106 auto reg = watch_tower->addFile( file_name, [this, weakHeartbeat] (void) { 107 // Ensure the fixture object is still alive using the heartbeat 108 if ( auto keep = weakHeartbeat.lock() ) { 109 unique_lock<mutex> lock(mutex_); 110 ++notification_received; 111 cv_.notify_one(); 112 } } ); 113 114 return reg; 115 } 116 117 WatchTowerSingleFile() : WatchTowerBehaviour(), 118 heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) ) 119 { } 120 121 void SetUp() override { 122 file_name = createTempEmptyFile(); 123 registration = registerFile( file_name ); 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 void TearDown() override { 145 remove( file_name.c_str() ); 146 } 147 148 private: 149 // Heartbeat ensures the object is still alive 150 shared_ptr<void> heartbeat_; 151 }; 152 153 #ifdef _WIN32 154 const int WatchTowerSingleFile::TIMEOUT = 2000; 155 #else 156 const int WatchTowerSingleFile::TIMEOUT = 20; 157 #endif 158 159 #include "log.h" 160 161 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) { 162 appendDataToFile( file_name ); 163 ASSERT_TRUE( waitNotificationReceived() ); 164 } 165 166 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) { 167 remove( file_name.c_str() ); 168 ASSERT_TRUE( waitNotificationReceived() ); 169 } 170 171 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) { 172 remove( file_name.c_str() ); 173 waitNotificationReceived(); 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 = createTempEmptyFile(); 211 remove( new_file_name.c_str() ); 212 213 rename( file_name.c_str(), new_file_name.c_str() ); 214 ASSERT_TRUE( waitNotificationReceived() ); 215 216 rename( new_file_name.c_str(), file_name.c_str() ); 217 } 218 219 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) { 220 remove( file_name.c_str() ); 221 waitNotificationReceived(); 222 223 auto new_file_name = createTempEmptyFile(); 224 appendDataToFile( new_file_name ); 225 226 rename( new_file_name.c_str(), file_name.c_str() ); 227 ASSERT_TRUE( waitNotificationReceived() ); 228 } 229 230 /*****/ 231 232 #ifdef HAVE_SYMLINK 233 class WatchTowerSymlink: public WatchTowerSingleFile { 234 public: 235 string symlink_name; 236 237 void SetUp() override { 238 file_name = createTempEmptyFile(); 239 symlink_name = createTempEmptyFile(); 240 remove( symlink_name.c_str() ); 241 symlink( file_name.c_str(), symlink_name.c_str() ); 242 243 registration = registerFile( symlink_name ); 244 } 245 246 void TearDown() override { 247 remove( symlink_name.c_str() ); 248 remove( file_name.c_str() ); 249 } 250 }; 251 252 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) { 253 appendDataToFile( symlink_name ); 254 ASSERT_TRUE( waitNotificationReceived() ); 255 } 256 257 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) { 258 appendDataToFile( file_name ); 259 ASSERT_TRUE( waitNotificationReceived() ); 260 } 261 262 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) { 263 remove( symlink_name.c_str() ); 264 ASSERT_TRUE( waitNotificationReceived() ); 265 } 266 267 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) { 268 remove( file_name.c_str() ); 269 ASSERT_TRUE( waitNotificationReceived() ); 270 } 271 272 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) { 273 auto new_target = createTempEmptyFile(); 274 remove( symlink_name.c_str() ); 275 waitNotificationReceived(); 276 277 symlink( new_target.c_str(), symlink_name.c_str() ); 278 ASSERT_TRUE( waitNotificationReceived() ); 279 280 remove( new_target.c_str() ); 281 } 282 #endif //HAVE_SYMLINK 283 284 /*****/ 285 286 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) { 287 #if 0 288 auto mortal_watch_tower = new INotifyWatchTower(); 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 #endif 294 } 295