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