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