xref: /glogg/tests/watchtowerTest.cpp (revision 5d489994434e252f0cbb5d15eefbd86a61d29bfb)
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 #ifdef _WIN32
17 #  include "winwatchtower.h"
18 #else
19 #  include "inotifywatchtower.h"
20 #endif
21 
22 using namespace std;
23 using namespace testing;
24 
25 class WatchTowerBehaviour: public testing::Test {
26   public:
27 #ifdef _WIN32
28     shared_ptr<WatchTower> watch_tower = make_shared<WinWatchTower>();
29 #else
30     shared_ptr<WatchTower> watch_tower = make_shared<INotifyWatchTower>();
31 #endif
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     WatchTower::Registration registration;
92 
93     mutex mutex_;
94     condition_variable cv_;
95     int notification_received = 0;
96 
97     WatchTower::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 = 5000;
147 #else
148 const int WatchTowerSingleFile::TIMEOUT = 20;
149 #endif
150 
151 TEST_F( WatchTowerSingleFile, Simple ) {
152 }
153 
154 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsAppended ) {
155     appendDataToFile( file_name );
156     ASSERT_TRUE( waitNotificationReceived() );
157 }
158 
159 TEST_F( WatchTowerSingleFile, SignalsWhenAWatchedFileIsRemoved) {
160     std::this_thread::sleep_for( std::chrono::milliseconds(1000) );
161     remove( file_name.c_str() );
162     ASSERT_TRUE( waitNotificationReceived() );
163 }
164 
165 TEST_F( WatchTowerSingleFile, SignalsWhenADeletedFileReappears ) {
166     std::this_thread::sleep_for( std::chrono::milliseconds(1000) );
167     remove( file_name.c_str() );
168     waitNotificationReceived();
169     std::this_thread::sleep_for( std::chrono::milliseconds(1000) );
170     createTempEmptyFile( file_name );
171     ASSERT_TRUE( waitNotificationReceived() );
172 }
173 
174 TEST_F( WatchTowerSingleFile, StopSignalingWhenWatchDeleted ) {
175     auto second_file_name = createTempEmptyFile();
176     {
177         auto second_registration = registerFile( second_file_name );
178         appendDataToFile( second_file_name );
179         ASSERT_TRUE( waitNotificationReceived() );
180     }
181 
182     // The registration will be removed here.
183     appendDataToFile( second_file_name );
184     ASSERT_FALSE( waitNotificationReceived() );
185 
186     remove( second_file_name.c_str() );
187 }
188 
189 TEST_F( WatchTowerSingleFile, TwoWatchesOnSameFileYieldsTwoNotifications ) {
190     auto second_registration = registerFile( file_name );
191     appendDataToFile( file_name );
192 
193     ASSERT_TRUE( waitNotificationReceived( 2 ) );
194 }
195 
196 TEST_F( WatchTowerSingleFile, RemovingOneWatchOfTwoStillYieldsOneNotification ) {
197     {
198         auto second_registration = registerFile( file_name );
199     }
200 
201     appendDataToFile( file_name );
202     ASSERT_TRUE( waitNotificationReceived( 1 ) );
203 }
204 
205 TEST_F( WatchTowerSingleFile, RenamingTheFileYieldsANotification ) {
206     auto new_file_name = createTempName();
207 
208     rename( file_name.c_str(), new_file_name );
209     ASSERT_TRUE( waitNotificationReceived() );
210 
211     rename( new_file_name, file_name.c_str() );
212 }
213 
214 TEST_F( WatchTowerSingleFile, RenamingAFileToTheWatchedNameYieldsANotification ) {
215     remove( file_name.c_str() );
216     waitNotificationReceived();
217 
218     std::string new_file_name = createTempEmptyFile();
219     appendDataToFile( new_file_name );
220 
221     rename( new_file_name.c_str(), file_name.c_str() );
222     ASSERT_TRUE( waitNotificationReceived() );
223 }
224 
225 /*****/
226 
227 #ifdef HAVE_SYMLINK
228 class WatchTowerSymlink: public WatchTowerSingleFile {
229   public:
230     string symlink_name;
231 
232     void SetUp() override {
233         file_name = createTempEmptyFile();
234         symlink_name = createTempEmptyFile();
235         remove( symlink_name.c_str() );
236         symlink( file_name.c_str(), symlink_name.c_str() );
237 
238         registration = registerFile( symlink_name );
239     }
240 
241     void TearDown() override {
242         remove( symlink_name.c_str() );
243         remove( file_name.c_str() );
244     }
245 };
246 
247 TEST_F( WatchTowerSymlink, AppendingToTheSymlinkYieldsANotification ) {
248     appendDataToFile( symlink_name );
249     ASSERT_TRUE( waitNotificationReceived() );
250 }
251 
252 TEST_F( WatchTowerSymlink, AppendingToTheTargetYieldsANotification ) {
253     appendDataToFile( file_name );
254     ASSERT_TRUE( waitNotificationReceived() );
255 }
256 
257 TEST_F( WatchTowerSymlink, RemovingTheSymlinkYieldsANotification ) {
258     remove( symlink_name.c_str() );
259     ASSERT_TRUE( waitNotificationReceived() );
260 }
261 
262 TEST_F( WatchTowerSymlink, RemovingTheTargetYieldsANotification ) {
263     remove( file_name.c_str() );
264     ASSERT_TRUE( waitNotificationReceived() );
265 }
266 
267 TEST_F( WatchTowerSymlink, ReappearingSymlinkYieldsANotification ) {
268     auto new_target = createTempEmptyFile();
269     remove( symlink_name.c_str() );
270     waitNotificationReceived();
271 
272     symlink( new_target.c_str(), symlink_name.c_str() );
273     ASSERT_TRUE( waitNotificationReceived() );
274 
275     remove( new_target.c_str() );
276 }
277 #endif //HAVE_SYMLINK
278 
279 /*****/
280 
281 TEST( WatchTowerLifetime, RegistrationCanBeDeletedWhenWeAreDead ) {
282 #if _WIN32
283     auto mortal_watch_tower = new WinWatchTower();
284 #else
285     auto mortal_watch_tower = new INotifyWatchTower();
286 #endif
287     auto reg = mortal_watch_tower->addFile( "/tmp/test_file", [] (void) { } );
288 
289     delete mortal_watch_tower;
290     // reg will be destroyed after the watch_tower
291 }
292 
293 /*****/
294 
295 #ifdef _WIN32
296 class WinNotificationInfoListTest : public testing::Test {
297   public:
298     using Action = WinNotificationInfo::Action;
299 
300     struct Buffer {
301         uint32_t next_addr;
302         uint32_t action;
303         uint32_t filename_length;
304         wchar_t filename[13];
305     };
306     static struct Buffer buffer[2];
307 
308     WinNotificationInfoList list { reinterpret_cast<char*>( buffer ), sizeof( buffer ) };
309     WinNotificationInfoList::iterator iterator { std::begin( list ) };
310 };
311 
312 struct WinNotificationInfoListTest::Buffer WinNotificationInfoListTest::buffer[] =
313     { { 40, 1, 26, L"Filename.txt" },
314       { 0, 3, 18, L"file2.txt" } };
315 
316 TEST_F( WinNotificationInfoListTest, FirstNotificationCanBeObtained ) {
317     auto notification = *iterator;
318     ASSERT_THAT( &notification, NotNull() );
319 }
320 
321 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightAction ) {
322     ASSERT_THAT( (*iterator).action(), Eq( Action::ADDED ) );
323 }
324 
325 TEST_F( WinNotificationInfoListTest, FirstNotificationHasRightFileName ) {
326     ASSERT_THAT( (*iterator).fileName(), Eq( std::wstring( L"Filename.txt\0", 13 ) ) );
327 }
328 
329 TEST_F( WinNotificationInfoListTest, SecondNotificationCanBeObtained ) {
330     ++iterator;
331     auto notification = *iterator;
332     ASSERT_THAT( &notification, NotNull() );
333 }
334 
335 TEST_F( WinNotificationInfoListTest, SecondNotificationIsCorrect ) {
336     iterator++;
337     ASSERT_THAT( iterator->action(), Eq( Action::MODIFIED ) );
338     ASSERT_THAT( iterator->fileName(), Eq( L"file2.txt" ) );
339 }
340 
341 TEST_F( WinNotificationInfoListTest, CanBeIteratedByFor ) {
342     for ( auto notification : list ) {
343         notification.action();
344     }
345 }
346 #endif
347