xref: /glogg/tests/watchtowerTest.cpp (revision f09fa65124f80be4a92fab17a1cccc63d18936a5)
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     string file_name;
91     Registration registration;
92 
93     mutex mutex_;
94     condition_variable cv_;
95     int notification_received = 0;
96 
97     Registration registerFile( const string& filename ) {
98         weak_ptr<void> weakHeartbeat( heartbeat_ );
99 
100         auto reg = watch_tower->addFile( filename, [this, weakHeartbeat] (void) {
101             // Ensure the fixture object is still alive using the heartbeat
102             if ( auto keep = weakHeartbeat.lock() ) {
103                 unique_lock<mutex> lock(mutex_);
104                 ++notification_received;
105                 cv_.notify_one();
106             } } );
107 
108         return reg;
109     }
110 
111     WatchTowerSingleFile()
112         : heartbeat_( shared_ptr<void>( (void*) 0xDEADC0DE, [] (void*) {} ) )
113     {
114         file_name = createTempEmptyFile();
115         registration = registerFile( file_name );
116     }
117 
118     ~WatchTowerSingleFile() {
119         remove( file_name.c_str() );
120     }
121 
122     bool waitNotificationReceived( int number = 1 ) {
123         unique_lock<mutex> lock(mutex_);
124         bool result = ( cv_.wait_for( lock, std::chrono::milliseconds(TIMEOUT),
125                 [this, number] { return notification_received >= number; } ) );
126 
127         // Reinit the notification
128         notification_received = 0;
129 
130         return result;
131     }
132 
133     void appendDataToFile( const string& file_name ) {
134         static const char* string = "Test line\n";
135         int fd = open( file_name.c_str(), O_WRONLY | O_APPEND );
136         write( fd, (void*) string, strlen( string ) );
137         close( fd );
138     }
139 
140   private:
141     // Heartbeat ensures the object is still alive
142     shared_ptr<void> heartbeat_;
143 };
144 
145 #ifdef _WIN32
146 const int WatchTowerSingleFile::TIMEOUT = 1000;
147 #else
148 const int WatchTowerSingleFile::TIMEOUT = 20;
149 #endif
150 
151 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) {
152     appendDataToFile( file_name );
153     ASSERT_TRUE( waitNotificationReceived() );
154 }
155 
156 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) {
157     remove( file_name.c_str() );
158     ASSERT_TRUE( waitNotificationReceived() );
159 }
160 
161 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) {
162     remove( file_name.c_str() );
163     waitNotificationReceived();
164     createTempEmptyFile( file_name );
165     ASSERT_TRUE( waitNotificationReceived() );
166 }
167 
168 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
169     auto second_file_name = createTempEmptyFile();
170     {
171         auto second_registration = registerFile( second_file_name );
172         appendDataToFile( second_file_name );
173         ASSERT_TRUE( waitNotificationReceived() );
174     }
175 
176     // The registration will be removed here.
177     appendDataToFile( second_file_name );
178     ASSERT_FALSE( waitNotificationReceived() );
179 
180     remove( second_file_name.c_str() );
181 }
182 
183 TEST_F( WatchTowerSingleFile, SignalsWhenSameFileIsFollowedMultipleTimes ) {
184     auto second_file_name = createTempEmptyFile();
185 
186     for ( int i = 0; i < 1000; i++ )
187     {
188         std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
189         auto second_registration = registerFile( second_file_name );
190         appendDataToFile( second_file_name );
191         ASSERT_TRUE( waitNotificationReceived() );
192         std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
193         appendDataToFile( second_file_name );
194     }
195 
196     // The registration will be removed here.
197     appendDataToFile( second_file_name );
198     ASSERT_FALSE( waitNotificationReceived() );
199 
200     remove( second_file_name.c_str() );
201 }
202 
203 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) {
204     auto second_registration = registerFile( file_name );
205     appendDataToFile( file_name );
206 
207     ASSERT_TRUE( waitNotificationReceived( 2 ) );
208 }
209 
210 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) {
211     {
212         auto second_registration = registerFile( file_name );
213     }
214 
215     appendDataToFile( file_name );
216     ASSERT_TRUE( waitNotificationReceived( 1 ) );
217 }
218 
219 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) {
220     auto new_file_name = createTempName();
221 
222     rename( file_name.c_str(), new_file_name );
223     ASSERT_TRUE( waitNotificationReceived() );
224 
225     rename( new_file_name, file_name.c_str() );
226 }
227 
228 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) {
229     remove( file_name.c_str() );
230     waitNotificationReceived();
231 
232     std::string new_file_name = createTempEmptyFile();
233     appendDataToFile( new_file_name );
234 
235     rename( new_file_name.c_str(), file_name.c_str() );
236     ASSERT_TRUE( waitNotificationReceived() );
237 }
238 
239 /*****/
240 
241 #ifdef HAVE_SYMLINK
242 class WatchTowerSymlink: public WatchTowerSingleFile {
243   public:
244     string symlink_name;
245 
246     void SetUp() override {
247         file_name = createTempEmptyFile();
248         symlink_name = createTempEmptyFile();
249         remove( symlink_name.c_str() );
250         symlink( file_name.c_str(), symlink_name.c_str() );
251 
252         registration = registerFile( symlink_name );
253     }
254 
255     void TearDown() override {
256         remove( symlink_name.c_str() );
257         remove( file_name.c_str() );
258     }
259 };
260 
261 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
262     appendDataToFile( symlink_name );
263     ASSERT_TRUE( waitNotificationReceived() );
264 }
265 
266 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
267     appendDataToFile( file_name );
268     ASSERT_TRUE( waitNotificationReceived() );
269 }
270 
271 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
272     remove( symlink_name.c_str() );
273     ASSERT_TRUE( waitNotificationReceived() );
274 }
275 
276 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
277     remove( file_name.c_str() );
278     ASSERT_TRUE( waitNotificationReceived() );
279 }
280 
281 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
282     auto new_target = createTempEmptyFile();
283     remove( symlink_name.c_str() );
284     waitNotificationReceived();
285 
286     symlink( new_target.c_str(), symlink_name.c_str() );
287     ASSERT_TRUE( waitNotificationReceived() );
288 
289     remove( new_target.c_str() );
290 }
291 #endif //HAVE_SYMLINK
292 
293 /*****/
294 
295 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
296     auto mortal_watch_tower = new PlatformWatchTower();
297     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
298 
299     delete mortal_watch_tower;
300     // reg will be destroyed after the watch_tower
301 }
302 
303 /*****/
304 
305 #ifdef _WIN32
306 class WinNotificationInfoListTest : public testing::Test {
307   public:
308     using Action = WinNotificationInfo::Action;
309 
310     struct Buffer {
311         uint32_t next_addr;
312         uint32_t action;
313         uint32_t filename_length;
314         wchar_t filename[13];
315     };
316     static struct Buffer buffer[2];
317 
318     WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) };
319     WinNotificationInfoList::iterator iterator { std::begin( list ) };
320 };
321 
322 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] =
323     { { 40, 1, 26, L"Filename.txt" },
324       { 0, 3, 18, L"file2.txt" } };
325 
326 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) {
327     auto notification = *iterator;
328     ASSERT_THAT( &notification, NotNull() );
329 }
330 
331 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) {
332     ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) );
333 }
334 
335 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) {
336     ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) );
337 }
338 
339 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) {
340     ++iterator;
341     auto notification = *iterator;
342     ASSERT_THAT( &notification, NotNull() );
343 }
344 
345 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) {
346     iterator++;
347     ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) );
348     ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) );
349 }
350 
351 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) {
352     for ( auto notification : list ) {
353         notification.action();
354     }
355 }
356 #endif
357