Line data Source code
1 : // Copyright (c) 2012-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // self
20 : //
21 : #include "catch_main.h"
22 :
23 :
24 : // eventdispatcher
25 : //
26 : #include <eventdispatcher/communicator.h>
27 : #include <eventdispatcher/file_changed.h>
28 :
29 : //#include <eventdispatcher/local_stream_server_client_message_connection.h>
30 : //#include <eventdispatcher/local_stream_server_connection.h>
31 : //#include <eventdispatcher/dispatcher.h>
32 :
33 :
34 : // cppthread
35 : //
36 : #include <cppthread/runner.h>
37 : #include <cppthread/thread.h>
38 :
39 :
40 : // snapdev
41 : //
42 : #include <snapdev/mkdir_p.h>
43 :
44 :
45 : // C
46 : //
47 : #include <unistd.h>
48 :
49 :
50 : // last include
51 : //
52 : #include <snapdev/poison.h>
53 :
54 :
55 :
56 : namespace
57 : {
58 :
59 :
60 :
61 : bool g_event_processed = false;
62 :
63 :
64 : typedef std::function<void()> tweak_callback_t;
65 :
66 :
67 : class tweak_files
68 : : public cppthread::runner
69 : {
70 : public:
71 : typedef std::shared_ptr<tweak_files> pointer_t;
72 :
73 : tweak_files(
74 : std::string const & name
75 : , tweak_callback_t callback);
76 :
77 : virtual void run() override;
78 :
79 : private:
80 : tweak_callback_t f_callback = tweak_callback_t();
81 : };
82 :
83 :
84 2 : tweak_files::tweak_files(
85 : std::string const & name
86 2 : , tweak_callback_t callback)
87 : : runner(name)
88 2 : , f_callback(callback)
89 : {
90 2 : }
91 :
92 :
93 2 : void tweak_files::run()
94 : {
95 2 : f_callback();
96 2 : }
97 :
98 :
99 :
100 :
101 :
102 :
103 : class file_listener
104 : : public ed::file_changed
105 : {
106 : public:
107 : typedef std::shared_ptr<file_listener> pointer_t;
108 :
109 : file_listener();
110 : virtual ~file_listener();
111 :
112 : void add_expected(
113 : std::string const & watched_path
114 : , ed::file_event_mask_t events
115 : , std::string const & filename);
116 : void run_test(
117 : std::string const & name
118 : , tweak_callback_t callback);
119 :
120 : // implementation of file_changed
121 : //
122 : virtual void process_event(ed::file_event const & watch_event) override;
123 : virtual void process_timeout() override;
124 :
125 : private:
126 : std::list<ed::file_event> f_expected = std::list<ed::file_event>();
127 : tweak_files::pointer_t f_tweak_files = tweak_files::pointer_t();
128 : cppthread::thread::pointer_t
129 : f_thread = cppthread::thread::pointer_t();
130 : };
131 :
132 :
133 :
134 :
135 :
136 :
137 :
138 :
139 :
140 2 : file_listener::file_listener()
141 : {
142 2 : set_name("file-listener");
143 :
144 2 : g_event_processed = false;
145 2 : }
146 :
147 :
148 2 : file_listener::~file_listener()
149 : {
150 2 : g_event_processed = f_expected.empty();
151 2 : }
152 :
153 :
154 9 : void file_listener::process_event(ed::file_event const & watch_event)
155 : {
156 : // if the vector is empty, then we received more events than expected
157 : // and there is a bug somewhere (test or implementation)
158 : //
159 : // Note: this can happen because once the vector is empty we wait
160 : // another second to know whether we're done or not
161 : //
162 9 : CATCH_REQUIRE_FALSE(f_expected.empty());
163 :
164 9 : CATCH_REQUIRE(f_expected.front().get_watched_path() == watch_event.get_watched_path());
165 9 : CATCH_REQUIRE(f_expected.front().get_events() == watch_event.get_events());
166 9 : CATCH_REQUIRE(f_expected.front().get_filename() == watch_event.get_filename());
167 :
168 9 : f_expected.pop_front();
169 :
170 9 : if(f_expected.empty())
171 : {
172 : // wait another 3 seconds to make sure that no more events occur
173 : // after the last one
174 : //
175 2 : set_timeout_delay(3'000'000);
176 : }
177 9 : }
178 :
179 :
180 2 : void file_listener::process_timeout()
181 : {
182 2 : remove_from_communicator();
183 2 : }
184 :
185 :
186 9 : void file_listener::add_expected(
187 : std::string const & watched_path
188 : , ed::file_event_mask_t events
189 : , std::string const & filename)
190 : {
191 9 : f_expected.push_back(ed::file_event(watched_path, events, filename));
192 9 : }
193 :
194 :
195 2 : void file_listener::run_test(
196 : std::string const & name
197 : , tweak_callback_t callback)
198 : {
199 2 : f_tweak_files = std::make_shared<tweak_files>(name, callback);
200 2 : f_thread = std::make_shared<cppthread::thread>(name, f_tweak_files);
201 2 : f_thread->start();
202 2 : }
203 :
204 :
205 :
206 :
207 :
208 :
209 :
210 :
211 :
212 2 : std::string get_tmp_dir(std::string const & subdir)
213 : {
214 2 : std::string const & tmp_dir(SNAP_CATCH2_NAMESPACE::g_tmp_dir() + "/" + subdir);
215 :
216 2 : CATCH_REQUIRE(snapdev::mkdir_p(tmp_dir, false, 0700) == 0);
217 :
218 4 : return tmp_dir;
219 2 : }
220 :
221 :
222 :
223 : } // no name namespace
224 :
225 :
226 :
227 2 : CATCH_TEST_CASE("file_changed_events", "[file_changed]")
228 : {
229 2 : CATCH_START_SECTION("file_changed_events: attributes")
230 : {
231 1 : ed::communicator::pointer_t communicator(ed::communicator::instance());
232 :
233 3 : std::string const dir(get_tmp_dir("attributes"));
234 :
235 : {
236 1 : file_listener::pointer_t listener(std::make_shared<file_listener>());
237 1 : listener->watch_files(dir, ed::SNAP_FILE_CHANGED_EVENT_ATTRIBUTES);
238 :
239 2 : listener->add_expected(
240 : dir
241 : , ed::SNAP_FILE_CHANGED_EVENT_ATTRIBUTES
242 : | ed::SNAP_FILE_CHANGED_EVENT_DIRECTORY
243 2 : , std::string());
244 :
245 1 : communicator->add_connection(listener);
246 :
247 1 : int r(0);
248 1 : listener->run_test("attributes", [dir, &r]() {
249 1 : sleep(rand() % 3);
250 1 : r = chmod(dir.c_str(), 0770);
251 1 : });
252 :
253 1 : communicator->run();
254 :
255 1 : CATCH_REQUIRE(r == 0);
256 1 : }
257 :
258 1 : CATCH_REQUIRE(g_event_processed);
259 1 : }
260 2 : CATCH_END_SECTION()
261 :
262 2 : CATCH_START_SECTION("file_changed_events: create, write, close file, then open, read, close, finally delete")
263 : {
264 1 : ed::communicator::pointer_t communicator(ed::communicator::instance());
265 :
266 3 : std::string const dir(get_tmp_dir("file-changed"));
267 1 : std::string const filename(dir + "/test.txt");
268 :
269 : {
270 1 : file_listener::pointer_t listener(std::make_shared<file_listener>());
271 1 : listener->watch_files(dir, ed::SNAP_FILE_CHANGED_EVENT_ALL);
272 :
273 : // create/write/close events
274 : //
275 1 : listener->add_expected(
276 : dir
277 : , ed::SNAP_FILE_CHANGED_EVENT_CREATED
278 : , "test.txt");
279 :
280 1 : listener->add_expected(
281 : dir
282 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
283 : , "test.txt");
284 :
285 1 : listener->add_expected(
286 : dir
287 : , ed::SNAP_FILE_CHANGED_EVENT_WRITE
288 : , "test.txt");
289 :
290 1 : listener->add_expected(
291 : dir
292 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
293 : | ed::SNAP_FILE_CHANGED_EVENT_UPDATED
294 : , "test.txt");
295 :
296 : // open/read/close events
297 : //
298 1 : listener->add_expected(
299 : dir
300 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
301 : , "test.txt");
302 :
303 1 : listener->add_expected(
304 : dir
305 : , ed::SNAP_FILE_CHANGED_EVENT_READ
306 : , "test.txt");
307 :
308 1 : listener->add_expected(
309 : dir
310 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
311 : , "test.txt");
312 :
313 : // delete events
314 : //
315 1 : listener->add_expected(
316 : dir
317 : , ed::SNAP_FILE_CHANGED_EVENT_DELETED
318 : , "test.txt");
319 :
320 1 : communicator->add_connection(listener);
321 :
322 1 : int r(0);
323 1 : listener->run_test("file", [filename, &r]() {
324 2 : std::string const message("this is a test file");
325 1 : sleep(rand() % 3);
326 :
327 : // create/write/close
328 : {
329 1 : std::ofstream out(filename);
330 1 : out << message << std::endl;
331 1 : if(r == 0)
332 : {
333 1 : r = out.fail() ? 1 : 0;
334 : }
335 1 : }
336 :
337 1 : sleep(rand() % 3);
338 :
339 : // open/read/close
340 : {
341 1 : std::ifstream in(filename);
342 1 : std::string line;
343 1 : std::getline(in, line, '\n');
344 1 : if(r == 0)
345 : {
346 1 : r = in.fail() || line != message ? 1 : 0;
347 : }
348 1 : }
349 :
350 1 : sleep(rand() % 3);
351 :
352 : // delete
353 : {
354 1 : if(unlink(filename.c_str()) != 0)
355 : {
356 0 : r = 1;
357 : }
358 : }
359 2 : });
360 :
361 1 : communicator->run();
362 :
363 1 : CATCH_REQUIRE(r == 0);
364 1 : }
365 :
366 1 : CATCH_REQUIRE(g_event_processed);
367 1 : }
368 2 : CATCH_END_SECTION()
369 2 : }
370 :
371 :
372 : // vim: ts=4 sw=4 et
|