xref: /glogg/tests/watchtowerTest.cpp (revision 87e056522fcbb3c5f21f5154425e1df944f6954c)
184af0c9bSNicolas Bonnefon #include "gmock/gmock.h"
284af0c9bSNicolas Bonnefon 
3*87e05652SNicolas Bonnefon #include "config.h"
4*87e05652SNicolas Bonnefon 
5b827fa8eSNicolas Bonnefon #include <cstdio>
684af0c9bSNicolas Bonnefon #include <fcntl.h>
784af0c9bSNicolas Bonnefon 
884af0c9bSNicolas Bonnefon #include <memory>
984af0c9bSNicolas Bonnefon #include <condition_variable>
1084af0c9bSNicolas Bonnefon #include <thread>
1184af0c9bSNicolas Bonnefon #include <mutex>
1284af0c9bSNicolas Bonnefon #include <chrono>
1384af0c9bSNicolas Bonnefon 
14*87e05652SNicolas Bonnefon #ifdef _WIN32
15*87e05652SNicolas Bonnefon #  include "winwatchtower.h"
16*87e05652SNicolas Bonnefon #else
1784af0c9bSNicolas Bonnefon #  include "inotifywatchtower.h"
18*87e05652SNicolas Bonnefon #endif
1984af0c9bSNicolas Bonnefon 
2084af0c9bSNicolas Bonnefon using namespace std;
2184af0c9bSNicolas Bonnefon using namespace testing;
2284af0c9bSNicolas Bonnefon 
2384af0c9bSNicolas Bonnefon class WatchTowerBehaviour: public testing::Test {
2484af0c9bSNicolas Bonnefon   public:
2584af0c9bSNicolas Bonnefon #ifdef _WIN32
26*87e05652SNicolas Bonnefon     shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>();
27*87e05652SNicolas Bonnefon #else
28*87e05652SNicolas Bonnefon     shared_ptr<WatchTower> watch_tower = make_shared<INotifyWatchTower>();
2984af0c9bSNicolas Bonnefon #endif
3084af0c9bSNicolas Bonnefon 
31b827fa8eSNicolas Bonnefon     string createTempEmptyFile( string file_name = "" ) {
32b827fa8eSNicolas Bonnefon         const char* name;
33b827fa8eSNicolas Bonnefon 
34b827fa8eSNicolas Bonnefon         if ( ! file_name.empty() ) {
35b827fa8eSNicolas Bonnefon             name = file_name.c_str();
36b827fa8eSNicolas Bonnefon         }
37b827fa8eSNicolas Bonnefon         else {
3884af0c9bSNicolas Bonnefon             // I know tmpnam is bad but I need control over the file
3984af0c9bSNicolas Bonnefon             // and it is the only one which exits on Windows.
40b827fa8eSNicolas Bonnefon             name = tmpnam( nullptr );
41b827fa8eSNicolas Bonnefon         }
42b827fa8eSNicolas Bonnefon         int fd = creat( name, S_IRUSR | S_IWUSR );
4384af0c9bSNicolas Bonnefon         close( fd );
4484af0c9bSNicolas Bonnefon 
45b827fa8eSNicolas Bonnefon         return string( name );
4684af0c9bSNicolas Bonnefon     }
4784af0c9bSNicolas Bonnefon 
4884af0c9bSNicolas Bonnefon     string getNonExistingFileName() {
4984af0c9bSNicolas Bonnefon         return string( tmpnam( nullptr ) );
5084af0c9bSNicolas Bonnefon     }
5184af0c9bSNicolas Bonnefon };
5284af0c9bSNicolas Bonnefon 
5384af0c9bSNicolas Bonnefon TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) {
5484af0c9bSNicolas Bonnefon     auto file_name = createTempEmptyFile();
55*87e05652SNicolas Bonnefon     auto registration = watch_tower->addFile( file_name, [] (void) { } );
5684af0c9bSNicolas Bonnefon }
5784af0c9bSNicolas Bonnefon 
5884af0c9bSNicolas Bonnefon TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) {
59*87e05652SNicolas Bonnefon     auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } );
6084af0c9bSNicolas Bonnefon }
6184af0c9bSNicolas Bonnefon 
62b827fa8eSNicolas Bonnefon /*****/
63b827fa8eSNicolas Bonnefon 
6484af0c9bSNicolas Bonnefon class WatchTowerSingleFile: public WatchTowerBehaviour {
6584af0c9bSNicolas Bonnefon   public:
66b827fa8eSNicolas Bonnefon     static const int TIMEOUT;
6784af0c9bSNicolas Bonnefon 
6884af0c9bSNicolas Bonnefon     string file_name;
69*87e05652SNicolas Bonnefon     WatchTower::Registration registration;
7084af0c9bSNicolas Bonnefon 
7184af0c9bSNicolas Bonnefon     mutex mutex_;
7284af0c9bSNicolas Bonnefon     condition_variable cv_;
738f031b5aSNicolas Bonnefon     int notification_received = 0;
7484af0c9bSNicolas Bonnefon 
75*87e05652SNicolas Bonnefon     WatchTower::Registration registerFile( const string& file_name ) {
76b827fa8eSNicolas Bonnefon         weak_ptr<void> weakHeartbeat( heartbeat_ );
77b827fa8eSNicolas Bonnefon 
78*87e05652SNicolas Bonnefon         auto reg = watch_tower->addFile( file_name, [this, weakHeartbeat] (void) {
79b827fa8eSNicolas Bonnefon             // Ensure the fixture object is still alive using the heartbeat
80b827fa8eSNicolas Bonnefon             if ( auto keep = weakHeartbeat.lock() ) {
8184af0c9bSNicolas Bonnefon                 unique_lock<mutex> lock(mutex_);
828f031b5aSNicolas Bonnefon                 ++notification_received;
83b827fa8eSNicolas Bonnefon                 cv_.notify_one();
84b827fa8eSNicolas Bonnefon             } } );
85b827fa8eSNicolas Bonnefon 
86b827fa8eSNicolas Bonnefon         return reg;
87b827fa8eSNicolas Bonnefon     }
88b827fa8eSNicolas Bonnefon 
89b827fa8eSNicolas Bonnefon     WatchTowerSingleFile() : WatchTowerBehaviour(),
90b827fa8eSNicolas Bonnefon         heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) )
91b827fa8eSNicolas Bonnefon     { }
92b827fa8eSNicolas Bonnefon 
93b827fa8eSNicolas Bonnefon     void SetUp() override {
94b827fa8eSNicolas Bonnefon         file_name = createTempEmptyFile();
95b827fa8eSNicolas Bonnefon         registration = registerFile( file_name );
9684af0c9bSNicolas Bonnefon     }
9784af0c9bSNicolas Bonnefon 
988f031b5aSNicolas Bonnefon     bool waitNotificationReceived( int number = 1 ) {
9984af0c9bSNicolas Bonnefon         unique_lock<mutex> lock(mutex_);
1008f031b5aSNicolas Bonnefon         bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT),
1018f031b5aSNicolas Bonnefon                 [this, number] { return notification_received >= number; } ) );
102b827fa8eSNicolas Bonnefon 
103b827fa8eSNicolas Bonnefon         // Reinit the notification
1048f031b5aSNicolas Bonnefon         notification_received = 0;
105b827fa8eSNicolas Bonnefon 
1068f031b5aSNicolas Bonnefon         return result;
10784af0c9bSNicolas Bonnefon     }
10884af0c9bSNicolas Bonnefon 
10984af0c9bSNicolas Bonnefon     void appendDataToFile( const string& file_name ) {
11084af0c9bSNicolas Bonnefon         static const char* string = "Test line\n";
11184af0c9bSNicolas Bonnefon         int fd = open( file_name.c_str(), O_WRONLY | O_APPEND );
11284af0c9bSNicolas Bonnefon         write( fd, (void*) string, strlen( string ) );
11384af0c9bSNicolas Bonnefon         close( fd );
11484af0c9bSNicolas Bonnefon     }
115b827fa8eSNicolas Bonnefon 
116b827fa8eSNicolas Bonnefon     void TearDown() override {
117b827fa8eSNicolas Bonnefon         remove( file_name.c_str() );
118b827fa8eSNicolas Bonnefon     }
119b827fa8eSNicolas Bonnefon 
120b827fa8eSNicolas Bonnefon   private:
121b827fa8eSNicolas Bonnefon     // Heartbeat ensures the object is still alive
122b827fa8eSNicolas Bonnefon     shared_ptr<void> heartbeat_;
12384af0c9bSNicolas Bonnefon };
12484af0c9bSNicolas Bonnefon 
125b827fa8eSNicolas Bonnefon const int WatchTowerSingleFile::TIMEOUT = 20;
126b827fa8eSNicolas Bonnefon 
12784af0c9bSNicolas Bonnefon TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) {
12884af0c9bSNicolas Bonnefon     appendDataToFile( file_name );
12984af0c9bSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
13084af0c9bSNicolas Bonnefon }
13184af0c9bSNicolas Bonnefon 
132b827fa8eSNicolas Bonnefon TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) {
13384af0c9bSNicolas Bonnefon     remove( file_name.c_str() );
13484af0c9bSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
13584af0c9bSNicolas Bonnefon }
136b827fa8eSNicolas Bonnefon 
137b827fa8eSNicolas Bonnefon TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) {
138b827fa8eSNicolas Bonnefon     remove( file_name.c_str() );
139b827fa8eSNicolas Bonnefon     waitNotificationReceived();
140b827fa8eSNicolas Bonnefon     createTempEmptyFile( file_name );
141b827fa8eSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
142b827fa8eSNicolas Bonnefon }
143b827fa8eSNicolas Bonnefon 
144b827fa8eSNicolas Bonnefon TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
145b827fa8eSNicolas Bonnefon     auto second_file_name = createTempEmptyFile();
146b827fa8eSNicolas Bonnefon     {
147b827fa8eSNicolas Bonnefon         auto second_registration = registerFile( second_file_name );
148b827fa8eSNicolas Bonnefon         appendDataToFile( second_file_name );
149b827fa8eSNicolas Bonnefon         ASSERT_TRUE( waitNotificationReceived() );
150b827fa8eSNicolas Bonnefon     }
151b827fa8eSNicolas Bonnefon 
152b827fa8eSNicolas Bonnefon     // The registration will be removed here.
153b827fa8eSNicolas Bonnefon     appendDataToFile( second_file_name );
154b827fa8eSNicolas Bonnefon     ASSERT_FALSE( waitNotificationReceived() );
155b827fa8eSNicolas Bonnefon 
156b827fa8eSNicolas Bonnefon     remove( second_file_name.c_str() );
157b827fa8eSNicolas Bonnefon }
158b827fa8eSNicolas Bonnefon 
1598f031b5aSNicolas Bonnefon TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) {
1608f031b5aSNicolas Bonnefon     auto second_registration = registerFile( file_name );
1618f031b5aSNicolas Bonnefon     appendDataToFile( file_name );
1628f031b5aSNicolas Bonnefon 
1638f031b5aSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived( 2 ) );
1648f031b5aSNicolas Bonnefon }
1658f031b5aSNicolas Bonnefon 
1668f031b5aSNicolas Bonnefon TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) {
1678f031b5aSNicolas Bonnefon     {
1688f031b5aSNicolas Bonnefon         auto second_registration = registerFile( file_name );
1698f031b5aSNicolas Bonnefon     }
1708f031b5aSNicolas Bonnefon 
1718f031b5aSNicolas Bonnefon     appendDataToFile( file_name );
1728f031b5aSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived( 1 ) );
1738f031b5aSNicolas Bonnefon }
1748f031b5aSNicolas Bonnefon 
1758f031b5aSNicolas Bonnefon TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) {
1768f031b5aSNicolas Bonnefon     auto new_file_name = createTempEmptyFile();
1778f031b5aSNicolas Bonnefon     remove( new_file_name.c_str() );
1788f031b5aSNicolas Bonnefon 
1798f031b5aSNicolas Bonnefon     rename( file_name.c_str(), new_file_name.c_str() );
1808f031b5aSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
1818f031b5aSNicolas Bonnefon 
1828f031b5aSNicolas Bonnefon     rename( new_file_name.c_str(), file_name.c_str() );
1838f031b5aSNicolas Bonnefon }
1848f031b5aSNicolas Bonnefon 
1858f031b5aSNicolas Bonnefon TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) {
1868f031b5aSNicolas Bonnefon     remove( file_name.c_str() );
1878f031b5aSNicolas Bonnefon     waitNotificationReceived();
1888f031b5aSNicolas Bonnefon 
1898f031b5aSNicolas Bonnefon     auto new_file_name = createTempEmptyFile();
1908f031b5aSNicolas Bonnefon     appendDataToFile( new_file_name );
1918f031b5aSNicolas Bonnefon 
1928f031b5aSNicolas Bonnefon     rename( new_file_name.c_str(), file_name.c_str() );
1938f031b5aSNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
1948f031b5aSNicolas Bonnefon }
1958f031b5aSNicolas Bonnefon 
1968f031b5aSNicolas Bonnefon /*****/
1978f031b5aSNicolas Bonnefon 
198*87e05652SNicolas Bonnefon #ifdef HAVE_SYMLINK
1993af68c64SNicolas Bonnefon class WatchTowerSymlink: public WatchTowerSingleFile {
2003af68c64SNicolas Bonnefon   public:
2013af68c64SNicolas Bonnefon     string symlink_name;
2023af68c64SNicolas Bonnefon 
2033af68c64SNicolas Bonnefon     void SetUp() override {
2043af68c64SNicolas Bonnefon         file_name = createTempEmptyFile();
2053af68c64SNicolas Bonnefon         symlink_name = createTempEmptyFile();
2063af68c64SNicolas Bonnefon         remove( symlink_name.c_str() );
2073af68c64SNicolas Bonnefon         symlink( file_name.c_str(), symlink_name.c_str() );
2083af68c64SNicolas Bonnefon 
2093af68c64SNicolas Bonnefon         registration = registerFile( symlink_name );
2103af68c64SNicolas Bonnefon     }
2113af68c64SNicolas Bonnefon 
2123af68c64SNicolas Bonnefon     void TearDown() override {
2133af68c64SNicolas Bonnefon         remove( symlink_name.c_str() );
2143af68c64SNicolas Bonnefon         remove( file_name.c_str() );
2153af68c64SNicolas Bonnefon     }
2163af68c64SNicolas Bonnefon };
2173af68c64SNicolas Bonnefon 
2183af68c64SNicolas Bonnefon TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
2193af68c64SNicolas Bonnefon     appendDataToFile( symlink_name );
2203af68c64SNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
2213af68c64SNicolas Bonnefon }
2223af68c64SNicolas Bonnefon 
2233af68c64SNicolas Bonnefon TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
2243af68c64SNicolas Bonnefon     appendDataToFile( file_name );
2253af68c64SNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
2263af68c64SNicolas Bonnefon }
2273af68c64SNicolas Bonnefon 
2283af68c64SNicolas Bonnefon TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
2293af68c64SNicolas Bonnefon     remove( symlink_name.c_str() );
2303af68c64SNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
2313af68c64SNicolas Bonnefon }
2323af68c64SNicolas Bonnefon 
2333af68c64SNicolas Bonnefon TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
2343af68c64SNicolas Bonnefon     remove( file_name.c_str() );
2353af68c64SNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
2363af68c64SNicolas Bonnefon }
2373af68c64SNicolas Bonnefon 
2383af68c64SNicolas Bonnefon TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
2393af68c64SNicolas Bonnefon     auto new_target = createTempEmptyFile();
2403af68c64SNicolas Bonnefon     remove( symlink_name.c_str() );
2413af68c64SNicolas Bonnefon     waitNotificationReceived();
2423af68c64SNicolas Bonnefon 
2433af68c64SNicolas Bonnefon     symlink( new_target.c_str(), symlink_name.c_str() );
2443af68c64SNicolas Bonnefon     ASSERT_TRUE( waitNotificationReceived() );
2453af68c64SNicolas Bonnefon 
2463af68c64SNicolas Bonnefon     remove( new_target.c_str() );
2473af68c64SNicolas Bonnefon }
248*87e05652SNicolas Bonnefon #endif //HAVE_SYMLINK
2493af68c64SNicolas Bonnefon 
2503af68c64SNicolas Bonnefon /*****/
2513af68c64SNicolas Bonnefon 
252b827fa8eSNicolas Bonnefon TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
253*87e05652SNicolas Bonnefon #if 0
254b827fa8eSNicolas Bonnefon     auto mortal_watch_tower = new INotifyWatchTower();
255b827fa8eSNicolas Bonnefon     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
256b827fa8eSNicolas Bonnefon 
257b827fa8eSNicolas Bonnefon     delete mortal_watch_tower;
2588f031b5aSNicolas Bonnefon     // reg will be destroyed after the watch_tower
259*87e05652SNicolas Bonnefon #endif
260b827fa8eSNicolas Bonnefon }
261