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