LCOV - code coverage report
Current view: top level - tests - catch_communicator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 171 177 96.6 %
Date: 2024-06-09 22:27:44 Functions: 11 12 91.7 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions