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