xref: /glogg/tests/watchtowerTest.cpp (revision 3af68c64af7cb4688bfaa0e34c226540931185a0)
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