xref: /glogg/tests/watchtowerTest.cpp (revision 3104b26858f76d3848af58b8865d4e7d5735d2f8)
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 #include "log.h"
15 
16 #include "watchtower.h"
17 
18 #ifdef _WIN32
19 #  include "winwatchtowerdriver.h"
20 using PlatformWatchTower = WatchTower<WinWatchTowerDriver>;
21 #else
22 #  include "inotifywatchtowerdriver.h"
23 using PlatformWatchTower = WatchTower<INotifyWatchTowerDriver>;
24 #endif
25 
26 using namespace std;
27 using namespace testing;
28 
29 class WatchTowerBehaviour: public testing::Test {
30   public:
31     shared_ptr<PlatformWatchTower> watch_tower = make_shared<PlatformWatchTower>();
32 
33     const char* createTempName()
34     {
35         const char* name;
36 #if _WIN32
37         name = _tempnam( "c:\\temp", "glogg_test" );
38 #else
39         name = tmpnam( nullptr );
40 #endif
41         return name;
42     }
43 
44     string createTempEmptyFile( string file_name = "" ) {
45         const char* name;
46 
47         if ( ! file_name.empty() ) {
48             name = file_name.c_str();
49         }
50         else {
51             // I know tmpnam is bad but I need control over the file
52             // and it is the only one which exits on Windows.
53             name = createTempName();
54         }
55         int fd = creat( name, S_IRUSR | S_IWUSR );
56         close( fd );
57 
58         return string( name );
59     }
60 
61     string getNonExistingFileName() {
62 #if _WIN32
63         return string( _tempnam( "c:\\temp", "inexistant" ) );
64 #else
65         return string( tmpnam( nullptr ) );
66 #endif
67     }
68 
69     WatchTowerBehaviour() {
70         // Default to quiet, but increase to debug
71         FILELog::setReportingLevel( logERROR );
72     }
73 };
74 
75 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) {
76     auto file_name = createTempEmptyFile();
77     auto registration = watch_tower->addFile( file_name, [] (void) { } );
78 }
79 
80 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) {
81     auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } );
82 }
83 
84 /*****/
85 
86 class WatchTowerSingleFile: public WatchTowerBehaviour {
87   public:
88     static const int TIMEOUT;
89 
90     mutex mutex_;
91     condition_variable cv_;
92 
93     string file_name;
94     Registration registration;
95 
96     int notification_received = 0;
97 
98     Registration registerFile( const string& filename ) {
99         weak_ptr<void> weakHeartbeat( heartbeat_ );
100 
101         auto reg = watch_tower->addFile( filename, [this, weakHeartbeat] (void) {
102             // Ensure the fixture object is still alive using the heartbeat
103             if ( auto keep = weakHeartbeat.lock() ) {
104                 unique_lock<mutex> lock(mutex_);
105                 ++notification_received;
106                 cv_.notify_one();
107             } } );
108 
109         return reg;
110     }
111 
112     WatchTowerSingleFile()
113         : heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) )
114     {
115         file_name = createTempEmptyFile();
116         registration = registerFile( file_name );
117     }
118 
119     ~WatchTowerSingleFile() {
120         remove( file_name.c_str() );
121     }
122 
123     bool waitNotificationReceived( int number = 1 ) {
124         unique_lock<mutex> lock(mutex_);
125         bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT),
126                 [this, number] { return notification_received >= number; } ) );
127 
128         // Reinit the notification
129         notification_received = 0;
130 
131         return result;
132     }
133 
134     void appendDataToFile( const string& file_name ) {
135         static const char* string = "Test line\n";
136         int fd = open( file_name.c_str(), O_WRONLY | O_APPEND );
137         write( fd, (void*) string, strlen( string ) );
138         close( fd );
139     }
140 
141   private:
142     // Heartbeat ensures the object is still alive
143     shared_ptr<void> heartbeat_;
144 };
145 
146 #ifdef _WIN32
147 const int WatchTowerSingleFile::TIMEOUT = 2000;
148 #else
149 const int WatchTowerSingleFile::TIMEOUT = 20;
150 #endif
151 
152 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) {
153     appendDataToFile( file_name );
154     ASSERT_TRUE( waitNotificationReceived() );
155 }
156 
157 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) {
158     remove( file_name.c_str() );
159     ASSERT_TRUE( waitNotificationReceived() );
160 }
161 
162 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) {
163     remove( file_name.c_str() );
164     waitNotificationReceived();
165     createTempEmptyFile( file_name );
166     ASSERT_TRUE( waitNotificationReceived() );
167 }
168 
169 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
170     auto second_file_name = createTempEmptyFile();
171     std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
172     // Ensure file creation has been 'digested'
173     {
174         auto second_registration = registerFile( second_file_name );
175         appendDataToFile( second_file_name );
176         ASSERT_TRUE( waitNotificationReceived() );
177     }
178 
179     // The registration will be removed here.
180     appendDataToFile( second_file_name );
181     ASSERT_FALSE( waitNotificationReceived() );
182 
183     remove( second_file_name.c_str() );
184 }
185 
186 TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) {
187     auto second_file_name = createTempEmptyFile();
188 
189     for ( int i = 0; i < 100; i++ )
190     {
191         auto second_registration = registerFile( second_file_name );
192         appendDataToFile( second_file_name );
193         ASSERT_TRUE( waitNotificationReceived() );
194     }
195 
196     // The registration will be removed here.
197     appendDataToFile( second_file_name );
198     ASSERT_FALSE( waitNotificationReceived() );
199 
200     remove( second_file_name.c_str() );
201 }
202 
203 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) {
204     auto second_registration = registerFile( file_name );
205     appendDataToFile( file_name );
206 
207     ASSERT_TRUE( waitNotificationReceived( 2 ) );
208 }
209 
210 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) {
211     {
212         auto second_registration = registerFile( file_name );
213     }
214 
215     appendDataToFile( file_name );
216     ASSERT_TRUE( waitNotificationReceived( 1 ) );
217 }
218 
219 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) {
220     auto new_file_name = createTempName();
221 
222     rename( file_name.c_str(), new_file_name );
223     ASSERT_TRUE( waitNotificationReceived() );
224 
225     rename( new_file_name, file_name.c_str() );
226 }
227 
228 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) {
229     remove( file_name.c_str() );
230     waitNotificationReceived();
231 
232     std::string new_file_name = createTempEmptyFile();
233     appendDataToFile( new_file_name );
234 
235     rename( new_file_name.c_str(), file_name.c_str() );
236     ASSERT_TRUE( waitNotificationReceived() );
237 }
238 
239 /*****/
240 
241 #ifdef HAVE_SYMLINK
242 class WatchTowerSymlink: public WatchTowerSingleFile {
243   public:
244     string symlink_name;
245 
246     void SetUp() override {
247         file_name = createTempEmptyFile();
248         symlink_name = createTempEmptyFile();
249         remove( symlink_name.c_str() );
250         symlink( file_name.c_str(), symlink_name.c_str() );
251 
252         registration = registerFile( symlink_name );
253     }
254 
255     void TearDown() override {
256         remove( symlink_name.c_str() );
257         remove( file_name.c_str() );
258     }
259 };
260 
261 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
262     appendDataToFile( symlink_name );
263     ASSERT_TRUE( waitNotificationReceived() );
264 }
265 
266 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
267     appendDataToFile( file_name );
268     ASSERT_TRUE( waitNotificationReceived() );
269 }
270 
271 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
272     remove( symlink_name.c_str() );
273     ASSERT_TRUE( waitNotificationReceived() );
274 }
275 
276 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
277     remove( file_name.c_str() );
278     ASSERT_TRUE( waitNotificationReceived() );
279 }
280 
281 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
282     auto new_target = createTempEmptyFile();
283     remove( symlink_name.c_str() );
284     waitNotificationReceived();
285 
286     symlink( new_target.c_str(), symlink_name.c_str() );
287     ASSERT_TRUE( waitNotificationReceived() );
288 
289     remove( new_target.c_str() );
290 }
291 #endif //HAVE_SYMLINK
292 
293 /*****/
294 
295 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
296     auto mortal_watch_tower = new PlatformWatchTower();
297     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
298 
299     delete mortal_watch_tower;
300     // reg will be destroyed after the watch_tower
301 }
302 
303 /*****/
304 
305 class WatchTowerDirectories: public WatchTowerSingleFile {
306   public:
307     string second_dir_name;
308     string second_file_name;
309     string third_file_name;
310 
311     Registration registration_two;
312     Registration registration_three;
313 
314     WatchTowerDirectories() {
315         second_dir_name = createTempDir();
316         second_file_name = createTempEmptyFileInDir( second_dir_name );
317         third_file_name  = createTempEmptyFileInDir( second_dir_name );
318     }
319 
320     ~WatchTowerDirectories() {
321         remove( third_file_name.c_str() );
322         remove( second_file_name.c_str() );
323 
324         removeDir( second_dir_name );
325     }
326 
327     string createTempDir() {
328 #ifdef _WIN32
329         static int counter = 1;
330         char temp_dir[255];
331 
332         GetTempPath( sizeof temp_dir, temp_dir );
333 
334         string dir_name = string { temp_dir } + string { "\\test" } + to_string( counter++ );
335         mkdir( dir_name.c_str() );
336         return dir_name;
337 #else
338         char dir_template[] = "/tmp/XXXXXX";
339         return { mkdtemp( dir_template ) };
340 #endif
341     }
342 
343     string createTempEmptyFileInDir( const string& dir ) {
344         static int counter = 1;
345         return createTempEmptyFile( dir + std::string { "/temp" } + to_string( counter++ ) );
346     }
347 
348     void removeDir( const string& name ) {
349         rmdir( name.c_str() );
350     }
351 };
352 
353 TEST_F( WatchTowerDirectories, FollowThreeFilesInTwoDirs ) {
354     registration_two   = registerFile( second_file_name );
355     registration_three = registerFile( third_file_name );
356 
357     ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) );
358 }
359 
360 TEST_F( WatchTowerDirectories, FollowTwoFilesInTwoDirs ) {
361     registration_two   = registerFile( second_file_name );
362     {
363         auto temp_registration_three = registerFile( third_file_name );
364     }
365 
366     ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) );
367 }
368 
369 TEST_F( WatchTowerDirectories, FollowOneFileInOneDir ) {
370     {
371         auto temp_registration_two   = registerFile( second_file_name );
372         auto temp_registration_three = registerFile( third_file_name );
373 
374         ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) );
375     }
376 
377     ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 1 ) );
378 }
379 
380 /*****/
381 
382 #ifdef _WIN32
383 class WinNotificationInfoListTest : public testing::Test {
384   public:
385     using Action = WinNotificationInfo::Action;
386 
387     struct Buffer {
388         uint32_t next_addr;
389         uint32_t action;
390         uint32_t filename_length;
391         wchar_t filename[13];
392     };
393     static struct Buffer buffer[2];
394 
395     WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) };
396     WinNotificationInfoList::iterator iterator { std::begin( list ) };
397 };
398 
399 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] =
400     { { 40, 1, 26, L"Filename.txt" },
401       { 0, 3, 18, L"file2.txt" } };
402 
403 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) {
404     auto notification = *iterator;
405     ASSERT_THAT( &notification, NotNull() );
406 }
407 
408 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) {
409     ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) );
410 }
411 
412 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) {
413     ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) );
414 }
415 
416 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) {
417     ++iterator;
418     auto notification = *iterator;
419     ASSERT_THAT( &notification, NotNull() );
420 }
421 
422 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) {
423     iterator++;
424     ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) );
425     ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) );
426 }
427 
428 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) {
429     for ( auto notification : list ) {
430         notification.action();
431     }
432 }
433 #endif
434