diff --git a/docs/rst/appendixes/glossary.rst b/docs/rst/appendixes/glossary.rst index 7b132f52..d2f75a4f 100644 --- a/docs/rst/appendixes/glossary.rst +++ b/docs/rst/appendixes/glossary.rst @@ -90,3 +90,10 @@ DDS nomenclature See `Fast DDS documentation `__ for further information. + + Partition + Logical domain that helps organize communication between publishers and subscribers. + Each Partition is uniquely identified by a name. + + See `Fast DDS documentation `__ + for further information. diff --git a/docs/rst/user_manual/commands/commands.rst b/docs/rst/user_manual/commands/commands.rst index 19471dd4..8f83e0a8 100644 --- a/docs/rst/user_manual/commands/commands.rst +++ b/docs/rst/user_manual/commands/commands.rst @@ -35,6 +35,16 @@ This commands show user data being received by the application in real time. /rst/user_manual/commands/data.rst +Filter commands +=============== + +This command enables the user to filter the information observed by the application. + +.. toctree:: + :maxdepth: 2 + + /rst/user_manual/commands/filter.rst + Extra commands ============== @@ -105,6 +115,17 @@ Summary - ``topic`` ``topics`` |br| ``t`` ``T`` + * - :ref:`user_manual_command_filter` + - Filter related commands + - ``set`` |br| + ``add`` |br| + ``remove`` |br| + ``clear`` |br| + ```` |br| + ```` |br| + - ``filter`` ``filters`` |br| + ``f`` ``F`` + * - :ref:`user_manual_command_echo` - Show real-time receiving user data. - ```` |br| diff --git a/docs/rst/user_manual/commands/filter.rst b/docs/rst/user_manual/commands/filter.rst new file mode 100644 index 00000000..ce3159ac --- /dev/null +++ b/docs/rst/user_manual/commands/filter.rst @@ -0,0 +1,169 @@ +.. include:: ../../exports/alias.include +.. include:: ../../exports/roles.include + +.. _user_manual_command_filter: + +###### +Filter +###### + +**Filter** is a command that adds filters + +retrieves information of the :term:`Topics ` with at least one endpoint currently active in the network. + +Key-words +========= + +These are the key-words recognize as this command: +``filter`` ``filters`` ``f`` ``F``. + +Arguments +========= + +**Filter** command supports from 0 to 3 arguments: + +*No argument* +------------- + +When no arguments are given to this command, the information shown is a **list** with all the list of filters +added during runtime. + +The information shown is divided into lists, one for each category added at runtime. +For each list, the added filters are displayed. + +The output format is as follows: :ref:`user_manual_command_filter_output`. + +*1 argument:* `` +------------------------------ + +- clear: This argument clear all the list of categories added to the filters. +- remove: This argument delete all the list of categories added to the filters. + +*2 argument:* ` ` +----------------------------------------- + +- clear: This argument clear the list "category" added to the filters. +- remove: This argument delete the list "category" from the filters. + +*3 argument:* ` ` +------------------------------------------------------ + +- set: This argument create the `category` filter list with `filter_str` as first element. +- add: This argument add `filter_str` to `category` filter list. +- remove: This argument delete `filter_str` from `category` filter list. + +.. _user_manual_command_filter_output: + +Output Format +============= + +The filters information is retrieved with the following format: + +.. code-block:: yaml + + Filter lists (1) + + category_1 (2): + - filter_str_1 + - filter_str_2 + + category_2 (2): + - filter_str_1 + +Example +======= + +Let's assume we have a DDS network where a ShapesDemo applications is running with +the following 2 DataWriters: +- Circle (partition A) +- Square (partitions B and C). + +This would be the expected output for the following commands: + +- ``filter set partitions A``: + +Nothing, the category "partitions" is created with filter "A" as first element. + +- ``filters``: + +.. code-block:: + + Filter lists (1) + + partitions (1): + - A + +- ``topics vv``: + +.. code-block:: + + - name: Circle + type: ShapeType + datawriters: + - 01.0f.72.e4.86.f3.9b.a0.00.00.00.00|0.0.6.2 [A] + rate: 12.5391 Hz + dynamic_type_discovered: true + + +- ``filters add partitions B``: + +Nothing, the filter "B" is added to the category "partitions". + +- ``filters``: + +.. code-block:: + + Filter lists (1) + + partitions (2): + - A + - B + +- ``topics vv``: + +.. code-block:: + + - name: Circle + type: ShapeType + datawriters: + - 01.0f.72.e4.86.f3.9b.a0.00.00.00.00|0.0.6.2 [A] + rate: 12.5391 Hz + dynamic_type_discovered: true + - name: Square + type: ShapeType + datawriters: + - 01.0f.72.e4.86.f3.9b.a0.00.00.00.00|0.0.7.2 [B|C] + rate: 12.5391 Hz + dynamic_type_discovered: true + +- ``filters remove partitions B``: + +Nothing, the filter "B" is removed from the category "partitions". + +- ``topics vv``: + +.. code-block:: + + - name: Circle + type: ShapeType + datawriters: + - 01.0f.72.e4.86.f3.9b.a0.00.00.00.00|0.0.6.2 [A] + rate: 12.5391 Hz + dynamic_type_discovered: true + +- ``filters``: + +.. code-block:: + + Filter lists (1) + + partitions (1): + - A + +- ``filter clear``: + +- ``filters``: + +.. code-block:: + + Filter lists (0) diff --git a/docs/rst/user_manual/commands/topic.rst b/docs/rst/user_manual/commands/topic.rst index e5ad58cf..b123b90c 100644 --- a/docs/rst/user_manual/commands/topic.rst +++ b/docs/rst/user_manual/commands/topic.rst @@ -93,10 +93,10 @@ It adds the Guid of each endpoint on the topic and the whether the type has been name: type: datawriters: - - + - [] - ... datareaders: - - + - [] - ... rate: Hz dynamic_type_discovered: @@ -108,39 +108,46 @@ Let's assume we have a DDS network where 2 ShapesDemo applications are running. This would be the expected output for the command ``topics``: +.. code-block:: + + - topic: Circle (ShapeType) (1|1) [13.0298 Hz] + - topic: Square (ShapeType) (2|2) [26.6975 Hz] + +This would be the expected output for the command ``topics v``: + .. code-block:: - name: Circle type: ShapeType - datawriters: 2 - datareaders: 2 + datawriters: 1 + datareaders: 1 rate: 13.0298 Hz - name: Square type: ShapeType - datawriters: 3 + datawriters: 2 datareaders: 2 rate: 26.6975 Hz -This would be the expected output for the command ``topics verbose``: +This would be the expected output for the command ``topics vv``: .. code-block:: - name: Circle type: ShapeType datawriters: - - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.3.2 + - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.3.2 ["A"] datareaders: - - 01.0f.44.59.c9.65.78.e5.00.00.00.00|0.0.2.7 + - 01.0f.44.59.c9.65.78.e5.00.00.00.00|0.0.2.7 ["A"] rate: 13.0286 Hz dynamic_type_discovered: true - name: Square type: ShapeType datawriters: - - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.1.2 - - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.2.2 + - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.1.2 ["A"] + - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.2.2 ["A|B"] datareaders: - - 01.0f.44.59.21.58.14.d2.00.00.00.00|0.0.2.7 - - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.4.7 + - 01.0f.44.59.21.58.14.d2.00.00.00.00|0.0.2.7 ["A"] + - 01.0f.44.59.da.57.de.ec.00.00.00.00|0.0.4.7 ["B"] rate: 26.685 Hz dynamic_type_discovered: true diff --git a/docs/rst/user_manual/usage_example.rst b/docs/rst/user_manual/usage_example.rst index a75ccbe6..221d7a04 100644 --- a/docs/rst/user_manual/usage_example.rst +++ b/docs/rst/user_manual/usage_example.rst @@ -97,10 +97,10 @@ Try out all the commands DDS Spy has to offer: - name: Circle type: ShapeType datawriters: - - 01.0f.93.86.fb.9a.7f.92.00.00.02.00|0.0.3.2 + - 01.0f.93.86.fb.9a.7f.92.00.00.02.00|0.0.3.2 ["A|B"] datareaders: - - 01.0f.93.86.fb.9a.7f.92.00.00.02.00|0.0.1.7 - - 01.0f.93.86.fb.9a.7f.92.00.00.02.00|0.0.2.7 + - 01.0f.93.86.fb.9a.7f.92.00.00.02.00|0.0.1.7 ["A"] + - 01.0f.93.86.fb.9a.7f.92.00.00.02.00|0.0.2.7 ["B"] rate: 11.2986 Hz dynamic_type_discovered: false @@ -127,27 +127,36 @@ Try out all the commands DDS Spy has to offer: Fast DDS Spy is an interactive CLI that allow to instrospect DDS networks. Each command shows data related with the network in Yaml format. Commands available and the information they show: - help : this help. - version : tool version. - quit : exit interactive CLI and close program. - participants : DomainParticipants discovered in the network. - participants verbose : verbose information about DomainParticipants discovered in the network. - participants : verbose information related with a specific DomainParticipant. - writers : DataWriters discovered in the network. - writers verbose : verbose information about DataWriters discovered in the network. - writers : verbose information related with a specific DataWriter. - reader : DataReaders discovered in the network. - reader verbose : verbose information about DataReaders discovered in the network. - reader : verbose information related with a specific DataReader. - topics : Topics discovered in the network in compact format. - topics v : Topics discovered in the network. - topics vv : verbose information about Topics discovered in the network. - topics : Topics discovered in the network filtered by name (wildcard allowed (*)). - echo : data of a specific Topic (Data Type must be discovered). - echo : data of a Topics matching the wildcard name (Data Type must be discovered). - echo verbose : data with additional source info of a specific Topic. - echo verbose : data with additional source info of Topics matching the topic name (wildcard allowed (*)). - echo all : verbose data of all topics (only those whose Data Type is discovered). + help : this help. + version : tool version. + quit : exit interactive CLI and close program. + participants : DomainParticipants discovered in the network. + participants verbose : verbose information about DomainParticipants discovered in the network. + participants : verbose information related with a specific DomainParticipant. + writers : DataWriters discovered in the network. + writers verbose : verbose information about DataWriters discovered in the network. + writers : verbose information related with a specific DataWriter. + reader : DataReaders discovered in the network. + reader verbose : verbose information about DataReaders discovered in the network. + reader : verbose information related with a specific DataReader. + topics : Topics discovered in the network in compact format. + topics v : Topics discovered in the network. + topics vv : verbose information about Topics discovered in the network. + topics : Topics discovered in the network filtered by name (wildcard allowed (*)). + topics idl : Display the IDL type definition for topics matching (wildcards allowed). + filters : Display the active filters. + filters clear : Clear all the filter lists. + filters remove : Remove all the filter lists. + filter clear : Clear filter list. + filter remove : Remove filter list. + filter set : Set filter list with as first value. + filter add : Add in filter list. + filter remove : Remove in filter list. + echo : data of a specific Topic (Data Type must be discovered). + echo : data of Topics matching the wildcard name (and whose Data Type is discovered). + echo verbose : data with additional source info of a specific Topic. + echo verbose : data with additional source info of Topics matching the topic name (wildcard allowed (*)). + echo all : verbose data of all topics (only those whose Data Type is discovered). Notes and comments: To exit from data printing, press enter. diff --git a/fastddsspy_participants/include/fastddsspy_participants/participant/SpyDdsParticipant.hpp b/fastddsspy_participants/include/fastddsspy_participants/participant/SpyDdsParticipant.hpp index e4446374..b58cca96 100644 --- a/fastddsspy_participants/include/fastddsspy_participants/participant/SpyDdsParticipant.hpp +++ b/fastddsspy_participants/include/fastddsspy_participants/participant/SpyDdsParticipant.hpp @@ -54,6 +54,12 @@ class SpyDdsParticipant : public ddspipe::participants::DynTypesParticipant std::shared_ptr create_reader( const ddspipe::core::ITopic& topic) override; + //! Override create_reader_() IParticipant method + FASTDDSSPY_PARTICIPANTS_DllAPI + std::shared_ptr create_reader_with_filter( + const ddspipe::core::ITopic& topic, + const std::set partitions) override; + class SpyDdsParticipantListener : public ddspipe::participants::DynTypesParticipant::DynTypesRtpsListener { public: diff --git a/fastddsspy_participants/include/fastddsspy_participants/visualization/parser_data.hpp b/fastddsspy_participants/include/fastddsspy_participants/visualization/parser_data.hpp index afb863f6..37aba70d 100644 --- a/fastddsspy_participants/include/fastddsspy_participants/visualization/parser_data.hpp +++ b/fastddsspy_participants/include/fastddsspy_participants/visualization/parser_data.hpp @@ -65,6 +65,7 @@ struct ComplexEndpointData { std::string topic_name; std::string topic_type; + std::string partition; }; struct QoS @@ -99,6 +100,7 @@ struct ComplexTopicData struct Endpoint { ddspipe::core::types::Guid guid; + std::string partition; }; std::string name; @@ -113,6 +115,7 @@ struct DdsDataData { SimpleEndpointData::Topic topic; ddspipe::core::types::Guid writer; + std::string partitions; TimestampData timestamp; }; diff --git a/fastddsspy_participants/src/cpp/participant/SpyDdsParticipant.cpp b/fastddsspy_participants/src/cpp/participant/SpyDdsParticipant.cpp index 266e3d32..08ed767b 100644 --- a/fastddsspy_participants/src/cpp/participant/SpyDdsParticipant.cpp +++ b/fastddsspy_participants/src/cpp/participant/SpyDdsParticipant.cpp @@ -60,6 +60,28 @@ std::shared_ptr SpyDdsParticipant::create_reader( return ddspipe::participants::DynTypesParticipant::create_reader(topic); } +std::shared_ptr SpyDdsParticipant::create_reader_with_filter( + const ddspipe::core::ITopic& topic, + const std::set partitions) +{ + // If participant info topic, return the internal reader for it + if (is_participant_info_topic(topic)) + { + return this->participants_reader_; + } + + // If endpoint info topic, return the internal reader for it + if (is_endpoint_info_topic(topic)) + { + return this->endpoints_reader_; + } + + bool tmp; + + // If not type object, use the parent method + return ddspipe::participants::DynTypesParticipant::create_reader_with_filter(topic, partitions); +} + SpyDdsParticipant::SpyDdsParticipantListener::SpyDdsParticipantListener( std::shared_ptr conf, std::shared_ptr ddb, diff --git a/fastddsspy_participants/src/cpp/testing/random_values.cpp b/fastddsspy_participants/src/cpp/testing/random_values.cpp index 8d11611f..75c972fe 100644 --- a/fastddsspy_participants/src/cpp/testing/random_values.cpp +++ b/fastddsspy_participants/src/cpp/testing/random_values.cpp @@ -51,6 +51,11 @@ void random_endpoint_info( endpoint_data.info.topic = topic; endpoint_data.info.discoverer_participant_id = ddspipe::core::testing::random_participant_id(seed); endpoint_data.type_idl = "type_idl"; + // add empty partition + endpoint_data.info.specific_partitions = std::map(); + std::ostringstream ss; + ss << endpoint_data.info.guid; + endpoint_data.info.specific_partitions[ss.str()] = ""; // empty partition } } /* namespace participants */ diff --git a/fastddsspy_participants/src/cpp/visualization/ModelParser.cpp b/fastddsspy_participants/src/cpp/visualization/ModelParser.cpp index 957c855e..21702d38 100644 --- a/fastddsspy_participants/src/cpp/visualization/ModelParser.cpp +++ b/fastddsspy_participants/src/cpp/visualization/ModelParser.cpp @@ -15,6 +15,7 @@ #include #include +#include #include @@ -182,6 +183,16 @@ void fill_complex_endpoint( result.topic.topic_type = model.get_ros2_types() ? utils::demangle_if_ros_type(endpoint.topic.type_name) : endpoint.topic. type_name; + // partition + std::ostringstream guid_ss; + guid_ss << endpoint.guid; // get the source guid + const auto partition_it = endpoint.specific_partitions.find(guid_ss.str()); + if (partition_it != endpoint.specific_partitions.end()) + { + // the endpoint has a partition set + result.topic.partition = partition_it->second; + } + result.qos.durability = endpoint.topic.topic_qos.durability_qos; result.qos.reliability = endpoint.topic.topic_qos.reliability_qos; } @@ -298,10 +309,12 @@ std::set ModelParser::get_topics( std::set result; for (const auto& endpoint : model.endpoint_database_) { - if (endpoint.second.info.active && filter_topic.matches(endpoint.second.info.topic)) + if (!endpoint.second.info.active || !filter_topic.matches(endpoint.second.info.topic)) { - result.insert(endpoint.second.info.topic); + continue; } + + result.insert(endpoint.second.info.topic); } return result; } @@ -356,13 +369,24 @@ ComplexTopicData ModelParser::complex_topic_data( { if (it.second.info.active && topic.m_topic_name == it.second.info.topic.m_topic_name) { + // add partitions + std::ostringstream guid_ss; + guid_ss << it.first; // get the source guid + const auto partition_it = it.second.info.specific_partitions.find(guid_ss.str()); + std::string partition = ""; + if (partition_it != it.second.info.specific_partitions.end()) + { + // the endpoint has a partition set + partition = partition_it->second; + } + if (it.second.info.is_reader()) { - result.datareaders.push_back({it.first}); + result.datareaders.push_back({it.first, partition}); } if (it.second.info.is_writer()) { - result.datawriters.push_back({it.first}); + result.datawriters.push_back({it.first, partition}); } } } diff --git a/fastddsspy_participants/test/unittest/visualization/CMakeLists.txt b/fastddsspy_participants/test/unittest/visualization/CMakeLists.txt index c7e985e4..3402b03f 100644 --- a/fastddsspy_participants/test/unittest/visualization/CMakeLists.txt +++ b/fastddsspy_participants/test/unittest/visualization/CMakeLists.txt @@ -68,6 +68,42 @@ set(TEST_LIST complex_topic_dds_endpoints_ros2_types complex_topic_ros2_endpoints complex_topic_ros2_endpoints_ros2_types + simple_dds_endpoint_writer_filtered + simple_dds_endpoint_writer_ros2_types_filtered + simple_ros2_endpoint_writer_filtered + simple_ros2_endpoint_writer_ros2_types_filtered + simple_dds_endpoint_reader_filtered + simple_dds_endpoint_reader_ros2_types_filtered + simple_ros2_endpoint_reader_filtered + simple_ros2_endpoint_reader_ros2_types_filtered + dds_endpoint_reader_verbose_filtered + dds_endpoint_reader_verbose_ros2_types_filtered + ros2_endpoint_reader_verbose_filtered + ros2_endpoint_reader_verbose_ros2_types_filtered + dds_endpoint_writer_verbose_filtered + dds_endpoint_writer_verbose_ros2_types_filtered + ros2_endpoint_writer_verbose_filtered + ros2_endpoint_writer_verbose_ros2_types_filtered + complex_dds_endpoint_writer_filtered + complex_dds_endpoint_writer_ros2_types_filtered + complex_ros2_endpoint_writer_filtered + complex_ros2_endpoint_writer_ros2_types_filtered + complex_dds_endpoint_reader_filtered + complex_dds_endpoint_reader_ros2_types_filtered + complex_ros2_endpoint_reader_filtered + complex_ros2_endpoint_reader_ros2_types_filtered + simple_topic_dds_endpoints_filtered + simple_topic_dds_endpoints_ros2_types_filtered + simple_topic_ros2_endpoints_filtered + simple_topic_ros2_endpoints_ros2_types_filtered + topics_verbose_dds_endpoints_filtered + topics_verbose_dds_endpoints_ros2_types_filtered + topics_verbose_ros2_endpoints_filtered + topics_verbose_ros2_endpoints_ros2_types_filtered + complex_topic_dds_endpoints_filtered + complex_topic_dds_endpoints_ros2_types_filtered + complex_topic_ros2_endpoints_filtered + complex_topic_ros2_endpoints_ros2_types_filtered ) set(TEST_EXTRA_LIBRARIES diff --git a/fastddsspy_participants/test/unittest/visualization/ModelParserTest.cpp b/fastddsspy_participants/test/unittest/visualization/ModelParserTest.cpp index c1d07e75..d1de4bf0 100644 --- a/fastddsspy_participants/test/unittest/visualization/ModelParserTest.cpp +++ b/fastddsspy_participants/test/unittest/visualization/ModelParserTest.cpp @@ -67,6 +67,37 @@ std::vector fill_database_endpoints( return endpoints; } +std::vector fill_database_endpoints_filtered( + spy::participants::SpyModel& model, + int n_readers, + int n_writers, + ddspipe::core::types::DdsTopic topic = ddspipe::core::testing::random_dds_topic()) +{ + bool active = true; + // Fill model + std::vector endpoints; + for (int i = 0; i < n_readers; i++) + { + spy::participants::EndpointInfoData endpoint_reader; + spy::participants::random_endpoint_info(endpoint_reader, ddspipe::core::types::EndpointKind::reader, active, i, + topic); + model.endpoint_database_.add(endpoint_reader.info.guid, endpoint_reader); + endpoints.push_back(endpoint_reader); + active = !active; + } + + for (int i = 0; i < n_writers; i++) + { + spy::participants::EndpointInfoData endpoint_writer; + spy::participants::random_endpoint_info(endpoint_writer, ddspipe::core::types::EndpointKind::writer, active, + n_readers + i, topic); + model.endpoint_database_.add(endpoint_writer.info.guid, endpoint_writer); + endpoints.push_back(endpoint_writer); + active = !active; + } + return endpoints; +} + /********* * TESTS ** *********/ @@ -2180,13 +2211,22 @@ TEST(ModelParserTest, topics_verbose_dds_endpoints) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } fill_expected_result.name = topic.m_topic_name; @@ -2249,13 +2289,22 @@ TEST(ModelParserTest, topics_verbose_dds_endpoints_ros2_types) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } fill_expected_result.name = topic.m_topic_name; @@ -2321,13 +2370,22 @@ TEST(ModelParserTest, topics_verbose_ros2_endpoints) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } fill_expected_result.name = topic.m_topic_name; @@ -2393,13 +2451,22 @@ TEST(ModelParserTest, topics_verbose_ros2_endpoints_ros2_types) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } fill_expected_result.name = utils::demangle_if_ros_topic(topic.m_topic_name); @@ -2459,13 +2526,22 @@ TEST(ModelParserTest, complex_topic_dds_endpoints) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } spy::participants::ComplexTopicData expected_result; @@ -2519,13 +2595,22 @@ TEST(ModelParserTest, complex_topic_dds_endpoints_ros2_types) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } spy::participants::ComplexTopicData expected_result; @@ -2582,13 +2667,22 @@ TEST(ModelParserTest, complex_topic_ros2_endpoints) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } spy::participants::ComplexTopicData expected_result; @@ -2645,13 +2739,22 @@ TEST(ModelParserTest, complex_topic_ros2_endpoints_ros2_types) std::vector datareaders; for (const auto& it : endpoints) { + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + if (it.info.is_reader()) { - datareaders.push_back({it.info.guid}); + datareaders.push_back({it.info.guid, partition}); } if (it.info.is_writer()) { - datawriters.push_back({it.info.guid}); + datawriters.push_back({it.info.guid, partition}); } } spy::participants::ComplexTopicData expected_result; @@ -2679,6 +2782,2104 @@ TEST(ModelParserTest, complex_topic_ros2_endpoints_ros2_types) ASSERT_FALSE(result.discovered); } +/** + * Add two DDS writers (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers(). + * Check the result guid, topic name and topic type of the writer who pass. + */ +TEST(ModelParserTest, simple_dds_endpoint_writer_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Endpoints + std::vector endpoints; + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + it.info.topic.m_topic_name, + it.info.topic.type_name + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add two DDS writers (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute writers(). + * Check the result guid, topic name and topic type of the writer who pass. + */ +TEST(ModelParserTest, simple_dds_endpoint_writer_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Endpoints + std::vector endpoints; + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + it.info.topic.m_topic_name, + it.info.topic.type_name + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add a ROS 2 writers (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers(). + * Check the result guid, topic name and topic type of that writer. + */ +TEST(ModelParserTest, simple_ros2_endpoint_writer_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + it.info.topic.m_topic_name, + it.info.topic.type_name + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add a ROS 2 writers (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute writers(). + * Check the result guid, topic name and topic type of that writer. + */ +TEST(ModelParserTest, simple_ros2_endpoint_writer_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + endpoints = fill_database_endpoints_filtered(model, 0, 2, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + utils::demangle_if_ros_topic(it.info.topic.m_topic_name), + utils::demangle_if_ros_type(it.info.topic.type_name) + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + + +/** + * Add a DDS readers (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute readers(). + * Check the result guid, topic name and topic type of that reader. + */ +TEST(ModelParserTest, simple_dds_endpoint_reader_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Endpoints + std::vector endpoints; + // Fill model + endpoints = fill_database_endpoints_filtered(model, 2, 0); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + it.info.topic.m_topic_name, + it.info.topic.type_name + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add two DDS readers (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute readers(). + * Check the result guid, topic name and topic type of that reader. + */ +TEST(ModelParserTest, simple_dds_endpoint_reader_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Endpoints + std::vector endpoints; + // Fill model + endpoints = fill_database_endpoints_filtered(model, 2, 0); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + it.info.topic.m_topic_name, + it.info.topic.type_name + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add two ROS 2 readers (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute readers(). + * Check the result guid, topic name and topic type of that reader. + */ +TEST(ModelParserTest, simple_ros2_endpoint_reader_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + endpoints = fill_database_endpoints_filtered(model, 2, 0, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + it.info.topic.m_topic_name, + it.info.topic.type_name + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add two ROS 2 readers (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute readers(). + * Check the result guid, topic name and topic type of that reader. + */ +TEST(ModelParserTest, simple_ros2_endpoint_reader_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + endpoints = fill_database_endpoints_filtered(model, 2, 0, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.push_back({ + it.info.guid, + it.info.discoverer_participant_id, + { + utils::demangle_if_ros_topic(it.info.topic.m_topic_name), + utils::demangle_if_ros_type(it.info.topic.type_name) + } + + }); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + ASSERT_EQ(result[0].guid, expected_result[0].guid); + ASSERT_EQ(result[0].topic.topic_name, expected_result[0].topic.topic_name); + ASSERT_EQ(result[0].topic.topic_type, expected_result[0].topic.topic_type); +} + +/** + * Add two DDS readers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute readers_verbose(). + * Check the result guid, topic and qos of that reader. + */ +TEST(ModelParserTest, dds_endpoint_reader_verbose_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + endpoints = fill_database_endpoints_filtered(model, 2, 0); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = it.info.topic.m_topic_name; + fill_expected_result.topic.topic_type = it.info.topic.type_name; + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + + ASSERT_EQ(result.size(), expected_result.size()); + + // Check information + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two DDS reader and a participant (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute readers_verbose(). + * Check the result guid, topic and qos of that reader. + */ +TEST(ModelParserTest, dds_endpoint_reader_verbose_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + endpoints = fill_database_endpoints_filtered(model, 2, 0); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = it.info.topic.m_topic_name; + fill_expected_result.topic.topic_type = it.info.topic.type_name; + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + ASSERT_EQ(result.size(), expected_result.size()); + + // Check information + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two ROS 2 readers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute readers_verbose(). + * Check the result guid, topic and qos of the reader who pass. + */ +TEST(ModelParserTest, ros2_endpoint_reader_verbose_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 0, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = it.info.topic.m_topic_name; + fill_expected_result.topic.topic_type = it.info.topic.type_name; + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + ASSERT_EQ(result.size(), expected_result.size()); + + // Check information + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two ROS 2 readers and a participant (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute readers_verbose(). + * Check the result guid, topic and qos of the reader who pass. + */ +TEST(ModelParserTest, ros2_endpoint_reader_verbose_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + endpoints = fill_database_endpoints_filtered(model, 2, 0, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::readers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = utils::demangle_if_ros_topic(it.info.topic.m_topic_name); + fill_expected_result.topic.topic_type = utils::demangle_if_ros_type(it.info.topic.type_name); + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two DDS writers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers_verbose(). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, dds_endpoint_writer_verbose_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = it.info.topic.m_topic_name; + fill_expected_result.topic.topic_type = it.info.topic.type_name; + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two DDS writers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers_verbose(). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, dds_endpoint_writer_verbose_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = it.info.topic.m_topic_name; + fill_expected_result.topic.topic_type = it.info.topic.type_name; + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two ROS 2 writers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers_verbose(). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, ros2_endpoint_writer_verbose_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = it.info.topic.m_topic_name; + fill_expected_result.topic.topic_type = it.info.topic.type_name; + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two ROS 2 writers and a participant (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute writers_verbose(). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, ros2_endpoint_writer_verbose_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + endpoints = fill_database_endpoints_filtered(model, 0, 2, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::writers_verbose(model); + + // Create expected return + std::vector expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + spy::participants::ComplexEndpointData fill_expected_result; + fill_expected_result.guid = it.info.guid; + fill_expected_result.topic.topic_name = utils::demangle_if_ros_topic(it.info.topic.m_topic_name); + fill_expected_result.topic.topic_type = utils::demangle_if_ros_type(it.info.topic.type_name); + fill_expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + fill_expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + expected_result.push_back(fill_expected_result); + } + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.guid, expected_result[i].guid); + ASSERT_EQ(it.topic.topic_name, expected_result[i].topic.topic_name); + ASSERT_EQ(it.topic.topic_type, expected_result[i].topic.topic_type); + ASSERT_EQ(it.qos.durability, expected_result[i].qos.durability); + ASSERT_EQ(it.qos.reliability, expected_result[i].qos.reliability); + + i++; + } + +} + +/** + * Add two DDS writers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers(guid). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, complex_dds_endpoint_writer_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::writers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = it.info.topic.m_topic_name; + expected_result.topic.topic_type = it.info.topic.type_name; + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two DDS writers and a participant (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute writers(guid). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, complex_dds_endpoint_writer_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::writers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = it.info.topic.m_topic_name; + expected_result.topic.topic_type = it.info.topic.type_name; + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two ROS 2 writers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute writers(guid). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, complex_ros2_endpoint_writer_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Fill model + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 0, 2, topic); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::writers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = it.info.topic.m_topic_name; + expected_result.topic.topic_type = it.info.topic.type_name; + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add a ROS 2 writers and a participant (with ros2-types = true) to the database + * and execute writers(guid). + * Check the result guid, topic and qos of the writer who pass. + */ +TEST(ModelParserTest, complex_ros2_endpoint_writer_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + endpoints = fill_database_endpoints_filtered(model, 0, 2, topic); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::writers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = utils::demangle_if_ros_topic(it.info.topic.m_topic_name); + expected_result.topic.topic_type = utils::demangle_if_ros_type(it.info.topic.type_name); + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two DDS readers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute readers(guid). + * Check the result guid, topic and qos of the reader who pass. + */ +TEST(ModelParserTest, complex_dds_endpoint_reader_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 0); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::readers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = it.info.topic.m_topic_name; + expected_result.topic.topic_type = it.info.topic.type_name; + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two DDS readers and a participant (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute readers(guid). + * Check the result guid, topic and qos of the reader who pass. + */ +TEST(ModelParserTest, complex_dds_endpoint_reader_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 0); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::readers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = it.info.topic.m_topic_name; + expected_result.topic.topic_type = it.info.topic.type_name; + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two ROS 2 readers and a participant (with ros2-types = false) to the database + * (1 pass the partition filter the other not) and execute readers(guid). + * Check the result guid, topic and qos of the reader who pass. + */ +TEST(ModelParserTest, complex_ros2_endpoint_reader_filtered) +{ + + // Create model + spy::participants::SpyModel model; + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 0, topic); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::readers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = it.info.topic.m_topic_name; + expected_result.topic.topic_type = it.info.topic.type_name; + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two ROS 2 readers and a participant (with ros2-types = true) to the database + * (1 pass the partition filter the other not) and execute readers(guid). + * Check the result guid, topic and qos of that reader. + */ +TEST(ModelParserTest, complex_ros2_endpoint_reader_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Participant + std::vector participants; + participants = fill_database_participants(model, 1); + // Endpoints + std::vector endpoints; + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + endpoints = fill_database_endpoints_filtered(model, 2, 0, topic); + + // Obtain information from model + spy::participants::ComplexEndpointData result; + result = spy::participants::ModelParser::readers(model, endpoints[0].info.guid); + + // Create expected return + spy::participants::ComplexEndpointData expected_result; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + expected_result.guid = it.info.guid; + expected_result.topic.topic_name = utils::demangle_if_ros_topic(it.info.topic.m_topic_name); + expected_result.topic.topic_type = utils::demangle_if_ros_type(it.info.topic.type_name); + expected_result.qos.durability = it.info.topic.topic_qos.durability_qos; + expected_result.qos.reliability = it.info.topic.topic_qos.reliability_qos; + } + + // Check information + ASSERT_EQ(result.guid, expected_result.guid); + ASSERT_EQ(result.topic.topic_name, expected_result.topic.topic_name); + ASSERT_EQ(result.topic.topic_type, expected_result.topic.topic_type); + ASSERT_EQ(result.qos.durability, expected_result.qos.durability); + ASSERT_EQ(result.qos.reliability, expected_result.qos.reliability); +} + +/** + * Add two DDS readers and four DDS writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, simple_topic_dds_endpoints_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + ddspipe::core::types::DdsTopic topic; + topic = ddspipe::core::testing::random_dds_topic(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + expected_result.push_back({ + topic.m_topic_name, + topic.type_name, + 2, + 1, + { + 10, + "Hz" + } + }); + + // Check information + ASSERT_EQ(result[0].name, expected_result[0].name); + ASSERT_EQ(result[0].type, expected_result[0].type); + ASSERT_EQ(result[0].datawriters, expected_result[0].datawriters); + ASSERT_EQ(result[0].datareaders, expected_result[0].datareaders); + ASSERT_FALSE(result[0].rate.rate); +} + +/** + * Add two DDS readers and four DDS writers (with ros2-types = true) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, simple_topic_dds_endpoints_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + ddspipe::core::types::DdsTopic topic; + topic = ddspipe::core::testing::random_dds_topic(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + expected_result.push_back({ + topic.m_topic_name, + topic.type_name, + 2, + 1, + { + 10, + "Hz" + } + }); + + // Check information + ASSERT_EQ(result[0].name, expected_result[0].name); + ASSERT_EQ(result[0].type, expected_result[0].type); + ASSERT_EQ(result[0].datawriters, expected_result[0].datawriters); + ASSERT_EQ(result[0].datareaders, expected_result[0].datareaders); + ASSERT_FALSE(result[0].rate.rate); +} + +/** + * Add two ROS 2 readers and four ROS 2 writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, simple_topic_ros2_endpoints_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + expected_result.push_back({ + topic.m_topic_name, + topic.type_name, + 2, + 1, + { + 10, + "Hz" + } + }); + + // Check information + ASSERT_EQ(result[0].name, expected_result[0].name); + ASSERT_EQ(result[0].type, expected_result[0].type); + ASSERT_EQ(result[0].datawriters, expected_result[0].datawriters); + ASSERT_EQ(result[0].datareaders, expected_result[0].datareaders); + ASSERT_FALSE(result[0].rate.rate); +} + +/** + * Add two ROS 2 readers and four ROS 2 writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, simple_topic_ros2_endpoints_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Endpoints + std::vector endpoints; + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + expected_result.push_back({ + utils::demangle_if_ros_topic(topic.m_topic_name), + utils::demangle_if_ros_type(topic.type_name), + 2, + 1, + { + 10, + "Hz" + } + }); + + // Check information + ASSERT_EQ(result[0].name, expected_result[0].name); + ASSERT_EQ(result[0].type, expected_result[0].type); + ASSERT_EQ(result[0].datawriters, expected_result[0].datawriters); + ASSERT_EQ(result[0].datareaders, expected_result[0].datareaders); + ASSERT_FALSE(result[0].rate.rate); +} + +/** + * Add two DDS readers and four DDS writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics_verbose(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, topics_verbose_dds_endpoints_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + ddspipe::core::types::DdsTopic topic; + topic = ddspipe::core::testing::random_dds_topic(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics_verbose( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + spy::participants::ComplexTopicData fill_expected_result; + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + fill_expected_result.name = topic.m_topic_name; + fill_expected_result.type = topic.type_name; + fill_expected_result.datawriters = datawriters; + fill_expected_result.datareaders = datareaders; + expected_result.push_back(fill_expected_result); + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.name, expected_result[i].name); + ASSERT_EQ(it.type, expected_result[i].type); + unsigned int l = 0; + for (const auto& datawriter : it.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result[i].datawriters[l].guid); + l++; + } + l = 0; + for (const auto& datareader : it.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result[i].datareaders[l].guid); + l++; + } + ASSERT_FALSE(it.rate.rate); + ASSERT_FALSE(it.discovered); + i++; + } + +} + +/** + * Add two DDS readers and four DDS writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics_verbose(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, topics_verbose_dds_endpoints_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + ddspipe::core::types::DdsTopic topic; + topic = ddspipe::core::testing::random_dds_topic(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics_verbose( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + spy::participants::ComplexTopicData fill_expected_result; + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + fill_expected_result.name = topic.m_topic_name; + fill_expected_result.type = topic.type_name; + fill_expected_result.datawriters = datawriters; + fill_expected_result.datareaders = datareaders; + expected_result.push_back(fill_expected_result); + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.name, expected_result[i].name); + ASSERT_EQ(it.type, expected_result[i].type); + unsigned int l = 0; + for (const auto& datawriter : it.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result[i].datawriters[l].guid); + l++; + } + l = 0; + for (const auto& datareader : it.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result[i].datareaders[l].guid); + l++; + } + ASSERT_FALSE(it.rate.rate); + ASSERT_FALSE(it.discovered); + i++; + } + +} + +/** + * Add two ROS 2 readers and four ROS 2 writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics_verbose(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, topics_verbose_ros2_endpoints_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics_verbose( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + spy::participants::ComplexTopicData fill_expected_result; + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + fill_expected_result.name = topic.m_topic_name; + fill_expected_result.type = topic.type_name; + fill_expected_result.datawriters = datawriters; + fill_expected_result.datareaders = datareaders; + expected_result.push_back(fill_expected_result); + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.name, expected_result[i].name); + ASSERT_EQ(it.type, expected_result[i].type); + unsigned int l = 0; + for (const auto& datawriter : it.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result[i].datawriters[l].guid); + l++; + } + l = 0; + for (const auto& datareader : it.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result[i].datareaders[l].guid); + l++; + } + ASSERT_FALSE(it.rate.rate); + ASSERT_FALSE(it.discovered); + i++; + } + +} + +/** + * Add two ROS 2 readers and four ROS 2 writers (with ros2-types = true) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics_verbose(). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, topics_verbose_ros2_endpoints_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Endpoints + std::vector endpoints; + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + std::vector result; + result = spy::participants::ModelParser::topics_verbose( + model, ddspipe::core::types::WildcardDdsFilterTopic()); + + // Create expected return + std::vector expected_result; + spy::participants::ComplexTopicData fill_expected_result; + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + fill_expected_result.name = utils::demangle_if_ros_topic(topic.m_topic_name); + fill_expected_result.type = utils::demangle_if_ros_type(topic.type_name); + fill_expected_result.datawriters = datawriters; + fill_expected_result.datareaders = datareaders; + expected_result.push_back(fill_expected_result); + + // Check information + ASSERT_EQ(result.size(), expected_result.size()); + + unsigned int i = 0; + for (const auto& it : result) + { + ASSERT_EQ(it.name, expected_result[i].name); + ASSERT_EQ(it.type, expected_result[i].type); + unsigned int l = 0; + for (const auto& datawriter : it.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result[i].datawriters[l].guid); + l++; + } + l = 0; + for (const auto& datareader : it.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result[i].datareaders[l].guid); + l++; + } + ASSERT_FALSE(it.rate.rate); + ASSERT_FALSE(it.discovered); + i++; + } + +} + +/** + * Add two DDS readers and four DDS writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(name). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, complex_topic_dds_endpoints_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + ddspipe::core::types::DdsTopic topic; + topic = ddspipe::core::testing::random_dds_topic(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + spy::participants::ComplexTopicData result; + result = spy::participants::ModelParser::complex_topic_data(model, topic); + + // Create expected return + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + spy::participants::ComplexTopicData expected_result; + expected_result.name = topic.m_topic_name; + expected_result.type = topic.type_name; + expected_result.datawriters = datawriters; + expected_result.datareaders = datareaders; + + // Check information + ASSERT_EQ(result.name, expected_result.name); + ASSERT_EQ(result.type, expected_result.type); + unsigned int i = 0; + for (const auto& datawriter : result.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result.datawriters[i].guid); + i++; + } + i = 0; + for (const auto& datareader : result.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result.datareaders[i].guid); + i++; + } + ASSERT_FALSE(result.rate.rate); + ASSERT_FALSE(result.discovered); +} + +/** + * Add two DDS readers and four DDS writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(name). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, complex_topic_dds_endpoints_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + ddspipe::core::types::DdsTopic topic; + topic = ddspipe::core::testing::random_dds_topic(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + spy::participants::ComplexTopicData result; + result = spy::participants::ModelParser::complex_topic_data(model, topic); + + // Create expected return + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + spy::participants::ComplexTopicData expected_result; + expected_result.name = topic.m_topic_name; + expected_result.type = topic.type_name; + expected_result.datawriters = datawriters; + expected_result.datareaders = datareaders; + + //ASSERT_EQ(result.size(), expected_result.size()); + + // Check information + ASSERT_EQ(result.name, expected_result.name); + ASSERT_EQ(result.type, expected_result.type); + unsigned int i = 0; + for (const auto& datawriter : result.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result.datawriters[i].guid); + i++; + } + i = 0; + for (const auto& datareader : result.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result.datareaders[i].guid); + i++; + } + ASSERT_FALSE(result.rate.rate); + ASSERT_FALSE(result.discovered); +} + +/** + * Add two ROS 2 readers and four ROS 2 writers (with ros2-types = false) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(name). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, complex_topic_ros2_endpoints_filtered) +{ + // Create model + spy::participants::SpyModel model; + // Fill model + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + spy::participants::ComplexTopicData result; + result = spy::participants::ModelParser::complex_topic_data(model, topic); + + // Create expected return + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + spy::participants::ComplexTopicData expected_result; + expected_result.name = topic.m_topic_name; + expected_result.type = topic.type_name; + expected_result.datawriters = datawriters; + expected_result.datareaders = datareaders; + + //ASSERT_EQ(result.size(), expected_result.size()); + + // Check information + ASSERT_EQ(result.name, expected_result.name); + ASSERT_EQ(result.type, expected_result.type); + unsigned int i = 0; + for (const auto& datawriter : result.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result.datawriters[i].guid); + i++; + } + i = 0; + for (const auto& datareader : result.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result.datareaders[i].guid); + i++; + } + ASSERT_FALSE(result.rate.rate); + ASSERT_FALSE(result.discovered); +} + +/** + * Add two ROS 2 readers and four ROS 2 writers (with ros2-types = true) with the same topic + * to the database (half of them pass the filter, the other half not) and execute topics(name). + * Check the result name, type, writers and readers + * of that topic. + */ +TEST(ModelParserTest, complex_topic_ros2_endpoints_ros2_types_filtered) +{ + // Create model + spy::participants::SpyModel model(true); + // Fill model + // Topic + ddspipe::core::types::DdsTopic topic; + topic.m_topic_name = "rt/hello"; + topic.type_name = "std_msgs::msg::dds_::String_"; + topic.topic_qos = ddspipe::core::testing::random_topic_qos(); + // Endpoints + std::vector endpoints; + // (simulate that the half of the endpoints do not pass the partition filter) + endpoints = fill_database_endpoints_filtered(model, 2, 4, topic); + + // Obtain information from model + spy::participants::ComplexTopicData result; + result = spy::participants::ModelParser::complex_topic_data(model, topic); + + // Create expected return + std::vector datawriters; + std::vector datareaders; + for (const auto& it : endpoints) + { + if (!it.info.active) + { + continue; + } + + std::ostringstream ss; + ss << it.info.guid; + std::string partition = ""; + const auto partition_it = it.info.specific_partitions.find(ss.str()); + if (partition_it != it.info.specific_partitions.end()) + { + partition = partition_it->second; + } + + if (it.info.is_reader()) + { + datareaders.push_back({it.info.guid, partition}); + } + if (it.info.is_writer()) + { + datawriters.push_back({it.info.guid, partition}); + } + } + spy::participants::ComplexTopicData expected_result; + expected_result.name = utils::demangle_if_ros_topic(topic.m_topic_name); + expected_result.type = utils::demangle_if_ros_type(topic.type_name); + expected_result.datawriters = datawriters; + expected_result.datareaders = datareaders; + + + //ASSERT_EQ(result.size(), expected_result.size()); + + // Check information + ASSERT_EQ(result.name, expected_result.name); + ASSERT_EQ(result.type, expected_result.type); + unsigned int i = 0; + for (const auto& datawriter : result.datawriters) + { + ASSERT_EQ(datawriter.guid, expected_result.datawriters[i].guid); + i++; + } + i = 0; + for (const auto& datareader : result.datareaders) + { + ASSERT_EQ(datareader.guid, expected_result.datareaders[i].guid); + i++; + } + ASSERT_FALSE(result.rate.rate); + ASSERT_FALSE(result.discovered); +} + + + + int main( int argc, char** argv) diff --git a/fastddsspy_tool/src/cpp/tool/Backend.cpp b/fastddsspy_tool/src/cpp/tool/Backend.cpp index 4a495e43..130ce316 100644 --- a/fastddsspy_tool/src/cpp/tool/Backend.cpp +++ b/fastddsspy_tool/src/cpp/tool/Backend.cpp @@ -150,5 +150,18 @@ std::shared_ptr Backend::model() const no return model_; } +void Backend::update_readers_track( + const std::string topic_name, + const std::set filter_partition_set) +{ + pipe_->update_readers_track(topic_name, filter_partition_set); +} + +void Backend::update_pipeline_filter( + const std::set filter_partition_set) +{ + pipe_->update_filter(filter_partition_set); +} + } /* namespace spy */ } /* namespace eprosima */ diff --git a/fastddsspy_tool/src/cpp/tool/Backend.hpp b/fastddsspy_tool/src/cpp/tool/Backend.hpp index 758e7b51..dad87012 100644 --- a/fastddsspy_tool/src/cpp/tool/Backend.hpp +++ b/fastddsspy_tool/src/cpp/tool/Backend.hpp @@ -81,6 +81,13 @@ class Backend */ std::shared_ptr model() const noexcept; + void update_readers_track( + const std::string topic_name, + const std::set filter_partition_set); + + void update_pipeline_filter( + const std::set filter_partition_set); + protected: /** diff --git a/fastddsspy_tool/src/cpp/tool/Command.hpp b/fastddsspy_tool/src/cpp/tool/Command.hpp index bd0b194a..09ccf8e7 100644 --- a/fastddsspy_tool/src/cpp/tool/Command.hpp +++ b/fastddsspy_tool/src/cpp/tool/Command.hpp @@ -31,7 +31,8 @@ ENUMERATION_BUILDER( datareader, topic, print, - error_input + error_input, + filter ); eProsima_ENUMERATION_BUILDER( @@ -47,6 +48,7 @@ eProsima_ENUMERATION_BUILDER( { CommandValue::datareader COMMA {"datareader" COMMA "datareaders" COMMA "r" COMMA "R" COMMA "reader" COMMA "readers" COMMA "subscription" COMMA "subscriptions"}} COMMA { CommandValue::topic COMMA {"topic" COMMA "topics" COMMA "t" COMMA "T"}} COMMA { CommandValue::print COMMA {"echo" COMMA "print" COMMA "show" COMMA "s" COMMA "S"}} COMMA + { CommandValue::filter COMMA {"filter" COMMA "filters" COMMA "partitions" COMMA "f" COMMA "F"}} COMMA } ); diff --git a/fastddsspy_tool/src/cpp/tool/Controller.cpp b/fastddsspy_tool/src/cpp/tool/Controller.cpp index 6a645259..8b9f4565 100644 --- a/fastddsspy_tool/src/cpp/tool/Controller.cpp +++ b/fastddsspy_tool/src/cpp/tool/Controller.cpp @@ -144,6 +144,10 @@ void Controller::run() while (command.command != CommandValue::exit) { command = input_.wait_next_command(); + // refresh the database if a filter partition is active. + // this checks if there is a new endpoint that does not + // pass the filter and disable it + refresh_database(); run_command_(command); } } @@ -151,10 +155,110 @@ void Controller::run() void Controller::one_shot_run( const std::vector& args) { - utils::sleep_for(configuration_.one_shot_wait_time_ms); + utils::sleep_for (configuration_.one_shot_wait_time_ms); run_command_(input_.parse_as_command(args)); } +void Controller::refresh_database() +{ + // vector of inactive endpoints to disable it after + // the filter is applied in all active endpoints + std::vector v_guid_disable; + + // check if there is a filter partition + if (filter_dict.find("partitions") != filter_dict.end()) + { + for (const auto& endpoint: model_->endpoint_database_) + { + if (!endpoint.second.info.active) + { + // the curent endpoint is not active + continue; + } + + // get the source guid of the endpoint + std::ostringstream ss_guid; + ss_guid << endpoint.second.info.guid; + + // get the partition set of the endpoint + std::string endpoint_partitions, curr_partition; + int i, n; + bool pass = false; + + endpoint_partitions = endpoint.second.info.specific_partitions.find(ss_guid.str())->second; + curr_partition = ""; + i = 0; + n = endpoint_partitions.size(); + // iterate to separete the partitions from the set + while (i < n) + { + if (endpoint_partitions[i] == '|') + { + // check if the current partition is in the filter + for (std::string filter_p: filter_dict["partitions"]) + { + if (utils::match_pattern(filter_p, curr_partition) || + utils::match_pattern(curr_partition, filter_p)) + { + // the current partition matches + // with one partition of the filter + pass = true; + break; + } + } + + if (pass) + { + // it is not necessary to check other partitions + // already satisfies the filter partition + break; + } + + // reset and check more + curr_partition = ""; + } + else + { + curr_partition += endpoint_partitions[i]; + } + + i++; + } + + // empty or last partition of the set + for (std::string filter_p: filter_dict["partitions"]) + { + if (utils::match_pattern(filter_p, curr_partition) || + utils::match_pattern(curr_partition, filter_p)) + { + // matches with one partition of the filter + pass = true; + break; + } + } + + if (!pass) + { + // the active endpoint does not pass the filter + // (this endpoint is recently discovered + // without using a filter command) + v_guid_disable.push_back(endpoint.first); + } + + } + + // disable the endpoints that did not pass the filter + for (const auto& curr_guid: v_guid_disable) + { + // get the endpoint associated with the guid and disable it. + auto endpoint_tmp = model_->endpoint_database_.find(curr_guid)->second; + endpoint_tmp.info.active = false; + // modify with the new active value + model_->endpoint_database_.add_or_modify(curr_guid, endpoint_tmp); + } + } +} + utils::ReturnCode Controller::reload_configuration( yaml::Configuration& new_configuration) { @@ -198,6 +302,10 @@ void Controller::run_command_( error_command_(command.arguments); break; + case CommandValue::filter: + filter_command_(command.arguments); + break; + default: break; } @@ -257,10 +365,22 @@ void Controller::data_stream_callback_verbose_( // Block entrance so prints does not collapse std::lock_guard _(view_mutex_); + // get the source guid + std::ostringstream guid_ss; + std::string partitions = ""; + guid_ss << data.source_guid; + const auto partition_it = topic.partition_name.find(guid_ss.str()); + if (partition_it != topic.partition_name.end()) + { + // add the partition set + partitions = partition_it->second; + } + // Prepare info data participants::DdsDataData data_info{ {topic.m_topic_name, topic.type_name}, data.source_guid, + partitions, data.source_timestamp }; @@ -614,7 +734,7 @@ void Controller::print_command_( model_->deactivate(); // Small delay to allow stdout to flush and avoid prompt overlap - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for (std::chrono::milliseconds(100)); } void Controller::version_command_( @@ -634,38 +754,46 @@ void Controller::help_command_( << "Fast DDS Spy is an interactive CLI that allow to instrospect DDS networks.\n" << "Each command shows data related with the network in Yaml format.\n" << "Commands available and the information they show:\n" - << "\thelp : this help.\n" - << "\tversion : tool version.\n" - << "\tquit : exit interactive CLI and close program.\n" - << "\tparticipants : DomainParticipants discovered in the network.\n" + << "\thelp : this help.\n" + << "\tversion : tool version.\n" + << "\tquit : exit interactive CLI and close program.\n" + << "\tparticipants : DomainParticipants discovered in the network.\n" << - "\tparticipants verbose : verbose information about DomainParticipants discovered in the network.\n" - << "\tparticipants : verbose information related with a specific DomainParticipant.\n" - << "\twriters : DataWriters discovered in the network.\n" - << "\twriters verbose : verbose information about DataWriters discovered in the network.\n" - << "\twriters : verbose information related with a specific DataWriter.\n" - << "\treader : DataReaders discovered in the network.\n" - << "\treader verbose : verbose information about DataReaders discovered in the network.\n" - << "\treader : verbose information related with a specific DataReader.\n" - << "\ttopics : Topics discovered in the network in compact format.\n" - << "\ttopics v : Topics discovered in the network.\n" - << "\ttopics vv : verbose information about Topics discovered in the network.\n" + "\tparticipants verbose : verbose information about DomainParticipants discovered in the network.\n" + << "\tparticipants : verbose information related with a specific DomainParticipant.\n" + << "\twriters : DataWriters discovered in the network.\n" + << "\twriters verbose : verbose information about DataWriters discovered in the network.\n" + << "\twriters : verbose information related with a specific DataWriter.\n" + << "\treader : DataReaders discovered in the network.\n" + << "\treader verbose : verbose information about DataReaders discovered in the network.\n" + << "\treader : verbose information related with a specific DataReader.\n" + << "\ttopics : Topics discovered in the network in compact format.\n" + << "\ttopics v : Topics discovered in the network.\n" + << "\ttopics vv : verbose information about Topics discovered in the network.\n" << - "\ttopics : Topics discovered in the network filtered by name (wildcard allowed (*)).\n" + "\ttopics : Topics discovered in the network filtered by name (wildcard allowed (*)).\n" << - "\ttopics idl : Display the IDL type definition for topics matching (wildcards allowed).\n" - << "\techo : data of a specific Topic (Data Type must be discovered).\n" + "\ttopics idl : Display the IDL type definition for topics matching (wildcards allowed).\n" + << "\tfilters : Display the active filters.\n" + << "\tfilters clear : Clear all the filter lists.\n" + << "\tfilters remove : Remove all the filter lists.\n" + << "\tfilter clear : Clear filter list.\n" + << "\tfilter remove : Remove filter list.\n" + << "\tfilter set : Set filter list with as first value.\n" + << "\tfilter add : Add in filter list.\n" + << "\tfilter remove : Remove in filter list.\n" + << "\techo : data of a specific Topic (Data Type must be discovered).\n" << - "\techo : data of Topics matching the wildcard name (and whose Data Type is discovered).\n" - << "\techo verbose : data with additional source info of a specific Topic.\n" + "\techo : data of Topics matching the wildcard name (and whose Data Type is discovered).\n" + << "\techo verbose : data with additional source info of a specific Topic.\n" << - "\techo verbose : data with additional source info of Topics matching the topic name (wildcard allowed (*)).\n" + "\techo verbose : data with additional source info of Topics matching the topic name (wildcard allowed (*)).\n" << - "\techo all : verbose data of all topics (only those whose Data Type is discovered).\n" + "\techo all : verbose data of all topics (only those whose Data Type is discovered).\n" << "\n" << "Notes and comments:\n" << "\tTo exit from data printing, press enter.\n" - << "\tEach command is accessible by using its first letter (h/v/q/p/w/r/t/s).\n" + << "\tEach command is accessible by using its first letter (h/v/q/p/w/r/t/s/f).\n" << "\n" << "For more information about these commands and formats, please refer to the documentation:\n" << "https://fast-dds-spy.readthedocs.io/en/latest/\n" @@ -680,5 +808,342 @@ void Controller::error_command_( << "Use command to see valid commands and arguments."); } +void Controller::filter_command_( + const std::vector& arguments) noexcept +{ + const auto& check_filter_dict_contains_category = [&](std::string category, bool& ret) + { + if (filter_dict.find(category) == filter_dict.end()) + { + view_.show_error(STR_ENTRY + << "Filter list do not contains category: " + << category + << "."); + ret = false; + } + else + { + ret = true; + } + }; + + bool pass; + std::string operation; + std::string category; + std::string filter_str; + + if (arguments.size() == 1) // print filters + { + if (arguments[0] == "filter") + { + view_.show_error(STR_ENTRY + << "Command <" + << arguments[0] + << "> requires 3 or 4 arguments."); + return; + } + + // print the filters list + std::cout << "Filter lists (" << filter_dict.size() << ")\n"; + for (const auto& category: filter_dict) + { + std::cout << "\n " << category.first << " (" << category.second.size() << "):\n"; + for (std::string filter: category.second) + { + std::cout << " - " << (filter == "" ? "\"\"" : filter) << "\n"; + } + } + } + else if (arguments.size() == 2) // clear filters + { + if (arguments[0] == "filter") + { + view_.show_error(STR_ENTRY + << "Command <" + << arguments[0] + << "> requires 3 or 4 arguments."); + return; + } + + std::set allowed_args = {"clear"}; + if(allowed_args.find(arguments[1]) == allowed_args.end()) + { + view_.show_error(STR_ENTRY + << "To clear filters list do: \"filters clear\"."); + return; + } + + // clear ther filters list + filter_dict.clear(); + + update_filter_partitions(); + } + else if (arguments.size() == 3) // filter + { + if (arguments[0] == "filters") + { + view_.show_error(STR_ENTRY + << "Command <" + << arguments[0] + << "> requires 1 or 2 arguments."); + return; + } + + operation = arguments[1]; + category = arguments[2]; + + bool pass; + check_filter_dict_contains_category(category, pass); + if (!pass) + { + // filter_dict do not contains the category + return; + } + std::set allowed_args = {"clear", "remove"}; + if(allowed_args.find(arguments[1]) == allowed_args.end()) + { + view_.show_error(STR_ENTRY + << "To clear or remove a filter category do: \"filters \"."); + return; + } + + if (operation == "clear") + { + filter_dict[category].clear(); + } + else if (operation == "remove") + { + filter_dict.erase(category); + } + + if (category == "partitions") + { + update_filter_partitions(); + } + } + else if (arguments.size() == 4) + { + if (arguments[0] == "filters") + { + view_.show_error(STR_ENTRY + << "Command <" + << arguments[0] + << "> requires 1 or 2 arguments."); + return; + } + + operation = arguments[1]; + category = arguments[2]; + filter_str = arguments[3]; + + std::set allowed_args = {"set", "add", "remove"}; + if(allowed_args.find(arguments[1]) == allowed_args.end()) + { + view_.show_error(STR_ENTRY + << "Command <" + << arguments[0] + << "> with 4 arguments have the following format: " + << arguments[0] << " ."); + return; + } + + if (operation == "set") + { + // set filter category + if (filter_dict.find(category) != filter_dict.end()) + { + view_.show_error(STR_ENTRY + << "Filter list already contains category: " + << category + << "."); + return; + } + + filter_dict[category] = std::set{filter_str}; + + if (category == "partitions") + { + update_filter_partitions(); + } + } + else if (operation == "add") + { + // add filter category + + bool pass; + check_filter_dict_contains_category(category, pass); + if (!pass) + { + // filter_dict do not contains the category + return; + } + + // check if filter_str is not in the filter list of the category + if (filter_dict[category].find(category) != filter_dict[category].end()) + { + view_.show_error(STR_ENTRY + << "Filter list already contains filter_str: " << filter_str + << " in category: " << category + << "."); + return; + } + + filter_dict[category].insert(filter_str); + + if (category == "partitions") + { + update_filter_partitions(); + } + } + else if (operation == "remove") + { + // remove filter category + + bool pass; + check_filter_dict_contains_category(category, pass); + if (!pass) + { + // filter_dict do not contains the category + return; + } + + // check if filter_str is in the filter list of the category + if (filter_dict[category].find(filter_str) == filter_dict[category].end()) + { + view_.show_error(STR_ENTRY + << "Filter list do not contains filter_str: " << filter_str + << " in category: " << category + << "."); + return; + } + + filter_dict[category].erase(filter_str); + + if (category == "partitions") + { + update_filter_partitions(); + } + } + } + else + { + view_.show_error(STR_ENTRY + << "Command <" + << arguments[0] + << "> requires less than 5 argument."); + return; + } +} + +void Controller::update_filter_partitions() +{ + // new filter partition list + // update the endpoints active variable + + std::string topic_name; + std::set topic_set; + std::vector v_guid_active, v_guid_disable; + + int i, n; + std::string curr_partition; + bool endpoint_active; + + bool partitions_exists = filter_dict.find("partitions") != filter_dict.end(); + + for (const auto& endpoint: model_->endpoint_database_) + { + topic_name = endpoint.second.info.topic.m_topic_name; + + // get the partition set of the current endpoint + for (const auto& guid_partition_pair: endpoint.second.info.specific_partitions) + { + i = 0; + n = guid_partition_pair.second.size(); + curr_partition = ""; + endpoint_active = filter_dict["partitions"].empty(); + // iterate in the partition set + while (i < n) + { + if (guid_partition_pair.second[i] == '|') + { + for (std::string filter_p: filter_dict["partitions"]) + { + if (utils::match_pattern(filter_p, curr_partition) || + utils::match_pattern(curr_partition, filter_p)) + { + // the current partition matches with a partition + // from the filter, the endpoint is active + endpoint_active = true; + break; + } + } + + curr_partition = ""; + } + else + { + curr_partition += guid_partition_pair.second[i]; + } + + i++; + } + + // empty or last partition + for (std::string filter_p: filter_dict["partitions"]) + { + if (utils::match_pattern(filter_p, curr_partition) || + utils::match_pattern(curr_partition, filter_p)) + { + // the current partition matches with a partition + // from the filter, the endpoint is active + endpoint_active = true; + break; + } + } + } + + // store the information of the active/disable endpoins, + // later used for changing the active variable of all endpoints. + if (!endpoint_active) + { + v_guid_disable.push_back(endpoint.first); + } + else + { + v_guid_active.push_back(endpoint.first); + } + + // update tracker if it has not being updated before + if (topic_set.find(topic_name) == topic_set.end()) + { + backend_.update_readers_track(topic_name, filter_dict["partitions"]); + topic_set.insert(topic_name); + } + } + + // update the filter of partitions in the pipeline + backend_.update_pipeline_filter(filter_dict["partitions"]); + + if (!partitions_exists) + { + // remove the partition list if it was not created before. + filter_dict.erase("partitions"); + } + + // change the active variable of the dabase + for (const auto& curr_guid: v_guid_disable) + { + auto endpoint_tmp = model_->endpoint_database_.find(curr_guid)->second; + endpoint_tmp.info.active = false; + model_->endpoint_database_.add_or_modify(curr_guid, endpoint_tmp); + } + for (const auto& curr_guid: v_guid_active) + { + auto endpoint_tmp = model_->endpoint_database_.find(curr_guid)->second; + endpoint_tmp.info.active = true; + model_->endpoint_database_.add_or_modify(curr_guid, endpoint_tmp); + } +} + } /* namespace spy */ } /* namespace eprosima */ diff --git a/fastddsspy_tool/src/cpp/tool/Controller.hpp b/fastddsspy_tool/src/cpp/tool/Controller.hpp index 71b7d229..a51fffbd 100644 --- a/fastddsspy_tool/src/cpp/tool/Controller.hpp +++ b/fastddsspy_tool/src/cpp/tool/Controller.hpp @@ -100,6 +100,11 @@ class Controller void print_command_( const std::vector& arguments) noexcept; + ///////////////////// + // FILTER + void filter_command_( + const std::vector& arguments) noexcept; + ///////////////////// // AUXILIARY void version_command_( @@ -109,6 +114,8 @@ class Controller void error_command_( const std::vector& arguments) noexcept; + void update_filter_partitions(); + ///////////////////// // VARIABLES Backend backend_; @@ -131,8 +138,14 @@ class Controller specificF specific_function, const char* entity_name) noexcept; + void refresh_database(); + std::mutex view_mutex_; + std::map> filter_dict; + + std::set allowed_filters_categories_; + }; } /* namespace spy */ diff --git a/fastddsspy_tool/test/application/test.py b/fastddsspy_tool/test/application/test.py index 37a9aaa2..2d1b7e51 100644 --- a/fastddsspy_tool/test/application/test.py +++ b/fastddsspy_tool/test/application/test.py @@ -154,8 +154,7 @@ def main(): sys.exit(1) if not test_class.one_shot: - - output = test_class.send_command_tool(spy) + output = test_class.send_commands_tool(spy) if not test_class.valid_output(output): test_class.stop_tool(spy) diff --git a/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds.py b/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds.py index 8772fa0c..0ea9437e 100644 --- a/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds.py +++ b/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds.py @@ -36,7 +36,7 @@ def __init__(self): dds=True, config='fastddsspy_tool/test/application/configuration/\ configuration_discovery_time.yaml', - arguments_dds=[], + arguments_dds=[''], arguments_spy=['--config-path', 'configuration', 'datawriter', 'verbose'], commands_spy=[], output="""- guid: %%guid%%\n\ @@ -44,7 +44,8 @@ def __init__(self): topic:\n\ name: HelloWorldTopic\n\ type: HelloWorld\n\ + partitions: ""\n\ qos:\n\ - durability: volatile\n\ - reliability: best-effort\n""" + durability: transient-local\n\ + reliability: reliable\n""" ) diff --git a/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds_qos.py b/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds_qos.py index 8d6da491..f3c39de6 100644 --- a/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds_qos.py +++ b/fastddsspy_tool/test/application/test_cases/one_shot_datawriter_verbose_dds_qos.py @@ -44,6 +44,7 @@ def __init__(self): topic:\n\ name: HelloWorldTopic\n\ type: HelloWorld\n\ + partitions: ""\n\ qos:\n\ durability: transient-local\n\ reliability: reliable\n""" diff --git a/fastddsspy_tool/test/application/test_cases/one_shot_help.py b/fastddsspy_tool/test/application/test_cases/one_shot_help.py index 24dee3c2..e11ebdc8 100644 --- a/fastddsspy_tool/test/application/test_cases/one_shot_help.py +++ b/fastddsspy_tool/test/application/test_cases/one_shot_help.py @@ -37,55 +37,100 @@ def __init__(self): arguments_spy=['help'], commands_spy=[], output=( - 'Fast DDS Spy is an interactive CLI that allow to instrospect DDS networks.\n' - 'Each command shows data related with the network in Yaml format.\n' - 'Commands available and the information they show:\n' - '\thelp : this help.\n' - '\tversion : tool version.\n' - '\tquit : exit interactive CLI and close program.\n' - '\tparticipants : DomainParticipants discovered in the ' - 'network.\n' - '\tparticipants verbose : verbose information about DomainParticipants ' - 'discovered in the network.\n' - '\tparticipants : verbose information related with a specific ' - 'DomainParticipant.\n' - '\twriters : DataWriters discovered in the network.\n' - '\twriters verbose : verbose information about DataWriters ' - 'discovered in the network.\n' - '\twriters : verbose information related with a specific ' - 'DataWriter.\n' - '\treader : DataReaders discovered in the network.\n' - '\treader verbose : verbose information about DataReaders ' - 'discovered in the network.\n' - '\treader : verbose information related with a specific ' - 'DataReader.\n' - '\ttopics : Topics discovered in the network ' - 'in compact format.\n' - '\ttopics v : Topics discovered in the network.\n' - '\ttopics vv : verbose information about Topics discovered ' - 'in the network.\n' - '\ttopics : Topics discovered in the network filtered by ' - 'name (wildcard allowed (*)).\n' - '\ttopics idl : Display the IDL type definition for topics ' - 'matching (wildcards allowed).\n' - '\techo : data of a specific Topic ' - '(Data Type must be discovered).\n' - '\techo : data of Topics matching the wildcard name ' - '(and whose Data Type is discovered).\n' - '\techo verbose : data with additional source info ' - 'of a specific Topic.\n' - '\techo verbose : data with additional source info ' - 'of Topics matching the ' - 'topic name (wildcard allowed (*)).\n' - '\techo all : verbose data of all topics (only those whose ' - 'Data Type is discovered).\n' - '\n' - 'Notes and comments:\n' - '\tTo exit from data printing, press enter.\n' - '\tEach command is accessible by using its first letter (h/v/q/p/w/r/t/s).\n' - '\n' + 'Fast DDS Spy is an interactive CLI that allow ' + 'to instrospect DDS networks.\n\n' + 'Each command shows data related with the network ' + 'in Yaml format.\n\n' + 'Commands available and the information they show:\n\n' + '\thelp : ' + 'this help.\n\n' + '\tversion : ' + 'tool version.\n\n' + '\tquit : ' + 'exit interactive CLI and close ' + 'program.\n\n' + '\tparticipants : ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants verbose : ' + 'verbose information about ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants : ' + 'verbose information related with ' + 'a specific DomainParticipant.\n\n' + '\twriters : ' + 'DataWriters discovered in the network.\n\n' + '\twriters verbose : ' + 'verbose information about DataWriters ' + 'discovered in the network.\n\n' + '\twriters : ' + 'verbose information related with a ' + 'specific DataWriter.\n\n' + '\treader : ' + 'DataReaders discovered in the network.\n\n' + '\treader verbose : ' + 'verbose information about ' + 'DataReaders discovered in the network.\n\n' + '\treader : ' + 'verbose information related with a ' + 'specific DataReader.\n\n' + '\ttopics : ' + 'Topics discovered in the network in ' + 'compact format.\n\n' + '\ttopics v : ' + 'Topics discovered in the network.\n\n' + '\ttopics vv : ' + 'verbose information about Topics ' + 'discovered in the network.\n\n' + '\ttopics : ' + 'Topics discovered in the network ' + 'filtered by name (wildcard allowed (*)).\n\n' + '\ttopics idl : ' + 'Display the IDL type definition for topics ' + 'matching (wildcards allowed).\n\n' + '\tfilters : ' + 'Display the active filters.\n\n' + '\tfilters clear : ' + 'Clear all the filter lists.\n\n' + '\tfilters remove : ' + 'Remove all the filter lists.\n\n' + '\tfilter clear : ' + 'Clear filter list.\n\n' + '\tfilter remove : ' + 'Remove filter list.\n\n' + '\tfilter set : ' + 'Set filter list with ' + ' as first value.\n\n' + '\tfilter add : ' + 'Add in ' + 'filter list.\n\n' + '\tfilter remove : ' + 'Remove in ' + 'filter list.\n\n' + '\techo : ' + 'data of a specific Topic ' + '(Data Type must be discovered).\n\n' + '\techo : ' + 'data of Topics matching the ' + 'wildcard name ' + '(and whose Data Type is discovered).\n\n' + '\techo verbose : ' + 'data with additional source info ' + 'of a specific Topic.\n\n' + '\techo verbose : ' + 'data with additional source info ' + 'of Topics ' + 'matching the topic name (wildcard allowed (*)).\n\n' + '\techo all : ' + 'verbose data of all topics ' + '(only those whose Data Type is discovered).\n\n' + '\n\n' + 'Notes and comments:\n\n' + '\tTo exit from data printing, press enter.\n\n' + '\tEach command is accessible by using its ' + 'first letter (h/v/q/p/w/r/t/s/f).\n\n' + '\n\n' 'For more information about these commands and formats, ' - 'please refer to the documentation:\n' + 'please refer to the documentation:\n\n' 'https://fast-dds-spy.readthedocs.io/en/latest/\n' ) ) diff --git a/fastddsspy_tool/test/application/test_cases/one_shot_help_dds.py b/fastddsspy_tool/test/application/test_cases/one_shot_help_dds.py index 3e510946..b19a3af9 100644 --- a/fastddsspy_tool/test/application/test_cases/one_shot_help_dds.py +++ b/fastddsspy_tool/test/application/test_cases/one_shot_help_dds.py @@ -40,55 +40,100 @@ def __init__(self): arguments_spy=['--config-path', 'configuration', 'help'], commands_spy=[], output=( - 'Fast DDS Spy is an interactive CLI that allow to instrospect DDS networks.\n' - 'Each command shows data related with the network in Yaml format.\n' - 'Commands available and the information they show:\n' - '\thelp : this help.\n' - '\tversion : tool version.\n' - '\tquit : exit interactive CLI and close program.\n' - '\tparticipants : DomainParticipants discovered in the ' - 'network.\n' - '\tparticipants verbose : verbose information about DomainParticipants ' - 'discovered in the network.\n' - '\tparticipants : verbose information related with a specific ' - 'DomainParticipant.\n' - '\twriters : DataWriters discovered in the network.\n' - '\twriters verbose : verbose information about DataWriters ' - 'discovered in the network.\n' - '\twriters : verbose information related with a specific ' - 'DataWriter.\n' - '\treader : DataReaders discovered in the network.\n' - '\treader verbose : verbose information about DataReaders ' - 'discovered in the network.\n' - '\treader : verbose information related with a specific ' - 'DataReader.\n' - '\ttopics : Topics discovered in the network ' - 'in compact format.\n' - '\ttopics v : Topics discovered in the network.\n' - '\ttopics vv : verbose information about Topics discovered ' - 'in the network.\n' - '\ttopics : Topics discovered in the network filtered by ' - 'name (wildcard allowed (*)).\n' - '\ttopics idl : Display the IDL type definition for topics ' - 'matching (wildcards allowed).\n' - '\techo : data of a specific Topic ' - '(Data Type must be discovered).\n' - '\techo : data of Topics matching the wildcard name ' - '(and whose Data Type is discovered).\n' - '\techo verbose : data with additional source info ' - 'of a specific Topic.\n' - '\techo verbose : data with additional source info ' - 'of Topics matching the ' - 'topic name (wildcard allowed (*)).\n' - '\techo all : verbose data of all topics (only those whose ' - 'Data Type is discovered).\n' - '\n' - 'Notes and comments:\n' - '\tTo exit from data printing, press enter.\n' - '\tEach command is accessible by using its first letter (h/v/q/p/w/r/t/s).\n' - '\n' + 'Fast DDS Spy is an interactive CLI that allow ' + 'to instrospect DDS networks.\n\n' + 'Each command shows data related with the network ' + 'in Yaml format.\n\n' + 'Commands available and the information they show:\n\n' + '\thelp : ' + 'this help.\n\n' + '\tversion : ' + 'tool version.\n\n' + '\tquit : ' + 'exit interactive CLI and close ' + 'program.\n\n' + '\tparticipants : ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants verbose : ' + 'verbose information about ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants : ' + 'verbose information related with ' + 'a specific DomainParticipant.\n\n' + '\twriters : ' + 'DataWriters discovered in the network.\n\n' + '\twriters verbose : ' + 'verbose information about DataWriters ' + 'discovered in the network.\n\n' + '\twriters : ' + 'verbose information related with a ' + 'specific DataWriter.\n\n' + '\treader : ' + 'DataReaders discovered in the network.\n\n' + '\treader verbose : ' + 'verbose information about ' + 'DataReaders discovered in the network.\n\n' + '\treader : ' + 'verbose information related with a ' + 'specific DataReader.\n\n' + '\ttopics : ' + 'Topics discovered in the network in ' + 'compact format.\n\n' + '\ttopics v : ' + 'Topics discovered in the network.\n\n' + '\ttopics vv : ' + 'verbose information about Topics ' + 'discovered in the network.\n\n' + '\ttopics : ' + 'Topics discovered in the network ' + 'filtered by name (wildcard allowed (*)).\n\n' + '\ttopics idl : ' + 'Display the IDL type definition for topics ' + 'matching (wildcards allowed).\n\n' + '\tfilters : ' + 'Display the active filters.\n\n' + '\tfilters clear : ' + 'Clear all the filter lists.\n\n' + '\tfilters remove : ' + 'Remove all the filter lists.\n\n' + '\tfilter clear : ' + 'Clear filter list.\n\n' + '\tfilter remove : ' + 'Remove filter list.\n\n' + '\tfilter set : ' + 'Set filter list with ' + ' as first value.\n\n' + '\tfilter add : ' + 'Add in ' + 'filter list.\n\n' + '\tfilter remove : ' + 'Remove in ' + 'filter list.\n\n' + '\techo : ' + 'data of a specific Topic ' + '(Data Type must be discovered).\n\n' + '\techo : ' + 'data of Topics matching the ' + 'wildcard name ' + '(and whose Data Type is discovered).\n\n' + '\techo verbose : ' + 'data with additional source info ' + 'of a specific Topic.\n\n' + '\techo verbose : ' + 'data with additional source info ' + 'of Topics ' + 'matching the topic name (wildcard allowed (*)).\n\n' + '\techo all : ' + 'verbose data of all topics ' + '(only those whose Data Type is discovered).\n\n' + '\n\n' + 'Notes and comments:\n\n' + '\tTo exit from data printing, press enter.\n\n' + '\tEach command is accessible by using its ' + 'first letter (h/v/q/p/w/r/t/s/f).\n\n' + '\n\n' 'For more information about these commands and formats, ' - 'please refer to the documentation:\n' + 'please refer to the documentation:\n\n' 'https://fast-dds-spy.readthedocs.io/en/latest/\n' ) ) diff --git a/fastddsspy_tool/test/application/test_cases/one_shot_topics_verbose_verbose_dds.py b/fastddsspy_tool/test/application/test_cases/one_shot_topics_verbose_verbose_dds.py index 70b51124..8b2aa231 100644 --- a/fastddsspy_tool/test/application/test_cases/one_shot_topics_verbose_verbose_dds.py +++ b/fastddsspy_tool/test/application/test_cases/one_shot_topics_verbose_verbose_dds.py @@ -42,7 +42,7 @@ def __init__(self): output="""- name: HelloWorldTopic\n\ type: HelloWorld\n\ datawriters:\n\ - - %%guid%%\n\ + - %%guid%% [""]\n\ rate: %%rate%%\n\ dynamic_type_discovered: true\n""" ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_datawriter_dds_filter.py b/fastddsspy_tool/test/application/test_cases/tool_datawriter_dds_filter.py new file mode 100644 index 00000000..9eec0b05 --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_datawriter_dds_filter.py @@ -0,0 +1,47 @@ +# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + Add filter partitions, and does not print the information. + + This test launch: + fastddsspy --config-path fastddsspy_tool/test/application/configuration/\ + configuration_discovery_time.yaml + >> filter set partitions A + >> datawriter + AdvancedConfigurationExample publisher + """ + super().__init__( + name='ToolDatawriterDDSFilter', + one_shot=False, + command=[], + dds=True, + config='fastddsspy_tool/test/application/configuration/\ +configuration_discovery_time.yaml', + arguments_dds=[], + arguments_spy=['--config-path', 'configuration'], + commands_spy=['filter set partitions A', 'datawriter'], + output='' + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_clear_lists.py b/fastddsspy_tool/test/application/test_cases/tool_filter_clear_lists.py new file mode 100644 index 00000000..b34a113f --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_clear_lists.py @@ -0,0 +1,54 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase creates two filter lists "partitions" and "topics" and clear all the filters. + + And prints the list of filters (empty) + + This test launch: + fastddsspy + >> filter set partitions A + >> filter set partitions A + >> filter set topics Square + >> filter clear + >> filters + """ + super().__init__( + name='ToolFilterClearLists', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=[ + 'filter set partitions A', + 'filter set topics Square', + 'filter clear', + 'filters', + ], + output='' + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_clear_partition_list.py b/fastddsspy_tool/test/application/test_cases/tool_filter_clear_partition_list.py new file mode 100644 index 00000000..b5ad63f0 --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_clear_partition_list.py @@ -0,0 +1,62 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase creates two filter lists "partitions" and "topics", + and clear "partitions" filter. + + And prints the list of filters + + This test launch: + fastddsspy + >> filter set partitions A + >> filter set topics Square + >> filter clear partitions + >> filters + """ + super().__init__( + name='ToolFilterClearPartitionList', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=[ + 'filter set partitions A', + 'filter set topics Square', + 'filter clear', + 'filters', + ], + output=( + 'Filter lists (2)\n' + '\n\n\n' + ' partitions (0):\n' + '\n\n\n' + ' topics (1):\n' + '\n' + ' - Square\n' + ) + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_empty_filter.py b/fastddsspy_tool/test/application/test_cases/tool_filter_empty_filter.py new file mode 100644 index 00000000..61cf4fbb --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_empty_filter.py @@ -0,0 +1,43 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase prints the (empty) list of filters. + + This test launch: + fastddsspy + >> filters + """ + super().__init__( + name='ToolFilterEmptyCommand', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=['filters'], + output="""Filter lists (0)\n""" + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_remove_partition_list.py b/fastddsspy_tool/test/application/test_cases/tool_filter_remove_partition_list.py new file mode 100644 index 00000000..3a3918cc --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_remove_partition_list.py @@ -0,0 +1,60 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase creates two filter lists "partitions" + and "topics" and remove "partitions" list. + + And prints the list of filters + + This test launch: + fastddsspy + >> filter set partitions A + >> filter set topics Square + >> filter remove partitions + >> filters + """ + super().__init__( + name='ToolFilterRemovePartitionList', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=[ + 'filter set partitions A', + 'filter set topics Square', + 'filter clear', + 'filters', + ], + output=( + 'Filter lists (1)\n' + '\n\n\n' + ' topics (1):\n' + '\n' + ' - Square\n' + ) + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_set_lists.py b/fastddsspy_tool/test/application/test_cases/tool_filter_set_lists.py new file mode 100644 index 00000000..20b193ab --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_set_lists.py @@ -0,0 +1,65 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase creates three filter list + "partitions", "partition", "topics" and remove "partition" list. + + And prints the list of filters + + This test launch: + fastddsspy + >> filter set partitions A + >> filter set partition A + >> filter set topic Square + >> filter remove partition + >> filters + """ + super().__init__( + name='ToolFilterSetLists', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=[ + 'filter set partitions A', + 'filter set topics Square', + 'filter clear', + 'filters', + ], + output=( + 'Filter lists (2)\n' + '\n\n\n' + ' partitions (1):\n' + '\n' + ' - A\n' + '\n\n\n' + ' topics (1):\n' + '\n' + ' - Square\n' + ) + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_set_partitions_complex.py b/fastddsspy_tool/test/application/test_cases/tool_filter_set_partitions_complex.py new file mode 100644 index 00000000..3febbe1b --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_set_partitions_complex.py @@ -0,0 +1,63 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase creates the filter list "partitions" with "B|C" as filters + (removes the first element "A"). + + And prints the list of filters + + This test launch: + fastddsspy + >> filter set partitions A + >> filter add partitions B + >> filter add partitions C + >> filter remove partitions A + >> filters + """ + super().__init__( + name='ToolFilterSetPartitionsComplex', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=[ + 'filter set partitions A', + 'filter set topics Square', + 'filter clear', + 'filters', + ], + output=( + 'Filter lists (1)\n' + '\n\n\n' + ' partitions (2):\n' + '\n' + ' - B\n' + '\n' + ' - C\n' + ) + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_filter_set_partitions_simple.py b/fastddsspy_tool/test/application/test_cases/tool_filter_set_partitions_simple.py new file mode 100644 index 00000000..9b7ba27c --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_filter_set_partitions_simple.py @@ -0,0 +1,51 @@ +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + This TestCase creates the filter list "partitions" with "A" as unique filter string. + And prints the list of filters + + This test launch: + fastddsspy + >> filter set partitions A + >> filters + """ + super().__init__( + name='ToolFilterSetPartitionsSimple', + one_shot=False, + command=[], + dds=False, + config='', + arguments_dds=[], + arguments_spy=[], + commands_spy=['filter set partitions A', 'filters'], + output=( + 'Filter lists (1)\n' + '\n\n\n' + ' partitions (1):\n' + '\n' + ' - A\n' + ) + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_help.py b/fastddsspy_tool/test/application/test_cases/tool_help.py index 385c17ca..df6c0b9e 100644 --- a/fastddsspy_tool/test/application/test_cases/tool_help.py +++ b/fastddsspy_tool/test/application/test_cases/tool_help.py @@ -38,52 +38,97 @@ def __init__(self): arguments_spy=[], commands_spy=['help'], output=( - 'Fast DDS Spy is an interactive CLI that allow to instrospect DDS networks.\n\n' - 'Each command shows data related with the network in Yaml format.\n\n' + 'Fast DDS Spy is an interactive CLI that allow ' + 'to instrospect DDS networks.\n\n' + 'Each command shows data related with the network ' + 'in Yaml format.\n\n' 'Commands available and the information they show:\n\n' - '\thelp : this help.\n\n' - '\tversion : tool version.\n\n' - '\tquit : exit interactive CLI and close program.\n\n' - '\tparticipants : DomainParticipants discovered in the ' - 'network.\n\n' - '\tparticipants verbose : verbose information about DomainParticipants ' + '\thelp : ' + 'this help.\n\n' + '\tversion : ' + 'tool version.\n\n' + '\tquit : ' + 'exit interactive CLI and close ' + 'program.\n\n' + '\tparticipants : ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants verbose : ' + 'verbose information about ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants : ' + 'verbose information related with ' + 'a specific DomainParticipant.\n\n' + '\twriters : ' + 'DataWriters discovered in the network.\n\n' + '\twriters verbose : ' + 'verbose information about DataWriters ' 'discovered in the network.\n\n' - '\tparticipants : verbose information related with a specific ' - 'DomainParticipant.\n\n' - '\twriters : DataWriters discovered in the network.\n\n' - '\twriters verbose : verbose information about DataWriters ' + '\twriters : ' + 'verbose information related with a ' + 'specific DataWriter.\n\n' + '\treader : ' + 'DataReaders discovered in the network.\n\n' + '\treader verbose : ' + 'verbose information about ' + 'DataReaders discovered in the network.\n\n' + '\treader : ' + 'verbose information related with a ' + 'specific DataReader.\n\n' + '\ttopics : ' + 'Topics discovered in the network in ' + 'compact format.\n\n' + '\ttopics v : ' + 'Topics discovered in the network.\n\n' + '\ttopics vv : ' + 'verbose information about Topics ' 'discovered in the network.\n\n' - '\twriters : verbose information related with a specific ' - 'DataWriter.\n\n' - '\treader : DataReaders discovered in the network.\n\n' - '\treader verbose : verbose information about DataReaders ' - 'discovered in the network.\n\n' - '\treader : verbose information related with a specific ' - 'DataReader.\n\n' - '\ttopics : Topics discovered in the network ' - 'in compact format.\n\n' - '\ttopics v : Topics discovered in the network.\n\n' - '\ttopics vv : verbose information about Topics discovered ' - 'in the network.\n\n' - '\ttopics : Topics discovered in the network filtered by ' - 'name (wildcard allowed (*)).\n\n' - '\ttopics idl : Display the IDL type definition for topics ' + '\ttopics : ' + 'Topics discovered in the network ' + 'filtered by name (wildcard allowed (*)).\n\n' + '\ttopics idl : ' + 'Display the IDL type definition for topics ' 'matching (wildcards allowed).\n\n' - '\techo : data of a specific Topic ' + '\tfilters : ' + 'Display the active filters.\n\n' + '\tfilters clear : ' + 'Clear all the filter lists.\n\n' + '\tfilters remove : ' + 'Remove all the filter lists.\n\n' + '\tfilter clear : ' + 'Clear filter list.\n\n' + '\tfilter remove : ' + 'Remove filter list.\n\n' + '\tfilter set : ' + 'Set filter list with ' + ' as first value.\n\n' + '\tfilter add : ' + 'Add in ' + 'filter list.\n\n' + '\tfilter remove : ' + 'Remove in ' + 'filter list.\n\n' + '\techo : ' + 'data of a specific Topic ' '(Data Type must be discovered).\n\n' - '\techo : data of Topics matching the wildcard name ' + '\techo : ' + 'data of Topics matching the ' + 'wildcard name ' '(and whose Data Type is discovered).\n\n' - '\techo verbose : data with additional source info ' + '\techo verbose : ' + 'data with additional source info ' 'of a specific Topic.\n\n' - '\techo verbose : data with additional source info ' - 'of Topics matching the ' - 'topic name (wildcard allowed (*)).\n\n' - '\techo all : verbose data of all topics (only those whose ' - 'Data Type is discovered).\n\n' + '\techo verbose : ' + 'data with additional source info ' + 'of Topics ' + 'matching the topic name (wildcard allowed (*)).\n\n' + '\techo all : ' + 'verbose data of all topics ' + '(only those whose Data Type is discovered).\n\n' '\n\n' 'Notes and comments:\n\n' '\tTo exit from data printing, press enter.\n\n' - '\tEach command is accessible by using its first letter (h/v/q/p/w/r/t/s).\n\n' + '\tEach command is accessible by using its ' + 'first letter (h/v/q/p/w/r/t/s/f).\n\n' '\n\n' 'For more information about these commands and formats, ' 'please refer to the documentation:\n\n' diff --git a/fastddsspy_tool/test/application/test_cases/tool_help_dds.py b/fastddsspy_tool/test/application/test_cases/tool_help_dds.py index e339febd..9a12fa48 100644 --- a/fastddsspy_tool/test/application/test_cases/tool_help_dds.py +++ b/fastddsspy_tool/test/application/test_cases/tool_help_dds.py @@ -41,52 +41,97 @@ def __init__(self): arguments_spy=['--config-path', 'configuration'], commands_spy=['help'], output=( - 'Fast DDS Spy is an interactive CLI that allow to instrospect DDS networks.\n\n' - 'Each command shows data related with the network in Yaml format.\n\n' + 'Fast DDS Spy is an interactive CLI that allow ' + 'to instrospect DDS networks.\n\n' + 'Each command shows data related with the network ' + 'in Yaml format.\n\n' 'Commands available and the information they show:\n\n' - '\thelp : this help.\n\n' - '\tversion : tool version.\n\n' - '\tquit : exit interactive CLI and close program.\n\n' - '\tparticipants : DomainParticipants discovered in the ' - 'network.\n\n' - '\tparticipants verbose : verbose information about DomainParticipants ' + '\thelp : ' + 'this help.\n\n' + '\tversion : ' + 'tool version.\n\n' + '\tquit : ' + 'exit interactive CLI and close ' + 'program.\n\n' + '\tparticipants : ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants verbose : ' + 'verbose information about ' + 'DomainParticipants discovered in the network.\n\n' + '\tparticipants : ' + 'verbose information related with ' + 'a specific DomainParticipant.\n\n' + '\twriters : ' + 'DataWriters discovered in the network.\n\n' + '\twriters verbose : ' + 'verbose information about DataWriters ' 'discovered in the network.\n\n' - '\tparticipants : verbose information related with a specific ' - 'DomainParticipant.\n\n' - '\twriters : DataWriters discovered in the network.\n\n' - '\twriters verbose : verbose information about DataWriters ' + '\twriters : ' + 'verbose information related with a ' + 'specific DataWriter.\n\n' + '\treader : ' + 'DataReaders discovered in the network.\n\n' + '\treader verbose : ' + 'verbose information about ' + 'DataReaders discovered in the network.\n\n' + '\treader : ' + 'verbose information related with a ' + 'specific DataReader.\n\n' + '\ttopics : ' + 'Topics discovered in the network in ' + 'compact format.\n\n' + '\ttopics v : ' + 'Topics discovered in the network.\n\n' + '\ttopics vv : ' + 'verbose information about Topics ' 'discovered in the network.\n\n' - '\twriters : verbose information related with a specific ' - 'DataWriter.\n\n' - '\treader : DataReaders discovered in the network.\n\n' - '\treader verbose : verbose information about DataReaders ' - 'discovered in the network.\n\n' - '\treader : verbose information related with a specific ' - 'DataReader.\n\n' - '\ttopics : Topics discovered in the network ' - 'in compact format.\n\n' - '\ttopics v : Topics discovered in the network.\n\n' - '\ttopics vv : verbose information about Topics discovered ' - 'in the network.\n\n' - '\ttopics : Topics discovered in the network filtered by ' - 'name (wildcard allowed (*)).\n\n' - '\ttopics idl : Display the IDL type definition for topics ' + '\ttopics : ' + 'Topics discovered in the network ' + 'filtered by name (wildcard allowed (*)).\n\n' + '\ttopics idl : ' + 'Display the IDL type definition for topics ' 'matching (wildcards allowed).\n\n' - '\techo : data of a specific Topic ' + '\tfilters : ' + 'Display the active filters.\n\n' + '\tfilters clear : ' + 'Clear all the filter lists.\n\n' + '\tfilters remove : ' + 'Remove all the filter lists.\n\n' + '\tfilter clear : ' + 'Clear filter list.\n\n' + '\tfilter remove : ' + 'Remove filter list.\n\n' + '\tfilter set : ' + 'Set filter list with ' + ' as first value.\n\n' + '\tfilter add : ' + 'Add in ' + 'filter list.\n\n' + '\tfilter remove : ' + 'Remove in ' + 'filter list.\n\n' + '\techo : ' + 'data of a specific Topic ' '(Data Type must be discovered).\n\n' - '\techo : data of Topics matching the wildcard name ' + '\techo : ' + 'data of Topics matching the ' + 'wildcard name ' '(and whose Data Type is discovered).\n\n' - '\techo verbose : data with additional source info ' + '\techo verbose : ' + 'data with additional source info ' 'of a specific Topic.\n\n' - '\techo verbose : data with additional source info ' - 'of Topics matching the ' - 'topic name (wildcard allowed (*)).\n\n' - '\techo all : verbose data of all topics (only those whose ' - 'Data Type is discovered).\n\n' + '\techo verbose : ' + 'data with additional source info ' + 'of Topics ' + 'matching the topic name (wildcard allowed (*)).\n\n' + '\techo all : ' + 'verbose data of all topics ' + '(only those whose Data Type is discovered).\n\n' '\n\n' 'Notes and comments:\n\n' '\tTo exit from data printing, press enter.\n\n' - '\tEach command is accessible by using its first letter (h/v/q/p/w/r/t/s).\n\n' + '\tEach command is accessible by using its ' + 'first letter (h/v/q/p/w/r/t/s/f).\n\n' '\n\n' 'For more information about these commands and formats, ' 'please refer to the documentation:\n\n' diff --git a/fastddsspy_tool/test/application/test_cases/tool_topics_dds_not_pass_filter.py b/fastddsspy_tool/test/application/test_cases/tool_topics_dds_not_pass_filter.py new file mode 100644 index 00000000..08f38240 --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_topics_dds_not_pass_filter.py @@ -0,0 +1,48 @@ +# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + Add filter partition ("A"), and the topic does not pass the filter, + the information is not printed. + + This test launch: + fastddsspy --config-path fastddsspy_tool/test/application/configuration/\ + configuration_discovery_time.yaml + >> filter set partitions A + >> topics + AdvancedConfigurationExample publisher + """ + super().__init__( + name='ToolTopicsDDSNotPassFilter', + one_shot=False, + command=[], + dds=True, + config='fastddsspy_tool/test/application/configuration/\ +configuration_discovery_time.yaml', + arguments_dds=[], + arguments_spy=['--config-path', 'configuration'], + commands_spy=['filter set partitions A', 'topics'], + output='' + ) diff --git a/fastddsspy_tool/test/application/test_cases/tool_topics_dds_pass_filter.py b/fastddsspy_tool/test/application/test_cases/tool_topics_dds_pass_filter.py new file mode 100644 index 00000000..d955e976 --- /dev/null +++ b/fastddsspy_tool/test/application/test_cases/tool_topics_dds_pass_filter.py @@ -0,0 +1,48 @@ +# Copyright 2023 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the fastddsspy executable.""" + +import test_class + + +class TestCase_instance (test_class.TestCase): + """@brief A subclass of `test_class.TestCase` representing a specific test case.""" + + def __init__(self): + """ + @brief Initialize the TestCase_instance object. + + Add filter partition (""), and the topic pass the filter, + the information is printed. + + This test launch: + fastddsspy --config-path fastddsspy_tool/test/application/configuration/\ + configuration_discovery_time.yaml + >> filter set partitions + >> topics + AdvancedConfigurationExample publisher + """ + super().__init__( + name='ToolTopicsDDSPassFilter', + one_shot=False, + command=[], + dds=True, + config='fastddsspy_tool/test/application/configuration/\ +configuration_discovery_time.yaml', + arguments_dds=[], + arguments_spy=['--config-path', 'configuration'], + commands_spy=['filter set partitions ', 'topics'], + output="""- topic: HelloWorldTopic (HelloWorld) (1|0) [%%rate%% Hz]\n""" + ) diff --git a/fastddsspy_tool/test/application/test_class.py b/fastddsspy_tool/test/application/test_class.py index 77ae92c5..eaa3a99f 100644 --- a/fastddsspy_tool/test/application/test_class.py +++ b/fastddsspy_tool/test/application/test_class.py @@ -134,6 +134,26 @@ def send_command_tool(self, proc): output = self.read_command_output(proc) return (output) + def send_commands_tool(self, proc): + """ + @brief Send all the commands to the running Spy. + + @param proc: The subprocess object representing the running Spy. + @return Returns the output received after sending the last command. + """ + + outputs = [] + + for cmd in self.commands_spy: + time.sleep(SLEEP_TIME) + proc.stdin.write(cmd + '\n') + proc.stdin.flush() + # read_command_output waits until the 'Insert a command...' prompt, so it + # returns the output for this command + outputs.append(self.read_command_output(proc)) + + return outputs[-1] + def read_command_output(self, proc): """ @brief Read the output from the subprocess. @@ -177,9 +197,15 @@ def valid_guid(self, guid) -> bool: """ pattern = r'^(([0-9a-f]{2}\.){11}[0-9a-f]{2}\|([0-9a-f]\.){3}[0-9a-f]{1,})$' if not re.match(pattern, guid): - print('Not valid guid: ') - print(guid) - return False + + # check if the guid contains it partitions + pattern = r'^(([0-9a-f]{2}\.){11}[0-9a-f]{2}) \ + \|(([0-9a-f]\.){3}[0-9a-f]{1,})\s+\[".*"\]$' + if not re.match(pattern, guid): + print('Not valid guid: ') + print(guid) + return False + return True def valid_rate(self, rate) -> bool: diff --git a/fastddsspy_yaml/src/cpp/YamlWriter.cpp b/fastddsspy_yaml/src/cpp/YamlWriter.cpp index 015144ea..ac03595e 100644 --- a/fastddsspy_yaml/src/cpp/YamlWriter.cpp +++ b/fastddsspy_yaml/src/cpp/YamlWriter.cpp @@ -113,6 +113,7 @@ void set( { set_in_tag(yml, "name", value.topic_name); set_in_tag(yml, "type", value.topic_type); + set_in_tag(yml, "partitions", value.partition); } template <> @@ -191,7 +192,13 @@ void set( Yaml& yml, const ComplexTopicData::Endpoint& value) { - set(yml, value.guid); + std::string guid_and_partition = ""; + std::ostringstream guid_ss; + guid_ss << value.guid; + guid_and_partition = guid_ss.str() + " [" + + (value.partition == "" ? "\"\"": value.partition) + "]"; + + set(yml, guid_and_partition); } template <> @@ -214,6 +221,7 @@ void set( { set_in_tag(yml, "topic", value.topic); set_in_tag(yml, "writer", value.writer); + set_in_tag(yml, "partitions", value.partitions); set_in_tag(yml, "timestamp", value.timestamp); } diff --git a/fastddsspy_yaml/test/unittest/yaml_writer/CMakeLists.txt b/fastddsspy_yaml/test/unittest/yaml_writer/CMakeLists.txt index 0f982279..70d44b9e 100644 --- a/fastddsspy_yaml/test/unittest/yaml_writer/CMakeLists.txt +++ b/fastddsspy_yaml/test/unittest/yaml_writer/CMakeLists.txt @@ -27,6 +27,12 @@ set(TEST_LIST test_SimpleParticipantData test_ComplexParticipantData test_ComplexParticipantData_Endpoint + test_SimpleEndpointData + test_ComplexEndpointData + test_SimpleTopicData_compact_false + test_SimpleTopicData_compact_true + test_ComplexTopicData + test_DdsDataData ) set(TEST_EXTRA_LIBRARIES diff --git a/fastddsspy_yaml/test/unittest/yaml_writer/YamlWriterTest.cpp b/fastddsspy_yaml/test/unittest/yaml_writer/YamlWriterTest.cpp index 74d48354..c2474ca2 100644 --- a/fastddsspy_yaml/test/unittest/yaml_writer/YamlWriterTest.cpp +++ b/fastddsspy_yaml/test/unittest/yaml_writer/YamlWriterTest.cpp @@ -103,6 +103,207 @@ TEST(YamlWriterTest, test_ComplexParticipantData_Endpoint) ); } +/** + * Convert a SimpleEndpointData to yaml + */ +TEST(YamlWriterTest, test_SimpleEndpointData) +{ + ddspipe::core::types::Guid guid = ddspipe::core::types::Guid::new_unique_guid(); + SimpleEndpointData data; + data.guid = guid; + data.participant_name = "Name"; + data.topic = {"topic_name", "topic_type"}; + + // Set yaml using set + Yaml yml; + set(yml, data); + + // Set yaml using Yaml functions + Yaml yml_expected; + yml_expected["guid"] = utils::generic_to_string(guid); + yml_expected["participant"] = "Name"; + yml_expected["topic"] = "topic_name [topic_type]"; + + // Check they are the same + ASSERT_EQ( + utils::generic_to_string(yml), + utils::generic_to_string(yml_expected) + ); +} + +/** + * Convert a ComplexEndpointData to yaml + */ +TEST(YamlWriterTest, test_ComplexEndpointData) +{ + ddspipe::core::types::Guid guid = ddspipe::core::types::Guid::new_unique_guid(); + ComplexEndpointData data; + data.guid = guid; + data.participant_name = "Name"; + data.topic = {"topic_name", "topic_type", "partition"}; + data.qos = {}; + + // Set yaml using set + Yaml yml; + set(yml, data); + + // Set yaml using Yaml functions + Yaml yml_expected; + yml_expected["guid"] = utils::generic_to_string(guid); + yml_expected["participant"] = "Name"; + // topic as a map + yml_expected["topic"]["name"] = "topic_name"; + yml_expected["topic"]["type"] = "topic_type"; + yml_expected["topic"]["partitions"] = "partition"; + + // include qos defaults (matches actual output) + yml_expected["qos"]["durability"] = "volatile"; + yml_expected["qos"]["reliability"] = "reliable"; + + // Check they are the same + ASSERT_EQ( + utils::generic_to_string(yml), + utils::generic_to_string(yml_expected) + ); +} + +/** + * Convert a SimpleTopicData to yaml (is_compact = false) + */ +TEST(YamlWriterTest, test_SimpleTopicData_compact_false) +{ + SimpleTopicData data; + + data.name = "Name"; + data.type = "Type"; + data.datawriters = 1; + data.datareaders = 0; + data.rate = {15, "Hz"}; + + // Set yaml using set + Yaml yml; + set(yml, data, false); + + // Set yaml using Yaml functions + Yaml yml_expected; + yml_expected["name"] = "Name"; + yml_expected["type"] = "Type"; + yml_expected["datawriters"] = "1"; + yml_expected["datareaders"] = "0"; + yml_expected["rate"] = "15 Hz"; + + // Check they are the same + ASSERT_EQ( + utils::generic_to_string(yml), + utils::generic_to_string(yml_expected) + ); +} + +/** + * Convert a SimpleTopicData to yaml (is_compact = true) + */ +TEST(YamlWriterTest, test_SimpleTopicData_compact_true) +{ + SimpleTopicData data; + + data.name = "Name"; + data.type = "Type"; + data.datawriters = 1; + data.datareaders = 0; + data.rate = {15, "Hz"}; + + // Set yaml using set + Yaml yml; + set(yml, data, true); + + // Set yaml using Yaml functions + Yaml yml_expected; + yml_expected["topic"] = "Name (Type) (1|0) [15.000000 Hz]"; + + // Check they are the same + ASSERT_EQ( + utils::generic_to_string(yml), + utils::generic_to_string(yml_expected) + ); +} + +/** + * Convert a ComplexTopicData to yaml + */ +TEST(YamlWriterTest, test_ComplexTopicData) +{ + ComplexTopicData data; + ddspipe::core::types::Guid guid = ddspipe::core::types::Guid::new_unique_guid(); + + std::ostringstream ss; + ss << guid; + std::string guid_str = ss.str(); + + data.name = "Name"; + data.type = "Type"; + data.datawriters = {{guid, "partition"}}; + data.datareaders = {}; + data.rate = {15, "Hz"}; + data.discovered = true; + + // Set yaml using set + Yaml yml; + set(yml, data); + + // Set yaml using Yaml functions + Yaml yml_expected; + yml_expected["name"] = "Name"; + yml_expected["type"] = "Type"; + yml_expected["datawriters"].push_back(guid_str + " [partition]"); + yml_expected["datareaders"]; + yml_expected["rate"] = "15 Hz"; + yml_expected["dynamic_type_discovered"] = true; + + // Check they are the same + ASSERT_EQ( + utils::generic_to_string(yml), + utils::generic_to_string(yml_expected) + ); +} + +/** + * Convert a DdsDataData to yaml + */ +TEST(YamlWriterTest, test_DdsDataData) +{ + DdsDataData data; + ddspipe::core::types::Guid guid_writer = ddspipe::core::types::Guid::new_unique_guid(); + + std::ostringstream ss_writer; + ss_writer << guid_writer; + std::string guid_str = ss_writer.str(); + + data.topic.topic_name = "Name"; + data.topic.topic_type = "Type"; + data.writer = guid_writer; + data.partitions = "partition"; + + // Set yaml using set + Yaml yml; + set(yml, data); + + // Set yaml using Yaml functions + Yaml yml_expected; + yml_expected["topic"] = "Name [Type]"; + yml_expected["writer"] = guid_str; + yml_expected["partitions"] = "partition"; + yml_expected["timestamp"] = "1970/01/01 00:00:00"; // empty + + ASSERT_EQ(utils::generic_to_string(yml), + utils::generic_to_string(yml_expected)); + + // Check they are the same + ASSERT_EQ( + utils::generic_to_string(yml), + utils::generic_to_string(yml_expected) + ); +} + int main( int argc, char** argv)