xref: /glogg/tests/watchtowerTest.cpp (revision 8b11848fd9995077713535870cee0df00a8eeea0)
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, SignalsWhenAReappearedFileIsAppended ) {
170     remove( file_name.c_str() );
171     waitNotificationReceived();
172     createTempEmptyFile( file_name );
173     waitNotificationReceived();
174 
175     appendDataToFile( file_name );
176     ASSERT_TRUE( waitNotificationReceived() );
177 }
178 
179 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
180     auto second_file_name = createTempEmptyFile();
181     std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
182     // Ensure file creation has been 'digested'
183     {
184         auto second_registration = registerFile( second_file_name );
185         appendDataToFile( second_file_name );
186         ASSERT_TRUE( waitNotificationReceived() );
187     }
188 
189     // The registration will be removed here.
190     appendDataToFile( second_file_name );
191     ASSERT_FALSE( waitNotificationReceived() );
192 
193     remove( second_file_name.c_str() );
194 }
195 
196 TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) {
197     auto second_file_name = createTempEmptyFile();
198 
199     for ( int i = 0; i < 100; i++ )
200     {
201         auto second_registration = registerFile( second_file_name );
202         appendDataToFile( second_file_name );
203         ASSERT_TRUE( waitNotificationReceived() );
204     }
205 
206     // The registration will be removed here.
207     appendDataToFile( second_file_name );
208     ASSERT_FALSE( waitNotificationReceived() );
209 
210     remove( second_file_name.c_str() );
211 }
212 
213 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) {
214     auto second_registration = registerFile( file_name );
215     appendDataToFile( file_name );
216 
217     ASSERT_TRUE( waitNotificationReceived( 2 ) );
218 }
219 
220 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) {
221     {
222         auto second_registration = registerFile( file_name );
223     }
224 
225     appendDataToFile( file_name );
226     ASSERT_TRUE( waitNotificationReceived( 1 ) );
227 }
228 
229 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) {
230     auto new_file_name = createTempName();
231 
232     rename( file_name.c_str(), new_file_name );
233     ASSERT_TRUE( waitNotificationReceived() );
234 
235     rename( new_file_name, file_name.c_str() );
236 }
237 
238 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) {
239     remove( file_name.c_str() );
240     waitNotificationReceived();
241 
242     std::string new_file_name = createTempEmptyFile();
243     appendDataToFile( new_file_name );
244 
245     rename( new_file_name.c_str(), file_name.c_str() );
246     ASSERT_TRUE( waitNotificationReceived() );
247 }
248 
249 /*****/
250 
251 #ifdef HAVE_SYMLINK
252 class WatchTowerSymlink: public WatchTowerSingleFile {
253   public:
254     string symlink_name;
255 
256     void SetUp() override {
257         file_name = createTempEmptyFile();
258         symlink_name = createTempEmptyFile();
259         remove( symlink_name.c_str() );
260         symlink( file_name.c_str(), symlink_name.c_str() );
261 
262         registration = registerFile( symlink_name );
263     }
264 
265     void TearDown() override {
266         remove( symlink_name.c_str() );
267         remove( file_name.c_str() );
268     }
269 };
270 
271 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
272     appendDataToFile( symlink_name );
273     ASSERT_TRUE( waitNotificationReceived() );
274 }
275 
276 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
277     appendDataToFile( file_name );
278     ASSERT_TRUE( waitNotificationReceived() );
279 }
280 
281 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
282     remove( symlink_name.c_str() );
283     ASSERT_TRUE( waitNotificationReceived() );
284 }
285 
286 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
287     remove( file_name.c_str() );
288     ASSERT_TRUE( waitNotificationReceived() );
289 }
290 
291 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
292     auto new_target = createTempEmptyFile();
293     remove( symlink_name.c_str() );
294     waitNotificationReceived();
295 
296     symlink( new_target.c_str(), symlink_name.c_str() );
297     ASSERT_TRUE( waitNotificationReceived() );
298 
299     remove( new_target.c_str() );
300 }
301 
302 TEST_F( WatchTowerSymlink, DataAddedInAReappearingSymlinkYieldsANotification ) {
303     auto new_target = createTempEmptyFile();
304     remove( symlink_name.c_str() );
305     waitNotificationReceived();
306     symlink( new_target.c_str(), symlink_name.c_str() );
307     waitNotificationReceived();
308 
309     appendDataToFile( new_target );
310     ASSERT_TRUE( waitNotificationReceived() );
311 
312     remove( new_target.c_str() );
313 }
314 #endif //HAVE_SYMLINK
315 
316 /*****/
317 
318 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
319     auto mortal_watch_tower = new PlatformWatchTower();
320     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
321 
322     delete mortal_watch_tower;
323     // reg will be destroyed after the watch_tower
324 }
325 
326 /*****/
327 
328 class WatchTowerDirectories: public WatchTowerSingleFile {
329   public:
330     string second_dir_name;
331     string second_file_name;
332     string third_file_name;
333 
334     Registration registration_two;
335     Registration registration_three;
336 
337     WatchTowerDirectories() {
338         second_dir_name = createTempDir();
339         second_file_name = createTempEmptyFileInDir( second_dir_name );
340         third_file_name  = createTempEmptyFileInDir( second_dir_name );
341     }
342 
343     ~WatchTowerDirectories() {
344         remove( third_file_name.c_str() );
345         remove( second_file_name.c_str() );
346 
347         removeDir( second_dir_name );
348     }
349 
350     string createTempDir() {
351 #ifdef _WIN32
352         static int counter = 1;
353         char temp_dir[255];
354 
355         GetTempPath( sizeof temp_dir, temp_dir );
356 
357         string dir_name = string { temp_dir } + string { "\\test" } + to_string( counter++ );
358         mkdir( dir_name.c_str() );
359         return dir_name;
360 #else
361         char dir_template[] = "/tmp/XXXXXX";
362         return { mkdtemp( dir_template ) };
363 #endif
364     }
365 
366     string createTempEmptyFileInDir( const string& dir ) {
367         static int counter = 1;
368         return createTempEmptyFile( dir + std::string { "/temp" } + to_string( counter++ ) );
369     }
370 
371     void removeDir( const string& name ) {
372         rmdir( name.c_str() );
373     }
374 };
375 
376 TEST_F( WatchTowerDirectories, FollowThreeFilesInTwoDirs ) {
377     registration_two   = registerFile( second_file_name );
378     registration_three = registerFile( third_file_name );
379 
380     ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) );
381 }
382 
383 TEST_F( WatchTowerDirectories, FollowTwoFilesInTwoDirs ) {
384     registration_two   = registerFile( second_file_name );
385     {
386         auto temp_registration_three = registerFile( third_file_name );
387     }
388 
389     ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) );
390 }
391 
392 TEST_F( WatchTowerDirectories, FollowOneFileInOneDir ) {
393     {
394         auto temp_registration_two   = registerFile( second_file_name );
395         auto temp_registration_three = registerFile( third_file_name );
396 
397         ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 2 ) );
398     }
399 
400     ASSERT_THAT( watch_tower->numberWatchedDirectories(), Eq( 1 ) );
401 }
402 
403 /*****/
404 
405 class WatchTowerInexistantDirectory: public WatchTowerDirectories {
406   public:
407     WatchTowerInexistantDirectory() {
408         test_dir = createTempDir();
409         rmdir( test_dir.c_str() );
410     }
411 
412     ~WatchTowerInexistantDirectory() {
413         rmdir( test_dir.c_str() );
414     }
415 
416     string test_dir;
417 };
418 
419 TEST_F( WatchTowerInexistantDirectory, LaterCreatedDirIsFollowed ) {
420     /* Dir (and file) don't exist */
421     auto file_name = createTempEmptyFileInDir( test_dir );
422     {
423         auto registration = registerFile( file_name );
424 
425 #ifdef _WIN32
426         mkdir( test_dir.c_str() );
427 #else
428         mkdir( test_dir.c_str(), 0777 );
429 #endif
430         createTempEmptyFile( file_name );
431     }
432 
433     auto registration2 = registerFile( file_name );
434 
435     appendDataToFile( file_name );
436     ASSERT_TRUE( waitNotificationReceived() );
437 
438     remove( file_name.c_str() );
439 }
440 
441 /*****/
442 
443 #ifdef _WIN32
444 class WinNotificationInfoListTest : public testing::Test {
445   public:
446     using Action = WinNotificationInfo::Action;
447 
448     struct Buffer {
449         uint32_t next_addr;
450         uint32_t action;
451         uint32_t filename_length;
452         wchar_t filename[13];
453     };
454     static struct Buffer buffer[2];
455 
456     WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) };
457     WinNotificationInfoList::iterator iterator { std::begin( list ) };
458 };
459 
460 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] =
461     { { 40, 1, 26, L"Filename.txt" },
462       { 0, 3, 18, L"file2.txt" } };
463 
464 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) {
465     auto notification = *iterator;
466     ASSERT_THAT( &notification, NotNull() );
467 }
468 
469 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) {
470     ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) );
471 }
472 
473 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) {
474     ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) );
475 }
476 
477 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) {
478     ++iterator;
479     auto notification = *iterator;
480     ASSERT_THAT( &notification, NotNull() );
481 }
482 
483 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) {
484     iterator++;
485     ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) );
486     ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) );
487 }
488 
489 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) {
490     for ( auto notification : list ) {
491         notification.action();
492     }
493 }
494 #endif
495