xref: /glogg/tests/watchtowerTest.cpp (revision af7adfddf26a3cbde5334934ffd95345d461716f)
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 #ifdef _WIN32
24 #if 0
25 TEST( WatchTowerBase, NotifyAFile ) {
26     int fd = creat( "c:\\test", S_IRUSR | S_IWUSR );
27     close( fd );
28 
29     auto watch_tower = make_shared<WinWatchTower>();
30     std::this_thread::sleep_for( std::chrono::milliseconds(1000) );
31     {
32         auto registration = watch_tower->addFile( "c:\\test", [] {} );
33 
34         std::this_thread::sleep_for( std::chrono::milliseconds(5000) );
35 
36         static const char* string = "Test line\n";
37         fd = open( "c:\\test", O_WRONLY | O_APPEND );
38         write( fd, (void*) string, strlen( string ) );
39         close( fd );
40 
41         std::this_thread::sleep_for( std::chrono::milliseconds(10000) );
42     }
43 }
44 #endif
45 #endif
46 
47 class WatchTowerBehaviour: public testing::Test {
48   public:
49 #ifdef _WIN32
50     shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>();
51 #else
52     shared_ptr<WatchTower> watch_tower = make_shared<INotifyWatchTower>();
53 #endif
54 
55     string createTempEmptyFile( string file_name = "" ) {
56         const char* name;
57 
58         if ( ! file_name.empty() ) {
59             name = file_name.c_str();
60         }
61         else {
62             // I know tmpnam is bad but I need control over the file
63             // and it is the only one which exits on Windows.
64 #if _WIN32
65             name = _tempnam( "c:\\temp", "glogg_test" );
66 #else
67             name = tmpnam( nullptr );
68 #endif
69         }
70         int fd = creat( name, S_IRUSR | S_IWUSR );
71         close( fd );
72 
73         return string( name );
74     }
75 
76     string getNonExistingFileName() {
77         return string( tmpnam( nullptr ) );
78     }
79 };
80 
81 #if 0
82 TEST_F( WatchTowerBehaviour, AcceptsAnExistingFileToWatch ) {
83     auto file_name = createTempEmptyFile();
84     auto registration = watch_tower->addFile( file_name, [] (void) { } );
85 }
86 
87 TEST_F( WatchTowerBehaviour, AcceptsANonExistingFileToWatch ) {
88     auto registration = watch_tower->addFile( getNonExistingFileName(), [] (void) { } );
89 }
90 #endif
91 
92 /*****/
93 
94 class WatchTowerSingleFile: public WatchTowerBehaviour {
95   public:
96     static const int TIMEOUT;
97 
98     string file_name;
99     WatchTower::Registration registration;
100 
101     mutex mutex_;
102     condition_variable cv_;
103     int notification_received = 0;
104 
105     WatchTower::Registration registerFile( const string& file_name ) {
106         weak_ptr<void> weakHeartbeat( heartbeat_ );
107 
108         auto reg = watch_tower->addFile( file_name, [this, weakHeartbeat] (void) {
109             // Ensure the fixture object is still alive using the heartbeat
110             if ( auto keep = weakHeartbeat.lock() ) {
111                 unique_lock<mutex> lock(mutex_);
112                 ++notification_received;
113                 cv_.notify_one();
114             } } );
115 
116         return reg;
117     }
118 
119     WatchTowerSingleFile() : WatchTowerBehaviour(),
120         heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) )
121     { }
122 
123     void SetUp() override {
124         file_name = createTempEmptyFile();
125         registration = registerFile( file_name );
126     }
127 
128     bool waitNotificationReceived( int number = 1 ) {
129         unique_lock<mutex> lock(mutex_);
130         bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT),
131                 [this, number] { return notification_received >= number; } ) );
132 
133         // Reinit the notification
134         notification_received = 0;
135 
136         return result;
137     }
138 
139     void appendDataToFile( const string& file_name ) {
140         static const char* string = "Test line\n";
141         int fd = open( file_name.c_str(), O_WRONLY | O_APPEND );
142         write( fd, (void*) string, strlen( string ) );
143         close( fd );
144     }
145 
146     void TearDown() override {
147         remove( file_name.c_str() );
148     }
149 
150   private:
151     // Heartbeat ensures the object is still alive
152     shared_ptr<void> heartbeat_;
153 };
154 
155 #ifdef _WIN32
156 const int WatchTowerSingleFile::TIMEOUT = 20000;
157 #else
158 const int WatchTowerSingleFile::TIMEOUT = 20;
159 #endif
160 
161 #include "log.h"
162 
163 TEST_F( WatchTowerSingleFile, DISABLED_SignalsWhenAWatchedFileIsAppended ) {
164     appendDataToFile( file_name );
165     ASSERT_TRUE( waitNotificationReceived() );
166 }
167 
168 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) {
169     remove( file_name.c_str() );
170     ASSERT_TRUE( waitNotificationReceived() );
171 }
172 
173 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) {
174     remove( file_name.c_str() );
175     waitNotificationReceived();
176     createTempEmptyFile( file_name );
177     ASSERT_TRUE( waitNotificationReceived() );
178 }
179 
180 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
181     auto second_file_name = createTempEmptyFile();
182     {
183         auto second_registration = registerFile( second_file_name );
184         appendDataToFile( second_file_name );
185         ASSERT_TRUE( waitNotificationReceived() );
186     }
187 
188     // The registration will be removed here.
189     appendDataToFile( second_file_name );
190     ASSERT_FALSE( waitNotificationReceived() );
191 
192     remove( second_file_name.c_str() );
193 }
194 
195 TEST_F( WatchTowerSingleFile, DISABLED_TwoWatchesOnSameFileYieldsTwoNotifications ) {
196     auto second_registration = registerFile( file_name );
197     appendDataToFile( file_name );
198 
199     ASSERT_TRUE( waitNotificationReceived( 2 ) );
200 }
201 
202 TEST_F( WatchTowerSingleFile, DISABLED_RemovingOneWatchOfTwoStillYieldsOneNotification ) {
203     {
204         auto second_registration = registerFile( file_name );
205     }
206 
207     appendDataToFile( file_name );
208     ASSERT_TRUE( waitNotificationReceived( 1 ) );
209 }
210 
211 TEST_F( WatchTowerSingleFile, DISABLED_RenamingTheFileYieldsANotification ) {
212     auto new_file_name = createTempEmptyFile();
213     remove( new_file_name.c_str() );
214 
215     rename( file_name.c_str(), new_file_name.c_str() );
216     ASSERT_TRUE( waitNotificationReceived() );
217 
218     rename( new_file_name.c_str(), file_name.c_str() );
219 }
220 
221 TEST_F( WatchTowerSingleFile, DISABLED_RenamingAFileToTheWatchedNameYieldsANotification ) {
222     remove( file_name.c_str() );
223     waitNotificationReceived();
224 
225     auto new_file_name = createTempEmptyFile();
226     appendDataToFile( new_file_name );
227 
228     rename( new_file_name.c_str(), file_name.c_str() );
229     ASSERT_TRUE( waitNotificationReceived() );
230 }
231 
232 /*****/
233 
234 #ifdef HAVE_SYMLINK
235 class WatchTowerSymlink: public WatchTowerSingleFile {
236   public:
237     string symlink_name;
238 
239     void SetUp() override {
240         file_name = createTempEmptyFile();
241         symlink_name = createTempEmptyFile();
242         remove( symlink_name.c_str() );
243         symlink( file_name.c_str(), symlink_name.c_str() );
244 
245         registration = registerFile( symlink_name );
246     }
247 
248     void TearDown() override {
249         remove( symlink_name.c_str() );
250         remove( file_name.c_str() );
251     }
252 };
253 
254 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
255     appendDataToFile( symlink_name );
256     ASSERT_TRUE( waitNotificationReceived() );
257 }
258 
259 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
260     appendDataToFile( file_name );
261     ASSERT_TRUE( waitNotificationReceived() );
262 }
263 
264 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
265     remove( symlink_name.c_str() );
266     ASSERT_TRUE( waitNotificationReceived() );
267 }
268 
269 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
270     remove( file_name.c_str() );
271     ASSERT_TRUE( waitNotificationReceived() );
272 }
273 
274 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
275     auto new_target = createTempEmptyFile();
276     remove( symlink_name.c_str() );
277     waitNotificationReceived();
278 
279     symlink( new_target.c_str(), symlink_name.c_str() );
280     ASSERT_TRUE( waitNotificationReceived() );
281 
282     remove( new_target.c_str() );
283 }
284 #endif //HAVE_SYMLINK
285 
286 /*****/
287 
288 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
289 #if 0
290     auto mortal_watch_tower = new INotifyWatchTower();
291     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
292 
293     delete mortal_watch_tower;
294     // reg will be destroyed after the watch_tower
295 #endif
296 }
297 
298 /*****/
299 
300 class WinNotificationInfoListTest : public testing::Test {
301   public:
302     using Action = WinNotificationInfo::Action;
303 
304     struct Buffer {
305         uint32_t next_addr;
306         uint32_t action;
307         uint32_t filename_length;
308         wchar_t filename[13];
309     };
310     static struct Buffer buffer[2];
311 
312     WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) };
313     WinNotificationInfoList::iterator iterator { std::begin( list ) };
314 };
315 
316 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] =
317     { { 40, 1, 26, L"Filename.txt" },
318       { 0, 3, 18, L"file2.txt" } };
319 
320 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) {
321     auto notification = *iterator;
322     ASSERT_THAT( &notification, NotNull() );
323 }
324 
325 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) {
326     ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) );
327 }
328 
329 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) {
330     ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) );
331 }
332 
333 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) {
334     ++iterator;
335     auto notification = *iterator;
336     ASSERT_THAT( &notification, NotNull() );
337 }
338 
339 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) {
340     iterator++;
341     ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) );
342     ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) );
343 }
344 
345 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) {
346     for ( auto notification : list ) {
347         notification.action();
348     }
349 }
350