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 : // C
41 : //
42 : #include <unistd.h>
43 :
44 :
45 : // last include
46 : //
47 : #include <snapdev/poison.h>
48 :
49 :
50 :
51 : namespace
52 : {
53 :
54 :
55 :
56 : bool g_event_processed = false;
57 :
58 :
59 : typedef std::function<void()> tweak_callback_t;
60 :
61 :
62 : class tweak_files
63 : : public cppthread::runner
64 : {
65 : public:
66 : typedef std::shared_ptr<tweak_files> pointer_t;
67 :
68 : tweak_files(
69 : std::string const & name
70 : , tweak_callback_t callback);
71 :
72 : virtual void run() override;
73 :
74 : private:
75 : tweak_callback_t f_callback = tweak_callback_t();
76 : };
77 :
78 :
79 2 : tweak_files::tweak_files(
80 : std::string const & name
81 2 : , tweak_callback_t callback)
82 : : runner(name)
83 2 : , f_callback(callback)
84 : {
85 2 : }
86 :
87 :
88 2 : void tweak_files::run()
89 : {
90 2 : f_callback();
91 2 : }
92 :
93 :
94 :
95 :
96 :
97 :
98 : class file_listener
99 : : public ed::file_changed
100 : {
101 : public:
102 : typedef std::shared_ptr<file_listener> pointer_t;
103 :
104 : file_listener();
105 : virtual ~file_listener();
106 :
107 : void add_expected(
108 : std::string const & watched_path
109 : , ed::file_event_mask_t events
110 : , std::string const & filename);
111 : void run_test(
112 : std::string const & name
113 : , tweak_callback_t callback);
114 :
115 : // implementation of file_changed
116 : //
117 : virtual void process_event(ed::file_event const & watch_event) override;
118 : virtual void process_timeout() override;
119 :
120 : private:
121 : std::list<ed::file_event> f_expected = std::list<ed::file_event>();
122 : tweak_files::pointer_t f_tweak_files = tweak_files::pointer_t();
123 : cppthread::thread::pointer_t
124 : f_thread = cppthread::thread::pointer_t();
125 : };
126 :
127 :
128 :
129 :
130 :
131 :
132 :
133 :
134 :
135 2 : file_listener::file_listener()
136 : {
137 6 : set_name("file-listener");
138 :
139 2 : g_event_processed = false;
140 2 : }
141 :
142 :
143 2 : file_listener::~file_listener()
144 : {
145 2 : g_event_processed = f_expected.empty();
146 2 : }
147 :
148 :
149 9 : void file_listener::process_event(ed::file_event const & watch_event)
150 : {
151 : // if the vector is empty, then we received more events than expected
152 : // and there is a bug somewhere (test or implementation)
153 : //
154 : // Note: this can happen because once the vector is empty we wait
155 : // another second to know whether we're done or not
156 : //
157 9 : CATCH_REQUIRE_FALSE(f_expected.empty());
158 :
159 9 : CATCH_REQUIRE(f_expected.front().get_watched_path() == watch_event.get_watched_path());
160 9 : CATCH_REQUIRE(f_expected.front().get_events() == watch_event.get_events());
161 9 : CATCH_REQUIRE(f_expected.front().get_filename() == watch_event.get_filename());
162 :
163 9 : f_expected.pop_front();
164 :
165 9 : if(f_expected.empty())
166 : {
167 : // wait another 3 seconds to make sure that no more events occur
168 : // after the last one
169 : //
170 2 : set_timeout_delay(3'000'000);
171 : }
172 9 : }
173 :
174 :
175 2 : void file_listener::process_timeout()
176 : {
177 2 : remove_from_communicator();
178 2 : }
179 :
180 :
181 9 : void file_listener::add_expected(
182 : std::string const & watched_path
183 : , ed::file_event_mask_t events
184 : , std::string const & filename)
185 : {
186 9 : f_expected.push_back(ed::file_event(watched_path, events, filename));
187 9 : }
188 :
189 :
190 2 : void file_listener::run_test(
191 : std::string const & name
192 : , tweak_callback_t callback)
193 : {
194 2 : f_tweak_files = std::make_shared<tweak_files>(name, callback);
195 2 : f_thread = std::make_shared<cppthread::thread>(name, f_tweak_files);
196 2 : f_thread->start();
197 2 : }
198 :
199 :
200 :
201 : } // no name namespace
202 :
203 :
204 :
205 2 : CATCH_TEST_CASE("file_changed_events", "[file_changed]")
206 : {
207 2 : CATCH_START_SECTION("file_changed_events: attributes")
208 : {
209 1 : ed::communicator::pointer_t communicator(ed::communicator::instance());
210 :
211 3 : std::string const dir(SNAP_CATCH2_NAMESPACE::get_tmp_dir("attributes"));
212 :
213 : {
214 1 : file_listener::pointer_t listener(std::make_shared<file_listener>());
215 1 : listener->watch_files(dir, ed::SNAP_FILE_CHANGED_EVENT_ATTRIBUTES);
216 :
217 1 : listener->add_expected(
218 : dir
219 : , ed::SNAP_FILE_CHANGED_EVENT_ATTRIBUTES
220 : | ed::SNAP_FILE_CHANGED_EVENT_DIRECTORY
221 1 : , std::string());
222 :
223 1 : communicator->add_connection(listener);
224 :
225 1 : int r(0);
226 3 : listener->run_test("attributes", [dir, &r]() {
227 1 : sleep(rand() % 3);
228 1 : r = chmod(dir.c_str(), 0770);
229 1 : });
230 :
231 1 : communicator->run();
232 :
233 1 : CATCH_REQUIRE(r == 0);
234 1 : }
235 :
236 1 : CATCH_REQUIRE(g_event_processed);
237 1 : }
238 2 : CATCH_END_SECTION()
239 :
240 2 : CATCH_START_SECTION("file_changed_events: create, write, close file, then open, read, close, finally delete")
241 : {
242 1 : ed::communicator::pointer_t communicator(ed::communicator::instance());
243 :
244 3 : std::string const dir(SNAP_CATCH2_NAMESPACE::get_tmp_dir("file-changed"));
245 1 : std::string const filename(dir + "/test.txt");
246 :
247 : {
248 1 : file_listener::pointer_t listener(std::make_shared<file_listener>());
249 1 : listener->watch_files(dir, ed::SNAP_FILE_CHANGED_EVENT_ALL);
250 :
251 : // create/write/close events
252 : //
253 3 : listener->add_expected(
254 : dir
255 : , ed::SNAP_FILE_CHANGED_EVENT_CREATED
256 : , "test.txt");
257 :
258 3 : listener->add_expected(
259 : dir
260 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
261 : , "test.txt");
262 :
263 3 : listener->add_expected(
264 : dir
265 : , ed::SNAP_FILE_CHANGED_EVENT_WRITE
266 : , "test.txt");
267 :
268 3 : listener->add_expected(
269 : dir
270 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
271 : | ed::SNAP_FILE_CHANGED_EVENT_UPDATED
272 : , "test.txt");
273 :
274 : // open/read/close events
275 : //
276 3 : listener->add_expected(
277 : dir
278 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
279 : , "test.txt");
280 :
281 3 : listener->add_expected(
282 : dir
283 : , ed::SNAP_FILE_CHANGED_EVENT_READ
284 : , "test.txt");
285 :
286 3 : listener->add_expected(
287 : dir
288 : , ed::SNAP_FILE_CHANGED_EVENT_ACCESS
289 : , "test.txt");
290 :
291 : // delete events
292 : //
293 3 : listener->add_expected(
294 : dir
295 : , ed::SNAP_FILE_CHANGED_EVENT_DELETED
296 : , "test.txt");
297 :
298 1 : communicator->add_connection(listener);
299 :
300 1 : int r(0);
301 3 : listener->run_test("file", [filename, &r]() {
302 3 : std::string const message("this is a test file");
303 1 : sleep(rand() % 3);
304 :
305 : // create/write/close
306 : {
307 1 : std::ofstream out(filename);
308 1 : out << message << std::endl;
309 1 : if(r == 0)
310 : {
311 1 : r = out.fail() ? 1 : 0;
312 : }
313 1 : }
314 :
315 1 : sleep(rand() % 3);
316 :
317 : // open/read/close
318 : {
319 1 : std::ifstream in(filename);
320 1 : std::string line;
321 1 : std::getline(in, line, '\n');
322 1 : if(r == 0)
323 : {
324 1 : r = in.fail() || line != message ? 1 : 0;
325 : }
326 1 : }
327 :
328 1 : sleep(rand() % 3);
329 :
330 : // delete
331 : {
332 1 : if(unlink(filename.c_str()) != 0)
333 : {
334 0 : r = 1;
335 : }
336 : }
337 2 : });
338 :
339 1 : communicator->run();
340 :
341 1 : CATCH_REQUIRE(r == 0);
342 1 : }
343 :
344 1 : CATCH_REQUIRE(g_event_processed);
345 1 : }
346 2 : CATCH_END_SECTION()
347 2 : }
348 :
349 :
350 : // vim: ts=4 sw=4 et
|