xref: /glogg/tests/watchtowerTest.cpp (revision 87e056522fcbb3c5f21f5154425e1df944f6954c)
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 #ifdef _WIN32
15 #  include "winwatchtower.h"
16 #else
17 #  include "inotifywatchtower.h"
18 #endif
19 
20 using namespace std;
21 using namespace testing;
22 
23 class WatchTowerBehaviour: public testing::Test {
24   public:
25 #ifdef _WIN32
26     shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>();
27 #else
28     shared_ptr<WatchTower> watch_tower = make_shared<INotifyWatchTower>();
29 #endif
30 
31     string createTempEmptyFile( string file_name = "" ) {
32         const char* name;
33 
34         if ( ! file_name.empty() ) {
35             name = file_name.c_str();
36         }
37         else {
38             // I know tmpnam is bad but I need control over the file
39             // and it is the only one which exits on Windows.
40             name = tmpnam( nullptr );
41         }
42         int fd = creat( name, S_IRUSR | S_IWUSR );
43         close( fd );
44 
45         return string( name );
46     }
47 
48     string getNonExistingFileName() {
49         return string( tmpnam( nullptr ) );
50     }
51 };
52 
53 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) {
54     auto file_name = createTempEmptyFile();
55     auto registration = watch_tower->addFile( file_name, [] (void) { } );
56 }
57 
58 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) {
59     auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } );
60 }
61 
62 /*****/
63 
64 class WatchTowerSingleFile: public WatchTowerBehaviour {
65   public:
66     static const int TIMEOUT;
67 
68     string file_name;
69     WatchTower::Registration registration;
70 
71     mutex mutex_;
72     condition_variable cv_;
73     int notification_received = 0;
74 
75     WatchTower::Registration registerFile( const string& file_name ) {
76         weak_ptr<void> weakHeartbeat( heartbeat_ );
77 
78         auto reg = watch_tower->addFile( file_name, [this, weakHeartbeat] (void) {
79             // Ensure the fixture object is still alive using the heartbeat
80             if ( auto keep = weakHeartbeat.lock() ) {
81                 unique_lock<mutex> lock(mutex_);
82                 ++notification_received;
83                 cv_.notify_one();
84             } } );
85 
86         return reg;
87     }
88 
89     WatchTowerSingleFile() : WatchTowerBehaviour(),
90         heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) )
91     { }
92 
93     void SetUp() override {
94         file_name = createTempEmptyFile();
95         registration = registerFile( file_name );
96     }
97 
98     bool waitNotificationReceived( int number = 1 ) {
99         unique_lock<mutex> lock(mutex_);
100         bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT),
101                 [this, number] { return notification_received >= number; } ) );
102 
103         // Reinit the notification
104         notification_received = 0;
105 
106         return result;
107     }
108 
109     void appendDataToFile( const string& file_name ) {
110         static const char* string = "Test line\n";
111         int fd = open( file_name.c_str(), O_WRONLY | O_APPEND );
112         write( fd, (void*) string, strlen( string ) );
113         close( fd );
114     }
115 
116     void TearDown() override {
117         remove( file_name.c_str() );
118     }
119 
120   private:
121     // Heartbeat ensures the object is still alive
122     shared_ptr<void> heartbeat_;
123 };
124 
125 const int WatchTowerSingleFile::TIMEOUT = 20;
126 
127 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) {
128     appendDataToFile( file_name );
129     ASSERT_TRUE( waitNotificationReceived() );
130 }
131 
132 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) {
133     remove( file_name.c_str() );
134     ASSERT_TRUE( waitNotificationReceived() );
135 }
136 
137 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) {
138     remove( file_name.c_str() );
139     waitNotificationReceived();
140     createTempEmptyFile( file_name );
141     ASSERT_TRUE( waitNotificationReceived() );
142 }
143 
144 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
145     auto second_file_name = createTempEmptyFile();
146     {
147         auto second_registration = registerFile( second_file_name );
148         appendDataToFile( second_file_name );
149         ASSERT_TRUE( waitNotificationReceived() );
150     }
151 
152     // The registration will be removed here.
153     appendDataToFile( second_file_name );
154     ASSERT_FALSE( waitNotificationReceived() );
155 
156     remove( second_file_name.c_str() );
157 }
158 
159 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) {
160     auto second_registration = registerFile( file_name );
161     appendDataToFile( file_name );
162 
163     ASSERT_TRUE( waitNotificationReceived( 2 ) );
164 }
165 
166 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) {
167     {
168         auto second_registration = registerFile( file_name );
169     }
170 
171     appendDataToFile( file_name );
172     ASSERT_TRUE( waitNotificationReceived( 1 ) );
173 }
174 
175 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) {
176     auto new_file_name = createTempEmptyFile();
177     remove( new_file_name.c_str() );
178 
179     rename( file_name.c_str(), new_file_name.c_str() );
180     ASSERT_TRUE( waitNotificationReceived() );
181 
182     rename( new_file_name.c_str(), file_name.c_str() );
183 }
184 
185 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) {
186     remove( file_name.c_str() );
187     waitNotificationReceived();
188 
189     auto new_file_name = createTempEmptyFile();
190     appendDataToFile( new_file_name );
191 
192     rename( new_file_name.c_str(), file_name.c_str() );
193     ASSERT_TRUE( waitNotificationReceived() );
194 }
195 
196 /*****/
197 
198 #ifdef HAVE_SYMLINK
199 class WatchTowerSymlink: public WatchTowerSingleFile {
200   public:
201     string symlink_name;
202 
203     void SetUp() override {
204         file_name = createTempEmptyFile();
205         symlink_name = createTempEmptyFile();
206         remove( symlink_name.c_str() );
207         symlink( file_name.c_str(), symlink_name.c_str() );
208 
209         registration = registerFile( symlink_name );
210     }
211 
212     void TearDown() override {
213         remove( symlink_name.c_str() );
214         remove( file_name.c_str() );
215     }
216 };
217 
218 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
219     appendDataToFile( symlink_name );
220     ASSERT_TRUE( waitNotificationReceived() );
221 }
222 
223 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
224     appendDataToFile( file_name );
225     ASSERT_TRUE( waitNotificationReceived() );
226 }
227 
228 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
229     remove( symlink_name.c_str() );
230     ASSERT_TRUE( waitNotificationReceived() );
231 }
232 
233 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
234     remove( file_name.c_str() );
235     ASSERT_TRUE( waitNotificationReceived() );
236 }
237 
238 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
239     auto new_target = createTempEmptyFile();
240     remove( symlink_name.c_str() );
241     waitNotificationReceived();
242 
243     symlink( new_target.c_str(), symlink_name.c_str() );
244     ASSERT_TRUE( waitNotificationReceived() );
245 
246     remove( new_target.c_str() );
247 }
248 #endif //HAVE_SYMLINK
249 
250 /*****/
251 
252 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
253 #if 0
254     auto mortal_watch_tower = new INotifyWatchTower();
255     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
256 
257     delete mortal_watch_tower;
258     // reg will be destroyed after the watch_tower
259 #endif
260 }
261