Line data Source code
1 : // Copyright (c) 2011-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/communicatord
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 : /** \file
20 : * \brief Verify some of the communicatord client implementation.
21 : *
22 : * This test file implements verifications of the communicatord client
23 : * class.
24 : */
25 :
26 : // communicatord
27 : //
28 : #include <communicatord/communicator.h>
29 : #include <communicatord/exception.h>
30 : #include <communicatord/version.h>
31 :
32 :
33 : // eventdispatcher
34 : //
35 : #include <eventdispatcher/communicator.h>
36 : #include <eventdispatcher/dispatcher.h>
37 : #include <eventdispatcher/names.h>
38 : #include <eventdispatcher/tcp_client_permanent_message_connection.h>
39 :
40 : #include <eventdispatcher/reporter/executor.h>
41 : #include <eventdispatcher/reporter/lexer.h>
42 : #include <eventdispatcher/reporter/parser.h>
43 : #include <eventdispatcher/reporter/state.h>
44 :
45 :
46 : // self
47 : //
48 : #include "catch_main.h"
49 :
50 :
51 :
52 : namespace
53 : {
54 :
55 :
56 :
57 : advgetopt::option const g_options[] =
58 : {
59 : advgetopt::define_option(
60 : advgetopt::Name("fancy-option")
61 : , advgetopt::Flags(advgetopt::all_flags<
62 : advgetopt::GETOPT_FLAG_REQUIRED
63 : , advgetopt::GETOPT_FLAG_GROUP_OPTIONS>())
64 : , advgetopt::Help("The fancy option.")
65 : ),
66 : advgetopt::end_options()
67 : };
68 :
69 :
70 : advgetopt::group_description const g_group_descriptions[] =
71 : {
72 : advgetopt::define_group(
73 : advgetopt::GroupNumber(advgetopt::GETOPT_FLAG_GROUP_COMMANDS)
74 : , advgetopt::GroupName("command")
75 : , advgetopt::GroupDescription("Commands:")
76 : ),
77 : advgetopt::define_group(
78 : advgetopt::GroupNumber(advgetopt::GETOPT_FLAG_GROUP_OPTIONS)
79 : , advgetopt::GroupName("option")
80 : , advgetopt::GroupDescription("Options:")
81 : ),
82 : advgetopt::end_groups()
83 : };
84 :
85 :
86 : //constexpr char const * const g_configuration_files[] =
87 : //{
88 : // "/etc/communicatord/communicatord.conf",
89 : // nullptr
90 : //};
91 :
92 :
93 : advgetopt::options_environment const g_options_environment =
94 : {
95 : .f_project_name = "communicatord",
96 : .f_group_name = "communicatord",
97 : .f_options = g_options,
98 : .f_environment_variable_name = "COMMUNICATORD_OPTIONS",
99 : //.f_configuration_files = g_configuration_files,
100 : .f_environment_flags = advgetopt::GETOPT_ENVIRONMENT_FLAG_SYSTEM_PARAMETERS
101 : | advgetopt::GETOPT_ENVIRONMENT_FLAG_PROCESS_SYSTEM_PARAMETERS,
102 : .f_help_header = "Usage: %p [-<opt>]\n"
103 : "where -<opt> is one or more of:",
104 : .f_help_footer = "%c",
105 : .f_version = COMMUNICATORD_VERSION_STRING,
106 : .f_license = "GNU GPL v3",
107 : .f_copyright = "Copyright Notice",
108 : .f_groups = g_group_descriptions,
109 : };
110 :
111 :
112 :
113 2 : addr::addr get_address()
114 : {
115 2 : addr::addr a;
116 2 : sockaddr_in ip = {
117 : .sin_family = AF_INET,
118 2 : .sin_port = htons(20002),
119 : .sin_addr = {
120 2 : .s_addr = htonl(0x7f000001),
121 : },
122 : .sin_zero = {},
123 2 : };
124 2 : a.set_ipv4(ip);
125 4 : return a;
126 0 : }
127 :
128 :
129 : // the cluck class requires a messenger to function, it is a client
130 : // extension instead of a standalone client
131 : //
132 : class test_messenger
133 : : public communicatord::communicator
134 : , public ed::manage_message_definition_paths
135 : {
136 : public:
137 : typedef std::shared_ptr<test_messenger> pointer_t;
138 :
139 : enum class sequence_t
140 : {
141 : SEQUENCE_SUCCESS,
142 : };
143 :
144 2 : test_messenger(
145 : advgetopt::getopt & opts
146 : , int argc
147 : , char * argv[]
148 : , sequence_t sequence)
149 2 : : communicator(opts, "test_communicator_client")
150 : , manage_message_definition_paths(
151 : // WARNING: the order matters, we want to test with our source
152 : // (i.e. original) files first
153 : //
154 4 : SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/message-definitions:"
155 6 : + SNAP_CATCH2_NAMESPACE::g_source_dir() + "/daemon/message-definitions:"
156 6 : + SNAP_CATCH2_NAMESPACE::g_dist_dir() + "/share/eventdispatcher/messages")
157 2 : , f_sequence(sequence)
158 6 : , f_dispatcher(std::make_shared<ed::dispatcher>(this))
159 : {
160 2 : set_name("test_messenger"); // connection name
161 2 : set_dispatcher(f_dispatcher);
162 :
163 2 : opts.finish_parsing(argc, argv);
164 :
165 6 : f_dispatcher->add_matches({
166 4 : DISPATCHER_MATCH("DATA", &test_messenger::msg_data),
167 : //DISPATCHER_CATCH_ALL(),
168 : });
169 2 : f_dispatcher->add_communicator_commands();
170 :
171 : // further dispatcher initialization
172 : //
173 : #ifdef _DEBUG
174 2 : f_dispatcher->set_trace();
175 2 : f_dispatcher->set_show_matches();
176 : #endif
177 2 : }
178 :
179 2 : void finish_init()
180 : {
181 2 : process_communicatord_options();
182 :
183 : // that function cannot be called again
184 : //
185 2 : CATCH_REQUIRE_THROWS_MATCHES(
186 : process_communicatord_options()
187 : , communicatord::logic_error
188 : , Catch::Matchers::ExceptionMessage("logic_error: process_communicatord_options() called twice."));
189 :
190 2 : CATCH_REQUIRE(service_name() == "test_communicator_client");
191 :
192 : // when we start, we're not connected
193 : //
194 2 : CATCH_REQUIRE_FALSE(is_connected());
195 :
196 : // at this point the communicator is not connected so sending
197 : // messages fails with false
198 : //
199 2 : ed::message too_early;
200 2 : too_early.set_command("TOO_EARLY");
201 2 : CATCH_REQUIRE_FALSE(send_message(too_early));
202 4 : }
203 :
204 : ed::dispatcher::pointer_t get_dispatcher() const
205 : {
206 : return f_dispatcher;
207 : }
208 :
209 : // virtual void process_connected() override
210 : // {
211 : // // always register at the time we connect
212 : // //
213 : // tcp_client_permanent_message_connection::process_connected();
214 : //
215 : //std::cerr << "--- ready!\n";
216 : // }
217 :
218 :
219 2 : void msg_data(ed::message & msg)
220 : {
221 2 : std::cerr << "--- got DATA message: " << msg << "...\n";
222 2 : CATCH_REQUIRE(msg.get_sent_from_server() == "monster");
223 2 : CATCH_REQUIRE(msg.get_sent_from_service() == "test_communicator_client");
224 2 : CATCH_REQUIRE(msg.get_server() == "communicatord");
225 2 : CATCH_REQUIRE(msg.get_service() == "communicator_test");
226 2 : CATCH_REQUIRE(msg.has_parameter("filename"));
227 2 : CATCH_REQUIRE(msg.get_parameter("filename") == "/var/log/communicatord/communicatord.log");
228 :
229 : //std::string const data(msg.get_parameter("data"));
230 : //std::int64_t const size(msg.get_integer_parameter("size"));
231 : //CATCH_REQUIRE(data.size() == static_cast<std::string::size_type>(size));
232 :
233 : // since we received a message, we're connected (registered)
234 : //
235 2 : CATCH_REQUIRE(is_connected());
236 :
237 2 : ed::message reply;
238 2 : reply.reply_to(msg);
239 2 : reply.set_command("DONE");
240 2 : CATCH_REQUIRE(send_message(reply));
241 4 : }
242 :
243 : //virtual void msg_reply_with_unknown(ed::message & msg) override
244 : //{
245 : // tcp_client_permanent_message_connection::msg_reply_with_unknown(msg);
246 :
247 : // // note that the cluck class has no idea about the unknown
248 : // // message so we do not get our finally() callback called
249 : // // automatically here (we should not get UNKNOWN messages
250 : // // about cluck anyway)
251 : // //
252 : // switch(f_sequence)
253 : // {
254 : // //case sequence_t::SEQUENCE_FAIL_MISSING_LOCKED_PARAMETERS:
255 : // //break;
256 :
257 : // default:
258 : // break;
259 :
260 : // }
261 : //}
262 :
263 2 : void stop(bool quitting)
264 : {
265 2 : unregister_communicator(quitting);
266 :
267 2 : if(quitting)
268 : {
269 : // at this point the communicator cleared its messenger
270 : //
271 1 : ed::message too_late;
272 1 : too_late.set_command("TOO_LATE");
273 1 : CATCH_REQUIRE_FALSE(send_message(too_late));
274 1 : }
275 :
276 : //disconnect();
277 2 : }
278 :
279 : void disconnect()
280 : {
281 : remove_from_communicator();
282 :
283 : ed::connection::pointer_t timer_ptr(f_timer.lock());
284 : if(timer_ptr != nullptr)
285 : {
286 : timer_ptr->remove_from_communicator();
287 : }
288 : }
289 :
290 2 : void set_timer(ed::connection::pointer_t done_timer)
291 : {
292 2 : f_timer = done_timer;
293 2 : }
294 :
295 : private:
296 : // the sequence & step define the next action
297 : //
298 : //advgetopt::getopt f_opts;
299 : sequence_t f_sequence = sequence_t::SEQUENCE_SUCCESS;
300 : ed::dispatcher::pointer_t f_dispatcher = ed::dispatcher::pointer_t();
301 : int f_step = 0;
302 : ed::connection::weak_pointer_t
303 : f_timer = ed::connection::weak_pointer_t();
304 : };
305 :
306 :
307 : class test_timer
308 : : public ed::timer
309 : {
310 : public:
311 : typedef std::shared_ptr<test_timer> pointer_t;
312 :
313 2 : test_timer(test_messenger::pointer_t m)
314 2 : : timer(-1)
315 2 : , f_messenger(m)
316 : {
317 2 : set_name("test_timer");
318 2 : }
319 :
320 0 : void process_timeout()
321 : {
322 0 : remove_from_communicator();
323 0 : f_messenger->remove_from_communicator();
324 0 : f_timed_out = true;
325 0 : }
326 :
327 : bool timed_out_prima() const
328 : {
329 : return f_timed_out;
330 : }
331 :
332 : private:
333 : test_messenger::pointer_t f_messenger = test_messenger::pointer_t();
334 : bool f_timed_out = false;
335 : };
336 :
337 :
338 :
339 : } // no name namespace
340 :
341 :
342 :
343 2 : CATCH_TEST_CASE("communicator", "[client]")
344 : {
345 2 : CATCH_START_SECTION("communicator: verify default strings")
346 : {
347 1 : std::stringstream port_str;
348 1 : port_str << communicatord::LOCAL_PORT;
349 1 : CATCH_REQUIRE(port_str.str() == std::string(communicatord::g_communicatord_default_port));
350 :
351 1 : std::stringstream ip_port;
352 1 : ip_port << communicatord::g_communicatord_default_ip << ":" << communicatord::LOCAL_PORT;
353 1 : CATCH_REQUIRE(ip_port.str() == std::string(communicatord::g_communicatord_default_ip_port));
354 1 : }
355 2 : CATCH_END_SECTION()
356 :
357 2 : CATCH_START_SECTION("communicator: verify request_failure()")
358 : {
359 1 : ed::message msg;
360 1 : CATCH_REQUIRE_FALSE(msg.has_parameter("transmission_report"));
361 1 : communicatord::request_failure(msg);
362 1 : CATCH_REQUIRE(msg.has_parameter("transmission_report"));
363 1 : CATCH_REQUIRE(msg.get_parameter("transmission_report") == "failure");
364 1 : }
365 2 : CATCH_END_SECTION()
366 2 : }
367 :
368 :
369 3 : CATCH_TEST_CASE("communicator_client_connection", "[client]")
370 : {
371 3 : CATCH_START_SECTION("communicator_client_connection: service name cannot be an empty string")
372 : {
373 1 : advgetopt::getopt opts(g_options_environment);
374 1 : CATCH_REQUIRE_THROWS_MATCHES(
375 : std::make_shared<communicatord::communicator>(opts, "")
376 : , communicatord::invalid_name
377 : , Catch::Matchers::ExceptionMessage("communicatord_exception: the service_name parameter of the communicator constructor cannot be an empty string."));
378 1 : }
379 3 : CATCH_END_SECTION()
380 :
381 3 : CATCH_START_SECTION("communicator_client_connection: test communicator client (regular stop)")
382 : {
383 1 : std::string const source_dir(SNAP_CATCH2_NAMESPACE::g_source_dir());
384 1 : std::string const filename(source_dir + "/tests/rprtr/communicator_server_test.rprtr");
385 1 : SNAP_CATCH2_NAMESPACE::reporter::lexer::pointer_t l(SNAP_CATCH2_NAMESPACE::reporter::create_lexer(filename));
386 1 : CATCH_REQUIRE(l != nullptr);
387 1 : SNAP_CATCH2_NAMESPACE::reporter::state::pointer_t s(std::make_shared<SNAP_CATCH2_NAMESPACE::reporter::state>());
388 1 : SNAP_CATCH2_NAMESPACE::reporter::parser::pointer_t p(std::make_shared<SNAP_CATCH2_NAMESPACE::reporter::parser>(l, s));
389 1 : p->parse_program();
390 :
391 1 : SNAP_CATCH2_NAMESPACE::reporter::executor::pointer_t e(std::make_shared<SNAP_CATCH2_NAMESPACE::reporter::executor>(s));
392 1 : e->start();
393 :
394 1 : addr::addr a(get_address());
395 1 : std::vector<std::string> const args = {
396 : "test-service", // name of command
397 : "--communicatord-listen",
398 2 : "cd://" + a.to_ipv4or6_string(addr::STRING_IP_ADDRESS_PORT),
399 : "--path-to-message-definitions",
400 :
401 : // WARNING: the order matters, we want to test with our source
402 : // (i.e. original) files first
403 : //
404 2 : SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/message-definitions:"
405 4 : + SNAP_CATCH2_NAMESPACE::g_source_dir() + "/daemon/message-definitions:"
406 3 : + SNAP_CATCH2_NAMESPACE::g_dist_dir() + "/share/eventdispatcher/messages",
407 13 : };
408 :
409 : // convert arguments so we can use them with execvpe()
410 : //
411 1 : std::vector<char const *> args_strings;
412 1 : args_strings.reserve(args.size() + 1);
413 6 : for(auto const & arg : args)
414 : {
415 5 : args_strings.push_back(arg.c_str());
416 : }
417 1 : args_strings.push_back(nullptr); // NULL terminated
418 :
419 1 : advgetopt::getopt opts(g_options_environment);
420 1 : test_messenger::pointer_t messenger(std::make_shared<test_messenger>(
421 : opts
422 2 : , args.size()
423 2 : , const_cast<char **>(args_strings.data())
424 4 : , test_messenger::sequence_t::SEQUENCE_SUCCESS));
425 1 : messenger->finish_init();
426 1 : ed::communicator::instance()->add_connection(messenger);
427 1 : test_timer::pointer_t timer(std::make_shared<test_timer>(messenger));
428 1 : ed::communicator::instance()->add_connection(timer);
429 1 : messenger->set_timer(timer);
430 :
431 1 : CATCH_REQUIRE(messenger->service_name() == "test_communicator_client");
432 :
433 1 : e->set_thread_done_callback([messenger, timer]()
434 : {
435 1 : ed::communicator::instance()->remove_connection(messenger);
436 1 : ed::communicator::instance()->remove_connection(timer);
437 1 : });
438 :
439 1 : CATCH_REQUIRE(e->run());
440 :
441 1 : CATCH_REQUIRE(s->get_exit_code() == 0);
442 1 : }
443 3 : CATCH_END_SECTION()
444 :
445 3 : CATCH_START_SECTION("communicator_client_connection: test communicator client (quitting)")
446 : {
447 1 : std::string const source_dir(SNAP_CATCH2_NAMESPACE::g_source_dir());
448 1 : std::string const filename(source_dir + "/tests/rprtr/communicator_server_test_quitting.rprtr");
449 1 : SNAP_CATCH2_NAMESPACE::reporter::lexer::pointer_t l(SNAP_CATCH2_NAMESPACE::reporter::create_lexer(filename));
450 1 : CATCH_REQUIRE(l != nullptr);
451 1 : SNAP_CATCH2_NAMESPACE::reporter::state::pointer_t s(std::make_shared<SNAP_CATCH2_NAMESPACE::reporter::state>());
452 1 : SNAP_CATCH2_NAMESPACE::reporter::parser::pointer_t p(std::make_shared<SNAP_CATCH2_NAMESPACE::reporter::parser>(l, s));
453 1 : p->parse_program();
454 :
455 1 : SNAP_CATCH2_NAMESPACE::reporter::executor::pointer_t e(std::make_shared<SNAP_CATCH2_NAMESPACE::reporter::executor>(s));
456 1 : e->start();
457 :
458 1 : addr::addr a(get_address());
459 1 : std::vector<std::string> const args = {
460 : "test-service", // name of command
461 : "--communicatord-listen",
462 2 : "cd://" + a.to_ipv4or6_string(addr::STRING_IP_ADDRESS_PORT),
463 : "--path-to-message-definitions",
464 :
465 : // WARNING: the order matters, we want to test with our source
466 : // (i.e. original) files first
467 : //
468 2 : SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/message-definitions:"
469 4 : + SNAP_CATCH2_NAMESPACE::g_source_dir() + "/daemon/message-definitions:"
470 3 : + SNAP_CATCH2_NAMESPACE::g_dist_dir() + "/share/eventdispatcher/messages",
471 13 : };
472 :
473 : // convert arguments so we can use them with execvpe()
474 : //
475 1 : std::vector<char const *> args_strings;
476 1 : args_strings.reserve(args.size() + 1);
477 6 : for(auto const & arg : args)
478 : {
479 5 : args_strings.push_back(arg.c_str());
480 : }
481 1 : args_strings.push_back(nullptr); // NULL terminated
482 :
483 1 : advgetopt::getopt opts(g_options_environment);
484 1 : test_messenger::pointer_t messenger(std::make_shared<test_messenger>(
485 : opts
486 2 : , args.size()
487 2 : , const_cast<char **>(args_strings.data())
488 4 : , test_messenger::sequence_t::SEQUENCE_SUCCESS));
489 1 : messenger->finish_init();
490 1 : ed::communicator::instance()->add_connection(messenger);
491 1 : test_timer::pointer_t timer(std::make_shared<test_timer>(messenger));
492 1 : ed::communicator::instance()->add_connection(timer);
493 1 : messenger->set_timer(timer);
494 :
495 1 : CATCH_REQUIRE(messenger->service_name() == "test_communicator_client");
496 :
497 1 : e->set_thread_done_callback([messenger, timer]()
498 : {
499 1 : ed::communicator::instance()->remove_connection(messenger);
500 1 : ed::communicator::instance()->remove_connection(timer);
501 1 : });
502 :
503 1 : CATCH_REQUIRE(e->run());
504 :
505 1 : CATCH_REQUIRE(s->get_exit_code() == 0);
506 1 : }
507 3 : CATCH_END_SECTION()
508 3 : }
509 :
510 :
511 : // vim: ts=4 sw=4 et
|