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