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( ¬ification, 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( ¬ification, 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