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