diff --git a/ros2bag/ros2bag/verb/info.py b/ros2bag/ros2bag/verb/info.py index 910cf6407..d101c0c52 100644 --- a/ros2bag/ros2bag/verb/info.py +++ b/ros2bag/ros2bag/verb/info.py @@ -30,6 +30,11 @@ def add_arguments(self, parser, cli_name): # noqa: D102 '-v', '--verbose', action='store_true', help='Display request/response information for services' ) + available_sorting_methods = Info().get_sorting_methods() + parser.add_argument( + '--sort', default='name', choices=available_sorting_methods, + help='Configuration for sorting of output. ' + 'By default sorts by topic name - use this argument to override.') def main(self, *, args): # noqa: D102 if args.topic_name and args.verbose: @@ -37,9 +42,9 @@ def main(self, *, args): # noqa: D102 'will be ignored.') m = Info().read_metadata(args.bag_path, args.storage) if args.verbose: - Info().print_output_verbose(args.bag_path, m) + Info().print_output_verbose(args.bag_path, m, args.sort) else: if args.topic_name: - Info().print_output_topic_name_only(m) + Info().print_output_topic_name_only(m, args.sort) else: - Info().print_output(m) + Info().print_output(m, args.sort) diff --git a/rosbag2_py/CMakeLists.txt b/rosbag2_py/CMakeLists.txt index feb07925f..a4364cc94 100644 --- a/rosbag2_py/CMakeLists.txt +++ b/rosbag2_py/CMakeLists.txt @@ -67,6 +67,7 @@ target_link_libraries(_reader PUBLIC pybind11_add_module(_storage SHARED src/rosbag2_py/_storage.cpp src/rosbag2_py/format_bag_metadata.cpp + src/rosbag2_py/info_sorting_method.cpp ) target_link_libraries(_storage PUBLIC rosbag2_cpp::rosbag2_cpp @@ -86,6 +87,7 @@ pybind11_add_module(_info SHARED src/rosbag2_py/_info.cpp src/rosbag2_py/format_bag_metadata.cpp src/rosbag2_py/format_service_info.cpp + src/rosbag2_py/info_sorting_method.cpp ) target_link_libraries(_info PUBLIC rosbag2_cpp::rosbag2_cpp diff --git a/rosbag2_py/rosbag2_py/_info.pyi b/rosbag2_py/rosbag2_py/_info.pyi index 8a7eb90c8..0b8098235 100644 --- a/rosbag2_py/rosbag2_py/_info.pyi +++ b/rosbag2_py/rosbag2_py/_info.pyi @@ -2,7 +2,8 @@ import rosbag2_py._storage class Info: def __init__(self) -> None: ... - def print_output(self, arg0: rosbag2_py._storage.BagMetadata) -> None: ... - def print_output_topic_name_only(self, arg0: rosbag2_py._storage.BagMetadata) -> None: ... - def print_output_verbose(self, arg0: str, arg1: rosbag2_py._storage.BagMetadata) -> None: ... + def get_sorting_methods(self) -> Set[str]: ... + def print_output(self, arg0: rosbag2_py._storage.BagMetadata, arg1: str) -> None: ... + def print_output_topic_name_only(self, arg0: rosbag2_py._storage.BagMetadata, arg1: str) -> None: ... + def print_output_verbose(self, arg0: str, arg1: rosbag2_py._storage.BagMetadata, arg2: str) -> None: ... def read_metadata(self, arg0: str, arg1: str) -> rosbag2_py._storage.BagMetadata: ... diff --git a/rosbag2_py/src/rosbag2_py/_info.cpp b/rosbag2_py/src/rosbag2_py/_info.cpp index 316d15af8..725506da2 100644 --- a/rosbag2_py/src/rosbag2_py/_info.cpp +++ b/rosbag2_py/src/rosbag2_py/_info.cpp @@ -15,7 +15,9 @@ #include #include #include +#include +#include "info_sorting_method.hpp" #include "format_bag_metadata.hpp" #include "format_service_info.hpp" #include "rosbag2_cpp/info.hpp" @@ -42,15 +44,24 @@ class Info return info_->read_metadata(uri, storage_id); } - void print_output(const rosbag2_storage::BagMetadata & metadata_info) + void print_output( + const rosbag2_storage::BagMetadata & metadata_info, const std::string & sorting_method = "name") { + InfoSortingMethod sort_method = info_sorting_method_from_string(sorting_method); // Output formatted metadata - std::cout << format_bag_meta_data(metadata_info) << std::endl; + std::cout << format_bag_meta_data(metadata_info, false, false, {}, sort_method) << std::endl; } - void print_output_topic_name_only(const rosbag2_storage::BagMetadata & metadata_info) + void print_output_topic_name_only( + const rosbag2_storage::BagMetadata & metadata_info, const std::string & sorting_method) { - for (const auto & topic_info : metadata_info.topics_with_message_count) { + InfoSortingMethod sort_method = info_sorting_method_from_string(sorting_method); + std::vector sorted_idx = generate_sorted_idx( + metadata_info.topics_with_message_count, + sort_method); + + for (auto idx : sorted_idx) { + const auto & topic_info = metadata_info.topics_with_message_count[idx]; if (!rosbag2_cpp::is_service_event_topic( topic_info.topic_metadata.name, topic_info.topic_metadata.type)) @@ -61,7 +72,9 @@ class Info } void print_output_verbose( - const std::string & uri, const rosbag2_storage::BagMetadata & metadata_info) + const std::string & uri, + const rosbag2_storage::BagMetadata & metadata_info, + const std::string & sorting_method = "name") { std::vector> all_services_info; for (auto & file_info : metadata_info.files) { @@ -86,9 +99,20 @@ class Info } } + rosbag2_py::InfoSortingMethod sort_method = info_sorting_method_from_string(sorting_method); // Output formatted metadata and service info - std::cout << format_bag_meta_data(metadata_info, true, true, messages_size); - std::cout << format_service_info(all_services_info, messages_size, true) << std::endl; + std::cout << format_bag_meta_data(metadata_info, true, true, messages_size, sort_method); + std::cout << + format_service_info(all_services_info, messages_size, true, sort_method) << std::endl; + } + + std::unordered_set get_sorting_methods() + { + std::unordered_set sorting_methods; + for (const auto & sorting_method : rosbag2_py::sorting_method_map) { + sorting_methods.insert(sorting_method.first); + } + return sorting_methods; } protected: @@ -105,5 +129,6 @@ PYBIND11_MODULE(_info, m) { .def("read_metadata", &rosbag2_py::Info::read_metadata) .def("print_output", &rosbag2_py::Info::print_output) .def("print_output_topic_name_only", &rosbag2_py::Info::print_output_topic_name_only) - .def("print_output_verbose", &rosbag2_py::Info::print_output_verbose); + .def("print_output_verbose", &rosbag2_py::Info::print_output_verbose) + .def("get_sorting_methods", &rosbag2_py::Info::get_sorting_methods); } diff --git a/rosbag2_py/src/rosbag2_py/format_bag_metadata.cpp b/rosbag2_py/src/rosbag2_py/format_bag_metadata.cpp index cda904e46..7f63c8c18 100644 --- a/rosbag2_py/src/rosbag2_py/format_bag_metadata.cpp +++ b/rosbag2_py/src/rosbag2_py/format_bag_metadata.cpp @@ -29,6 +29,7 @@ #include "rosbag2_storage/bag_metadata.hpp" #include "format_bag_metadata.hpp" +#include "service_event_info.hpp" namespace { @@ -115,7 +116,8 @@ void format_topics_with_type( const std::unordered_map & messages_size, bool verbose, std::stringstream & info_stream, - int indentation_spaces) + int indentation_spaces, + const rosbag2_py::InfoSortingMethod sort_method = rosbag2_py::InfoSortingMethod::NAME) { if (topics.empty()) { info_stream << std::endl; @@ -139,13 +141,15 @@ void format_topics_with_type( info_stream << std::endl; }; + std::vector sorted_idx = rosbag2_py::generate_sorted_idx(topics, sort_method); + size_t number_of_topics = topics.size(); size_t i = 0; // Find first topic which isn't service event topic while (i < number_of_topics && rosbag2_cpp::is_service_event_topic( - topics[i].topic_metadata.name, - topics[i].topic_metadata.type)) + topics[sorted_idx[i]].topic_metadata.name, + topics[sorted_idx[i]].topic_metadata.type)) { i++; } @@ -155,43 +159,31 @@ void format_topics_with_type( return; } - print_topic_info(topics[i]); + print_topic_info(topics[sorted_idx[i]]); for (size_t j = ++i; j < number_of_topics; ++j) { if (rosbag2_cpp::is_service_event_topic( - topics[j].topic_metadata.name, topics[j].topic_metadata.type)) + topics[sorted_idx[j]].topic_metadata.name, topics[sorted_idx[j]].topic_metadata.type)) { continue; } indent(info_stream, indentation_spaces); - print_topic_info(topics[j]); + print_topic_info(topics[sorted_idx[j]]); } } -struct ServiceMetadata -{ - std::string name; - std::string type; - std::string serialization_format; -}; - -struct ServiceInformation -{ - ServiceMetadata service_metadata; - size_t event_message_count = 0; -}; -std::vector> filter_service_event_topic( +std::vector> filter_service_event_topic( const std::vector & topics_with_message_count, size_t & total_service_event_msg_count) { total_service_event_msg_count = 0; - std::vector> service_info_list; + std::vector> service_info_list; for (auto & topic : topics_with_message_count) { if (rosbag2_cpp::is_service_event_topic( topic.topic_metadata.name, topic.topic_metadata.type)) { - auto service_info = std::make_shared(); + auto service_info = std::make_shared(); service_info->service_metadata.name = rosbag2_cpp::service_event_topic_name_to_service_name(topic.topic_metadata.name); service_info->service_metadata.type = @@ -208,11 +200,12 @@ std::vector> filter_service_event_topic( } void format_service_with_type( - const std::vector> & services, + const std::vector> & services, const std::unordered_map & messages_size, bool verbose, std::stringstream & info_stream, - int indentation_spaces) + int indentation_spaces, + const rosbag2_py::InfoSortingMethod sort_method = rosbag2_py::InfoSortingMethod::NAME) { if (services.empty()) { info_stream << std::endl; @@ -221,7 +214,7 @@ void format_service_with_type( auto print_service_info = [&info_stream, &messages_size, verbose]( - const std::shared_ptr & si) -> void { + const std::shared_ptr & si) -> void { info_stream << "Service: " << si->service_metadata.name << " | "; info_stream << "Type: " << si->service_metadata.type << " | "; info_stream << "Event Count: " << si->event_message_count << " | "; @@ -238,11 +231,13 @@ void format_service_with_type( info_stream << std::endl; }; - print_service_info(services[0]); + std::vector sorted_idx = rosbag2_py::generate_sorted_idx(services, sort_method); + + print_service_info(services[sorted_idx[0]]); auto number_of_services = services.size(); for (size_t j = 1; j < number_of_services; ++j) { indent(info_stream, indentation_spaces); - print_service_info(services[j]); + print_service_info(services[sorted_idx[j]]); } } @@ -255,7 +250,8 @@ std::string format_bag_meta_data( const rosbag2_storage::BagMetadata & metadata, bool verbose, bool only_topic, - const std::unordered_map & messages_size) + const std::unordered_map & messages_size, + const InfoSortingMethod sort_method) { auto start_time = metadata.starting_time.time_since_epoch(); auto end_time = start_time + metadata.duration; @@ -267,10 +263,8 @@ std::string format_bag_meta_data( } size_t total_service_event_msg_count = 0; - std::vector> service_info_list; - service_info_list = filter_service_event_topic( - metadata.topics_with_message_count, - total_service_event_msg_count); + std::vector> service_info_list = + filter_service_event_topic(metadata.topics_with_message_count, total_service_event_msg_count); info_stream << std::endl; info_stream << "Files: "; @@ -288,14 +282,22 @@ std::string format_bag_meta_data( std::endl; info_stream << "Topic information: "; format_topics_with_type( - metadata.topics_with_message_count, messages_size, verbose, info_stream, indentation_spaces); + metadata.topics_with_message_count, + messages_size, verbose, info_stream, + indentation_spaces, + sort_method); if (!only_topic) { info_stream << "Service: " << service_info_list.size() << std::endl; info_stream << "Service information: "; if (!service_info_list.empty()) { format_service_with_type( - service_info_list, messages_size, verbose, info_stream, indentation_spaces + 2); + service_info_list, + messages_size, + verbose, + info_stream, + indentation_spaces + 2, + sort_method); } } diff --git a/rosbag2_py/src/rosbag2_py/format_bag_metadata.hpp b/rosbag2_py/src/rosbag2_py/format_bag_metadata.hpp index cbaccb803..0aa99c7a7 100644 --- a/rosbag2_py/src/rosbag2_py/format_bag_metadata.hpp +++ b/rosbag2_py/src/rosbag2_py/format_bag_metadata.hpp @@ -18,6 +18,7 @@ #include #include +#include "info_sorting_method.hpp" #include "rosbag2_storage/bag_metadata.hpp" namespace rosbag2_py @@ -27,7 +28,8 @@ std::string format_bag_meta_data( const rosbag2_storage::BagMetadata & metadata, bool verbose = false, bool only_topic = false, - const std::unordered_map & messages_size = {}); + const std::unordered_map & messages_size = {}, + InfoSortingMethod sort_method = InfoSortingMethod::NAME); } // namespace rosbag2_py diff --git a/rosbag2_py/src/rosbag2_py/format_service_info.cpp b/rosbag2_py/src/rosbag2_py/format_service_info.cpp index 379425915..814076b83 100644 --- a/rosbag2_py/src/rosbag2_py/format_service_info.cpp +++ b/rosbag2_py/src/rosbag2_py/format_service_info.cpp @@ -46,7 +46,8 @@ std::string format_service_info( std::vector> & service_info_list, const std::unordered_map & messages_size, - bool verbose) + bool verbose, + const InfoSortingMethod sort_method) { std::stringstream info_stream; const std::string service_info_string = "Service information: "; @@ -78,11 +79,13 @@ format_service_info( info_stream << std::endl; }; - print_service_info(service_info_list[0]); + std::vector sorted_idx = generate_sorted_idx(service_info_list, sort_method); + + print_service_info(service_info_list[sorted_idx[0]]); auto number_of_services = service_info_list.size(); for (size_t j = 1; j < number_of_services; ++j) { info_stream << std::string(indentation_spaces, ' '); - print_service_info(service_info_list[j]); + print_service_info(service_info_list[sorted_idx[j]]); } return info_stream.str(); diff --git a/rosbag2_py/src/rosbag2_py/format_service_info.hpp b/rosbag2_py/src/rosbag2_py/format_service_info.hpp index 2feb1432c..58b696e08 100644 --- a/rosbag2_py/src/rosbag2_py/format_service_info.hpp +++ b/rosbag2_py/src/rosbag2_py/format_service_info.hpp @@ -20,6 +20,7 @@ #include #include +#include "info_sorting_method.hpp" #include "rosbag2_cpp/info.hpp" namespace rosbag2_py @@ -28,7 +29,8 @@ namespace rosbag2_py std::string format_service_info( std::vector> & service_info, const std::unordered_map & messages_size = {}, - bool verbose = false); + bool verbose = false, + const InfoSortingMethod sort_method = InfoSortingMethod::NAME); } // namespace rosbag2_py diff --git a/rosbag2_py/src/rosbag2_py/info_sorting_method.cpp b/rosbag2_py/src/rosbag2_py/info_sorting_method.cpp new file mode 100644 index 000000000..6a6e9aace --- /dev/null +++ b/rosbag2_py/src/rosbag2_py/info_sorting_method.cpp @@ -0,0 +1,129 @@ +// Copyright 2024 Open Source Robotics Foundation, Inc. +// +// 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. + +#include "rosbag2_storage/bag_metadata.hpp" + +#include "info_sorting_method.hpp" + + +namespace rosbag2_py +{ + +InfoSortingMethod info_sorting_method_from_string(std::string str) +{ + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + auto find_result = sorting_method_map.find(str); + if (find_result == sorting_method_map.end()) { + throw std::runtime_error("Enum value match for \"" + str + "\" string is not found."); + } + return find_result->second; +} + +std::vector generate_sorted_idx( + const std::vector & topics, + const InfoSortingMethod sort_method) +{ + std::vector sorted_idx(topics.size()); + std::iota(sorted_idx.begin(), sorted_idx.end(), 0); + std::sort( + sorted_idx.begin(), + sorted_idx.end(), + [&topics, sort_method](size_t i1, size_t i2) { + bool is_greater = false; + switch (sort_method) { + case InfoSortingMethod::NAME: + is_greater = topics[i1].topic_metadata.name < topics[i2].topic_metadata.name; + break; + case InfoSortingMethod::TYPE: + is_greater = topics[i1].topic_metadata.type < topics[i2].topic_metadata.type; + break; + case InfoSortingMethod::COUNT: + is_greater = topics[i1].message_count < topics[i2].message_count; + break; + default: + throw std::runtime_error("switch is not exhaustive"); + } + return is_greater; + } + ); + return sorted_idx; +} + + +std::vector generate_sorted_idx( + const std::vector> & services, + const InfoSortingMethod sort_method) +{ + std::vector sorted_idx(services.size()); + std::iota(sorted_idx.begin(), sorted_idx.end(), 0); + std::sort( + sorted_idx.begin(), + sorted_idx.end(), + [&services, sort_method](size_t i1, size_t i2) { + bool is_greater = false; + switch (sort_method) { + case InfoSortingMethod::NAME: + is_greater = services[i1]->name < services[i2]->name; + break; + case InfoSortingMethod::TYPE: + is_greater = services[i1]->type < services[i2]->type; + break; + case InfoSortingMethod::COUNT: + { + const auto & count_1 = services[i1]->request_count + services[i1]->response_count; + const auto & count_2 = services[i2]->request_count + services[i2]->response_count; + is_greater = count_1 < count_2; + break; + } + default: + throw std::runtime_error("switch is not exhaustive"); + } + return is_greater; + } + ); + return sorted_idx; +} + + +std::vector generate_sorted_idx( + const std::vector> & services, + const InfoSortingMethod sort_method) +{ + std::vector sorted_idx(services.size()); + std::iota(sorted_idx.begin(), sorted_idx.end(), 0); + std::sort( + sorted_idx.begin(), + sorted_idx.end(), + [&services, sort_method](size_t i1, size_t i2) { + bool is_greater = false; + switch (sort_method) { + case InfoSortingMethod::NAME: + is_greater = services[i1]->service_metadata.name < services[i2]->service_metadata.name; + break; + case InfoSortingMethod::TYPE: + is_greater = services[i1]->service_metadata.type < services[i2]->service_metadata.type; + break; + case InfoSortingMethod::COUNT: + is_greater = services[i1]->event_message_count < services[i2]->event_message_count; + break; + default: + throw std::runtime_error("switch is not exhaustive"); + } + return is_greater; + } + ); + return sorted_idx; +} + +} // namespace rosbag2_py diff --git a/rosbag2_py/src/rosbag2_py/info_sorting_method.hpp b/rosbag2_py/src/rosbag2_py/info_sorting_method.hpp new file mode 100644 index 000000000..7f3ef4bcf --- /dev/null +++ b/rosbag2_py/src/rosbag2_py/info_sorting_method.hpp @@ -0,0 +1,64 @@ +// Copyright 2024 Open Source Robotics Foundation, Inc. +// +// 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. + + +#ifndef ROSBAG2_PY__INFO_SORTING_METHOD_HPP_ +#define ROSBAG2_PY__INFO_SORTING_METHOD_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "rosbag2_storage/topic_metadata.hpp" +#include "rosbag2_storage/bag_metadata.hpp" +#include "rosbag2_cpp/info.hpp" +#include "service_event_info.hpp" + +namespace rosbag2_py +{ + +/// \brief Available sorting methods for info output. +enum class InfoSortingMethod +{ + NAME, + TYPE, + COUNT, +}; + +const std::unordered_map sorting_method_map = { + {"name", InfoSortingMethod::NAME}, + {"type", InfoSortingMethod::TYPE}, + {"count", InfoSortingMethod::COUNT}}; + +InfoSortingMethod info_sorting_method_from_string(std::string str); + +std::vector generate_sorted_idx( + const std::vector & topics, + InfoSortingMethod sort_method = InfoSortingMethod::NAME); + +std::vector generate_sorted_idx( + const std::vector> & services, + InfoSortingMethod sort_method = InfoSortingMethod::NAME); + +std::vector generate_sorted_idx( + const std::vector> & services, + InfoSortingMethod sort_method = InfoSortingMethod::NAME); + +} // namespace rosbag2_py + +#endif // ROSBAG2_PY__INFO_SORTING_METHOD_HPP_ diff --git a/rosbag2_py/src/rosbag2_py/service_event_info.hpp b/rosbag2_py/src/rosbag2_py/service_event_info.hpp new file mode 100644 index 000000000..2c5746a40 --- /dev/null +++ b/rosbag2_py/src/rosbag2_py/service_event_info.hpp @@ -0,0 +1,39 @@ +// Copyright 2024 Open Source Robotics Foundation, Inc. +// +// 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. + + +#ifndef ROSBAG2_PY__SERVICE_EVENT_INFO_HPP_ +#define ROSBAG2_PY__SERVICE_EVENT_INFO_HPP_ + +#include + +namespace rosbag2_py +{ + +struct ServiceMetadata +{ + std::string name; + std::string type; + std::string serialization_format; +}; + +struct ServiceEventInformation +{ + ServiceMetadata service_metadata; + size_t event_message_count = 0; +}; + +} // namespace rosbag2_py + +#endif // ROSBAG2_PY__SERVICE_EVENT_INFO_HPP_ diff --git a/rosbag2_tests/test/rosbag2_tests/test_rosbag2_info_end_to_end.cpp b/rosbag2_tests/test/rosbag2_tests/test_rosbag2_info_end_to_end.cpp index 61808d1c9..92e1a59ef 100644 --- a/rosbag2_tests/test/rosbag2_tests/test_rosbag2_info_end_to_end.cpp +++ b/rosbag2_tests/test/rosbag2_tests/test_rosbag2_info_end_to_end.cpp @@ -185,6 +185,102 @@ TEST_P(InfoEndToEndTestFixture, info_basic_types_and_arrays_with_verbose_option_ "Size Contribution: 2.7 KiB | Serialization Format: cdr")); } +TEST_P(InfoEndToEndTestFixture, info_output_default_sorted_by_name_test) { + internal::CaptureStdout(); + auto exit_code = execute_and_wait_until_completion("ros2 bag info cdr_test", bags_path_); + std::string output = internal::GetCapturedStdout(); + + EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS)); + EXPECT_THAT( + output, HasSubstr( + "Topic: /array_topic | Type: test_msgs/msg/Arrays | Count: 4 | " + "Serialization Format: cdr\n" + " Topic: /test_topic | Type: test_msgs/msg/BasicTypes | Count: 3 | " + "Serialization Format: cdr")); +} + +TEST_P(InfoEndToEndTestFixture, info_output_sorted_by_name_test) { + internal::CaptureStdout(); + auto exit_code = execute_and_wait_until_completion( + "ros2 bag info cdr_test --sort name", bags_path_); + std::string output = internal::GetCapturedStdout(); + + EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS)); + EXPECT_THAT( + output, HasSubstr( + "Topic: /array_topic | Type: test_msgs/msg/Arrays | Count: 4 | " + "Serialization Format: cdr\n" + " Topic: /test_topic | Type: test_msgs/msg/BasicTypes | Count: 3 | " + "Serialization Format: cdr")); +} + +TEST_P(InfoEndToEndTestFixture, info_output_sorted_by_type_test) { + internal::CaptureStdout(); + auto exit_code = execute_and_wait_until_completion( + "ros2 bag info cdr_test --sort type", bags_path_); + std::string output = internal::GetCapturedStdout(); + + EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS)); + EXPECT_THAT( + output, HasSubstr( + "Topic: /array_topic | Type: test_msgs/msg/Arrays | Count: 4 | " + "Serialization Format: cdr\n" + " Topic: /test_topic | Type: test_msgs/msg/BasicTypes | Count: 3 | " + "Serialization Format: cdr")); +} + +TEST_P(InfoEndToEndTestFixture, info_output_sorted_by_count_test) { + internal::CaptureStdout(); + auto exit_code = execute_and_wait_until_completion( + "ros2 bag info cdr_test --sort count", bags_path_); + std::string output = internal::GetCapturedStdout(); + + EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS)); + EXPECT_THAT( + output, HasSubstr( + "Topic: /test_topic | Type: test_msgs/msg/BasicTypes | Count: 3 | " + "Serialization Format: cdr\n" + " Topic: /array_topic | Type: test_msgs/msg/Arrays | Count: 4 | " + "Serialization Format: cdr")); +} + +TEST_P(InfoEndToEndTestFixture, info_output_topics_only_sorted_by_count_test) { + internal::CaptureStdout(); + auto exit_code = execute_and_wait_until_completion( + "ros2 bag info -t cdr_test --sort count", bags_path_); + std::string output = internal::GetCapturedStdout(); + + EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS)); + EXPECT_THAT( + output, HasSubstr( + "/test_topic\n" + "/array_topic")); +} + +TEST_P(InfoEndToEndTestFixture, info_output_topics_only_default_sorted_by_name_test) { + internal::CaptureStdout(); + auto exit_code = execute_and_wait_until_completion( + "ros2 bag info -t cdr_test", bags_path_); + std::string output = internal::GetCapturedStdout(); + + EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS)); + EXPECT_THAT( + output, HasSubstr( + "/array_topic\n" + "/test_topic")); +} + +TEST_P(InfoEndToEndTestFixture, info_fails_gracefully_sort_method_does_not_exist_test) { + internal::CaptureStderr(); + auto exit_code = execute_and_wait_until_completion( + "ros2 bag info cdr_test --sort non_existent", bags_path_); + auto error_output = internal::GetCapturedStderr(); + + EXPECT_THAT(exit_code, Ne(EXIT_SUCCESS)); + EXPECT_THAT( + error_output, HasSubstr("--sort: invalid choice: 'non_existent'")); +} + // TODO(Martin-Idel-SI): Revisit exit code non-zero here, gracefully should be exit code zero TEST_P(InfoEndToEndTestFixture, info_fails_gracefully_if_bag_does_not_exist) { internal::CaptureStderr();