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