From 1370c7f8f12b53598bb4c3ef52cc1b936bd1df85 Mon Sep 17 00:00:00 2001 From: Martin Idel Date: Fri, 28 Sep 2018 14:58:41 +0200 Subject: [PATCH] Introduce rosbag2_transport layer and CLI (#38) * rosbag2_transport package with python interface * use cpp for python extension * use rosbag2_transport cpp API * use rosbag2_transport API in cli * linters * GH-25 Rename target librosbag2 to rosbag2 CMake already prepends libraries with `lib`, so the old name resulted in `liblibrosbag2` * GH-21 Initial call of rosbag2.record() from rosbag2_transport * GH-21 Add missing copyright header * GH-21 Cleanup clang tidy issues * GH-21 Remove rclcpp dependency from rosbag2 * GH-21 Wire rosbag play into CLI * GH-21 Add missing test_depend in rosbag2_transport package.xml * GH-21 Unify name of python import * GH-21 Enable -a in CLI, show help on wrong args * GH-85 Introduce topic and type struct for readability * GH-85 Do not export sqlite3 as dependency from default plugins - not referenced in header, therefore unnecessary * GH-85 Move rosbag2 except typesupport to rosbag2_transport * GH-85 Add rosbag2 wrapper * GH-85 Change signature of create_topic to take TopicWithType * GH-85 Use rosbag2 in rosbag2_transport - Don't link against rosbag2_storage anymore * GH-84 Cleanup package.xmls and CMakeLists everywhere * GH-21 Add missing init() and shutdown() in record * GH-85 Fix Windows build * GH-85 Add visibility control to rosbag2 * GH-85 Cleanup and documentation * GH-87 Add test package rosbag2_tests * GH-87 [WIP] Add first working prototype of an end-to-end test * GH-87 Use test_msgs instead of std_msgs/String in end-to-end test * GH-87 Use SIGTERM instead of SIGKILL and refactor test * GH-87 Make end-to-end test work on Windows * GH-87 Fix uncrustify * GH-87 Refactor end-to-end test fixture * GH-21 Extend transport python module interface The python interface should accept all options that can be passed to rosbag2_transport * GH-87 Fix test fixture for Windows * GH-87 Refactor test fixture * GH-87 Separate record from play end-to-end test * GH-87 Make record end-to-end test work * GH-87 Publish before recording to create topic * GH-87 Fix record all on Windows * GH-87 Check for topics instead of all * GH-87 Wait until rosbag record opened database * GH-87 Delete directory recursively * GH-87 Delete directories recursively on Linux * GH-87 Reset ROS_DOMAIN_ID to protect against concurrent tests * GH-89 Make rosbag2 interfaces virtual and add explicit open() method This allows downstream packages (e.g. rosbag2_transport) to mock these interfaces in tests. * GH-87 Improve test and refactoring * GH-87 Minor refactoring to increase test readability * GH-87 Fix environmental variable behaviour on Mac * GH-87 Fix Windows build * GH-89 Use mock reader and writer in rosbag2_transport tests * GH-87 Add play end_to_end test * GH-87 Improvements of test * GH-87 Fix Windows build * GH-89 Cleanup: small documentation fixes. * GH-89 [WIP] Test if Writer and Reader work with class visibility * GH-87 Stabilize rosbag2_play test * GH-87 Minor refactoring of tests * GH-87 Rename end to end tests * add license agreement * GH-89 Simplification of writing to in-memory storage * GH-89 Stabilize transport tests * GH-87 Refactoring of tests - Extract temporary file handling - Extract subscription management * GH-87 Add pytest cache to gitignore * GH-87 Refactoring of play test - Extract Publisher manager * GH-87 Extract record test fixture for readability * GH-89 Refactor transport tests - Use subscription and publisher manager just as e2e tests - Use options in recording * GH-89 Use temporary directory fixture in sqlite tests * GH-89 Conform to naming standard for tests * GH-89 Prevent burst publishing of all messages - Improves test stability * GH-89 Improve play stability - Sometimes the first message is lost (discovery) * GH-25 Fix package.xmls * Consistently use project name in CMakeLists * Minor cleanup - make rosbag2_transport description more expressive - hide unnecessary methods in typesupport_helpers - fix incorrect logging in tests - minor cleanup * Change name of nodes in rosbag2_transport * Cleanup folder structure in rosbag2_storage and rosbag2_tests - use src// and test// folders everywhere - harmonises with all other packages - results in better header guards * Export sqlite3 dependency as package dependency * Create node in Rosbag2Transport always * Only hold one node in rosbag2_transport * Move all duplicate files to common package * Adapt namespacing in test commons package - use "using namespace" declaratives for tests - use package name as namespace * Replace "Waiting for messages..." message * GH-25 rename rosbag2_test_commons -> rosbag2_test_common * GH-25 Overwrite already existing test.bag when recording This is a temporary solution and will be handled properly once a file path can be passed via the cli. * GH-25 Cleanups - Log every subscription - move all dependencies onside BUILD_TESTING for rosbag2_test_common * fix cmake typo for test_common * Remove superfluous loop in rosbag2 transport * Delete superfluous test_msgs dependency * Add rclcpp to test dependencies - Apparently ament_export_dependencies does not work in rosbag2_test_common * Fix rosbag2 node test - Clock topic is no longer present on all nodes - Remove assumptions on foreign ros topics * Fix dependencies by exporting them explicitly --- .gitignore | 1 + ros2bag/COLCON_IGNORE | 0 ros2bag/package.xml | 1 + ros2bag/ros2bag/verb/play.py | 4 +- ros2bag/ros2bag/verb/record.py | 32 +-- rosbag2/CMakeLists.txt | 116 ++-------- rosbag2/include/rosbag2/rosbag2.hpp | 88 -------- rosbag2/include/rosbag2/sequential_reader.hpp | 90 ++++++++ ...2_play_options.hpp => storage_options.hpp} | 14 +- rosbag2/include/rosbag2/types.hpp | 27 +++ .../rosbag2/typesupport_helpers.hpp | 8 +- rosbag2/include/rosbag2/writer.hpp | 79 +++++++ rosbag2/package.xml | 4 - rosbag2/src/rosbag2/demo_record.cpp | 54 ----- rosbag2/src/rosbag2/rosbag2.cpp | 138 ------------ rosbag2/src/rosbag2/sequential_reader.cpp | 61 ++++++ rosbag2/src/rosbag2/typesupport_helpers.cpp | 2 +- rosbag2/src/rosbag2/writer.cpp | 56 +++++ .../rosbag2/rosbag2_play_integration_test.cpp | 152 ------------- .../rosbag2_record_integration_fixture.hpp | 164 -------------- rosbag2/test/rosbag2/rosbag2_test_fixture.hpp | 165 --------------- .../rosbag2_typesupport_helpers_test.cpp | 2 +- .../test/rosbag2/test_memory_management.cpp | 53 ----- rosbag2_storage/CMakeLists.txt | 17 +- .../base_read_interface.hpp | 5 +- .../base_write_interface.hpp | 3 +- .../rosbag2_storage/topic_with_type.hpp | 25 +-- .../impl/storage_factory_impl.hpp | 6 +- .../{ => rosbag2_storage}/storage_factory.cpp | 0 .../{ => rosbag2_storage}/test_plugin.cpp | 9 +- .../{ => rosbag2_storage}/test_plugin.hpp | 12 +- .../{ => rosbag2_storage}/test_plugin.xml | 0 .../test_read_only_plugin.cpp | 5 +- .../test_read_only_plugin.hpp | 9 +- .../{ => rosbag2_storage}/test_ros_helper.cpp | 0 .../test_storage_factory.cpp | 0 .../CMakeLists.txt | 36 ++-- .../sqlite/sqlite_storage.hpp | 7 +- rosbag2_storage_default_plugins/package.xml | 19 +- .../sqlite/sqlite_storage.cpp | 12 +- .../sqlite/storage_test_fixture.hpp | 62 +----- ...ation_test.cpp => test_sqlite_storage.cpp} | 12 +- ...ation_test.cpp => test_sqlite_wrapper.cpp} | 0 rosbag2_test_common/CMakeLists.txt | 43 ++++ .../rosbag2_test_common/memory_management.hpp | 60 ++++-- .../rosbag2_test_common/publisher_manager.hpp | 83 ++++++++ .../subscription_manager.hpp | 99 +++++++++ .../temporary_directory_fixture.hpp | 108 ++++++++++ rosbag2_test_common/package.xml | 21 ++ rosbag2_tests/CMakeLists.txt | 60 ++++++ rosbag2_tests/package.xml | 27 +++ rosbag2_tests/resources/test.bag | Bin 0 -> 12288 bytes rosbag2_tests/resources/test.bag-shm | Bin 0 -> 32768 bytes rosbag2_tests/resources/test.bag-wal | Bin 0 -> 61832 bytes .../test/rosbag2_tests/record_fixture.hpp | 200 ++++++++++++++++++ .../test_rosbag2_play_end_to_end.cpp | 114 ++++++++++ .../test_rosbag2_record_end_to_end.cpp | 49 +++++ rosbag2_transport/CMakeLists.txt | 133 ++++++++++++ .../cmake/configure_python.cmake | 56 +++++ .../include/rosbag2_transport/logging.hpp | 61 ++++++ .../rosbag2_transport/play_options.hpp | 31 +++ .../rosbag2_transport/record_options.hpp | 32 +++ .../rosbag2_transport/rosbag2_transport.hpp | 94 ++++++++ .../rosbag2_transport/storage_options.hpp | 29 +++ .../rosbag2_transport/visibility_control.hpp | 56 +++++ rosbag2_transport/package.xml | 25 +++ .../rosbag2_transport/__init__.py | 31 +++ .../rosbag2_transport}/generic_publisher.cpp | 4 +- .../rosbag2_transport}/generic_publisher.hpp | 10 +- .../generic_subscription.cpp | 8 +- .../generic_subscription.hpp | 10 +- .../src/rosbag2_transport}/player.cpp | 43 ++-- .../src/rosbag2_transport}/player.hpp | 32 +-- .../rosbag2_transport}/replayable_message.hpp | 14 +- .../src/rosbag2_transport}/rosbag2_node.cpp | 27 +-- .../src/rosbag2_transport}/rosbag2_node.hpp | 10 +- .../rosbag2_transport/rosbag2_transport.cpp | 142 +++++++++++++ .../rosbag2_transport_python.cpp | 134 ++++++++++++ .../mock_sequential_reader.hpp | 61 ++++++ .../test/rosbag2_transport/mock_writer.hpp | 65 ++++++ .../record_integration_fixture.hpp | 92 ++++++++ .../rosbag2_transport_test_fixture.hpp | 86 ++++++++ .../test/rosbag2_transport/test_play.cpp | 108 ++++++++++ .../rosbag2_transport/test_play_timing.cpp | 24 +-- .../test/rosbag2_transport/test_record.cpp | 25 +-- .../rosbag2_transport/test_record_all.cpp | 23 +- .../rosbag2_transport/test_rosbag2_node.cpp | 18 +- 87 files changed, 2666 insertions(+), 1232 deletions(-) delete mode 100644 ros2bag/COLCON_IGNORE delete mode 100644 rosbag2/include/rosbag2/rosbag2.hpp create mode 100644 rosbag2/include/rosbag2/sequential_reader.hpp rename rosbag2/include/rosbag2/{rosbag2_play_options.hpp => storage_options.hpp} (76%) create mode 100644 rosbag2/include/rosbag2/types.hpp rename rosbag2/{src => include}/rosbag2/typesupport_helpers.hpp (89%) create mode 100644 rosbag2/include/rosbag2/writer.hpp delete mode 100644 rosbag2/src/rosbag2/demo_record.cpp delete mode 100644 rosbag2/src/rosbag2/rosbag2.cpp create mode 100644 rosbag2/src/rosbag2/sequential_reader.cpp create mode 100644 rosbag2/src/rosbag2/writer.cpp delete mode 100644 rosbag2/test/rosbag2/rosbag2_play_integration_test.cpp delete mode 100644 rosbag2/test/rosbag2/rosbag2_record_integration_fixture.hpp delete mode 100644 rosbag2/test/rosbag2/rosbag2_test_fixture.hpp delete mode 100644 rosbag2/test/rosbag2/test_memory_management.cpp rename rosbag2/src/rosbag2/demo_play.cpp => rosbag2_storage/include/rosbag2_storage/topic_with_type.hpp (63%) rename rosbag2_storage/src/{ => rosbag2_storage}/impl/storage_factory_impl.hpp (96%) rename rosbag2_storage/src/{ => rosbag2_storage}/storage_factory.cpp (100%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_plugin.cpp (85%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_plugin.hpp (79%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_plugin.xml (100%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_read_only_plugin.cpp (91%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_read_only_plugin.hpp (81%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_ros_helper.cpp (100%) rename rosbag2_storage/test/{ => rosbag2_storage}/test_storage_factory.cpp (100%) rename rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/{sqlite_storage_integration_test.cpp => test_sqlite_storage.cpp} (91%) rename rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/{sqlite_wrapper_integration_test.cpp => test_sqlite_wrapper.cpp} (100%) create mode 100644 rosbag2_test_common/CMakeLists.txt rename rosbag2/test/rosbag2/test_memory_management.hpp => rosbag2_test_common/include/rosbag2_test_common/memory_management.hpp (55%) create mode 100644 rosbag2_test_common/include/rosbag2_test_common/publisher_manager.hpp create mode 100644 rosbag2_test_common/include/rosbag2_test_common/subscription_manager.hpp create mode 100644 rosbag2_test_common/include/rosbag2_test_common/temporary_directory_fixture.hpp create mode 100644 rosbag2_test_common/package.xml create mode 100644 rosbag2_tests/CMakeLists.txt create mode 100644 rosbag2_tests/package.xml create mode 100644 rosbag2_tests/resources/test.bag create mode 100644 rosbag2_tests/resources/test.bag-shm create mode 100644 rosbag2_tests/resources/test.bag-wal create mode 100644 rosbag2_tests/test/rosbag2_tests/record_fixture.hpp create mode 100644 rosbag2_tests/test/rosbag2_tests/test_rosbag2_play_end_to_end.cpp create mode 100644 rosbag2_tests/test/rosbag2_tests/test_rosbag2_record_end_to_end.cpp create mode 100644 rosbag2_transport/CMakeLists.txt create mode 100644 rosbag2_transport/cmake/configure_python.cmake create mode 100644 rosbag2_transport/include/rosbag2_transport/logging.hpp create mode 100644 rosbag2_transport/include/rosbag2_transport/play_options.hpp create mode 100644 rosbag2_transport/include/rosbag2_transport/record_options.hpp create mode 100644 rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp create mode 100644 rosbag2_transport/include/rosbag2_transport/storage_options.hpp create mode 100644 rosbag2_transport/include/rosbag2_transport/visibility_control.hpp create mode 100644 rosbag2_transport/package.xml create mode 100644 rosbag2_transport/rosbag2_transport/__init__.py rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/generic_publisher.cpp (95%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/generic_publisher.hpp (82%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/generic_subscription.cpp (95%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/generic_subscription.hpp (93%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/player.cpp (73%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/player.hpp (64%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/replayable_message.hpp (70%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/rosbag2_node.cpp (86%) rename {rosbag2/src/rosbag2 => rosbag2_transport/src/rosbag2_transport}/rosbag2_node.hpp (89%) create mode 100644 rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp create mode 100644 rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp create mode 100644 rosbag2_transport/test/rosbag2_transport/mock_sequential_reader.hpp create mode 100644 rosbag2_transport/test/rosbag2_transport/mock_writer.hpp create mode 100644 rosbag2_transport/test/rosbag2_transport/record_integration_fixture.hpp create mode 100644 rosbag2_transport/test/rosbag2_transport/rosbag2_transport_test_fixture.hpp create mode 100644 rosbag2_transport/test/rosbag2_transport/test_play.cpp rename rosbag2/test/rosbag2/rosbag2_play_timing_integration_test.cpp => rosbag2_transport/test/rosbag2_transport/test_play_timing.cpp (71%) rename rosbag2/test/rosbag2/rosbag2_record_all_integration_test.cpp => rosbag2_transport/test/rosbag2_transport/test_record.cpp (68%) rename rosbag2/test/rosbag2/rosbag2_record_integration_test.cpp => rosbag2_transport/test/rosbag2_transport/test_record_all.cpp (68%) rename rosbag2/test/rosbag2/rosbag2_rosbag_node_test.cpp => rosbag2_transport/test/rosbag2_transport/test_rosbag2_node.cpp (89%) diff --git a/.gitignore b/.gitignore index bfa8015b9a..26bc611653 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ cmake-build-debug/ cmake-build-release/ build/ venv/ +**/.pytest_cache/ diff --git a/ros2bag/COLCON_IGNORE b/ros2bag/COLCON_IGNORE deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ros2bag/package.xml b/ros2bag/package.xml index 3f1a5e9433..e1a1cf6297 100644 --- a/ros2bag/package.xml +++ b/ros2bag/package.xml @@ -10,6 +10,7 @@ Apache License 2.0 ros2cli + rosbag2_transport ament_copyright ament_flake8 diff --git a/ros2bag/ros2bag/verb/play.py b/ros2bag/ros2bag/verb/play.py index 92e9f1f4a8..9edc4e35ba 100644 --- a/ros2bag/ros2bag/verb/play.py +++ b/ros2bag/ros2bag/verb/play.py @@ -14,6 +14,8 @@ from ros2bag.verb import VerbExtension +from rosbag2_transport import rosbag2_transport_py + class PlayVerb(VerbExtension): """ros2 bag play.""" @@ -24,4 +26,4 @@ def add_arguments(self, parser, cli_name): # noqa: D102 def main(self, *, args): # noqa: D102 bag_file = args.bag_file - print('calling ros2 bag play on', bag_file) + rosbag2_transport_py.play(uri=bag_file, storage_id='sqlite3') diff --git a/ros2bag/ros2bag/verb/record.py b/ros2bag/ros2bag/verb/record.py index 2d51335013..dc2b00fb80 100644 --- a/ros2bag/ros2bag/verb/record.py +++ b/ros2bag/ros2bag/verb/record.py @@ -12,19 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys -import time +import os + +from ros2bag.verb import VerbExtension -from ros2cli.node.strategy import add_arguments from ros2cli.node.strategy import NodeStrategy +from ros2cli.node.strategy import add_arguments -from ros2bag.verb import VerbExtension +from rosbag2_transport import rosbag2_transport_py class RecordVerb(VerbExtension): """ros2 bag record.""" def add_arguments(self, parser, cli_name): # noqa: D102 + self._subparser = parser + add_arguments(parser) parser.add_argument( '-a', '--all', action='store_true', help='recording all topics') @@ -36,11 +39,16 @@ def main(self, *, args): # noqa: D102 print('invalid choice: Can not specify topics and -a at the same time') return - with NodeStrategy(args) as node: - if args.all: - t_and_n = node.get_topic_names_and_types() - print(t_and_n) - topics = [t for t,n in node.get_topic_names_and_types()] - if args.topics: - topics = args.topics - print('topics to be recorded:', topics) + uri = 'test.bag' + if os.path.exists(uri): + os.remove(uri) + print('warning: Overwriting already existing \'test.bag\'!') + + storage_id = 'sqlite3' + + if args.all: + rosbag2_transport_py.record(uri=uri, storage_id=storage_id, all=True) + elif args.topics and len(args.topics) > 0: + rosbag2_transport_py.record(uri=uri, storage_id=storage_id, topics=args.topics) + else: + self._subparser.print_help() diff --git a/rosbag2/CMakeLists.txt b/rosbag2/CMakeLists.txt index 7600030c35..c06a080b3c 100644 --- a/rosbag2/CMakeLists.txt +++ b/rosbag2/CMakeLists.txt @@ -19,35 +19,24 @@ find_package(ament_cmake REQUIRED) find_package(ament_index_cpp REQUIRED) find_package(poco_vendor) find_package(Poco COMPONENTS Foundation) -find_package(rcl REQUIRED) -find_package(rclcpp REQUIRED) find_package(rcutils REQUIRED) find_package(rosbag2_storage REQUIRED) find_package(rosidl_generator_cpp REQUIRED) -find_package(shared_queues_vendor REQUIRED) -add_library( - librosbag2 - SHARED - src/rosbag2/rosbag2.cpp - src/rosbag2/player.cpp +add_library(${PROJECT_NAME} SHARED + src/rosbag2/sequential_reader.cpp src/rosbag2/typesupport_helpers.cpp - src/rosbag2/generic_publisher.cpp - src/rosbag2/generic_subscription.cpp - src/rosbag2/rosbag2_node.cpp) + src/rosbag2/writer.cpp) -ament_target_dependencies(librosbag2 +ament_target_dependencies(${PROJECT_NAME} ament_index_cpp Poco - rcl - rclcpp rcutils rosbag2_storage - rosidl_generator_c - shared_queues_vendor + rosidl_generator_cpp ) -target_include_directories(librosbag2 +target_include_directories(${PROJECT_NAME} PUBLIC $ $ @@ -55,107 +44,32 @@ target_include_directories(librosbag2 # Causes the visibility macros to use dllexport rather than dllimport, # which is appropriate when building the dll but not consuming it. -target_compile_definitions(librosbag2 PRIVATE "ROSBAG2_BUILDING_DLL") - -add_executable(${PROJECT_NAME}_record src/rosbag2/demo_record.cpp) -target_link_libraries(${PROJECT_NAME}_record librosbag2) - -add_executable(${PROJECT_NAME}_play src/rosbag2/demo_play.cpp) -target_link_libraries(${PROJECT_NAME}_play librosbag2) +target_compile_definitions(${PROJECT_NAME} PRIVATE "ROSBAG2_BUILDING_DLL") install( DIRECTORY include/ DESTINATION include) install( - TARGETS librosbag2 + TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib) + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + +ament_export_include_directories(include) +ament_export_libraries(${PROJECT_NAME}) +ament_export_dependencies(rosbag2_storage) if(BUILD_TESTING) find_package(ament_cmake_gmock REQUIRED) find_package(ament_lint_auto REQUIRED) - find_package(rosbag2_storage_default_plugins REQUIRED) - find_package(test_msgs REQUIRED) ament_lint_auto_find_test_dependencies() - ament_add_gmock(rosbag2_record_integration_test - test/rosbag2/rosbag2_record_integration_test.cpp - test/rosbag2/test_memory_management.cpp - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET rosbag2_record_integration_test) - target_link_libraries(rosbag2_record_integration_test librosbag2 - ${test_msgs_LIBRARIES} - ${rosbag2_storage_default_plugins_LIBRARIES}) - target_include_directories(rosbag2_record_integration_test PRIVATE - ${test_msgs_INCLUDE_DIRS} - ${rosbag2_storage_default_plugins_INCLUDE_DIRS}) - endif() - - ament_add_gmock(rosbag2_record_all_integration_test - test/rosbag2/rosbag2_record_all_integration_test.cpp - test/rosbag2/test_memory_management.cpp - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET rosbag2_record_all_integration_test) - target_link_libraries(rosbag2_record_all_integration_test librosbag2 - ${test_msgs_LIBRARIES} - ${rosbag2_storage_default_plugins_LIBRARIES}) - target_include_directories(rosbag2_record_all_integration_test PRIVATE - ${test_msgs_INCLUDE_DIRS} - ${rosbag2_storage_default_plugins_INCLUDE_DIRS}) - endif() - - ament_add_gmock(rosbag2_play_integration_test - test/rosbag2/rosbag2_play_integration_test.cpp - test/rosbag2/test_memory_management.cpp - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET rosbag2_play_integration_test) - target_link_libraries(rosbag2_play_integration_test librosbag2) - ament_target_dependencies(rosbag2_play_integration_test test_msgs) - endif() - - ament_add_gmock(rosbag2_play_timing_integration_test - test/rosbag2/rosbag2_play_timing_integration_test.cpp - test/rosbag2/test_memory_management.cpp - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET rosbag2_play_timing_integration_test) - target_link_libraries(rosbag2_play_timing_integration_test librosbag2) - ament_target_dependencies(rosbag2_play_timing_integration_test test_msgs) - endif() - ament_add_gmock(rosbag2_typesupport_helpers_test test/rosbag2/rosbag2_typesupport_helpers_test.cpp - src/rosbag2/typesupport_helpers.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) if(TARGET rosbag2_typesupport_helpers_test) - ament_target_dependencies(rosbag2_typesupport_helpers_test - ament_index_cpp - Poco - rcl - rosidl_generator_cpp - ) - endif() - - ament_add_gmock(rosbag2_rosbag_node_test - test/rosbag2/rosbag2_rosbag_node_test.cpp - test/rosbag2/test_memory_management.cpp - src/rosbag2/generic_publisher.cpp - src/rosbag2/generic_subscription.cpp - src/rosbag2/rosbag2_node.cpp - src/rosbag2/typesupport_helpers.cpp - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET rosbag2_rosbag_node_test) - target_include_directories(rosbag2_rosbag_node_test - PUBLIC - $ - $ - ) - ament_target_dependencies(rosbag2_rosbag_node_test - ament_index_cpp - Poco - rclcpp - test_msgs - ) + target_link_libraries(rosbag2_typesupport_helpers_test rosbag2) endif() endif() diff --git a/rosbag2/include/rosbag2/rosbag2.hpp b/rosbag2/include/rosbag2/rosbag2.hpp deleted file mode 100644 index 317bceb25e..0000000000 --- a/rosbag2/include/rosbag2/rosbag2.hpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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__ROSBAG2_HPP_ -#define ROSBAG2__ROSBAG2_HPP_ - -#include -#include -#include -#include -#include - -#include "rclcpp/rclcpp.hpp" - -#include "rosbag2_storage/storage_factory.hpp" -#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" -#include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" -#include "rosbag2/rosbag2_play_options.hpp" -#include "rosbag2/visibility_control.hpp" - -namespace rosbag2 -{ - -class GenericPublisher; -class GenericSubscription; -class Rosbag2Node; -class Player; - -class Rosbag2 -{ -public: - ROSBAG2_PUBLIC - Rosbag2(); - - /** - * Records topics to a bagfile. Subscription happens at startup time, hence the topics must - * exist when "record" is called. - * - * \param file_name Name of the bagfile to write - * \param topic_names Vector of topics to subscribe to. Topics must exist at startup time. If - * the vector is empty, all topics will be subscribed. - */ - ROSBAG2_PUBLIC - void record(const std::string & file_name, const std::vector & topic_names); - - /** - * Records all available topics to a bagfile. Subscription happens at startup time, hence only - * topics available at startup time are recorded. - * - * \param file_name Name of the bagfile to write - */ - ROSBAG2_PUBLIC - void record(const std::string & file_name); - - /** - * Replay all topics in a bagfile. - * - * \param file_name Name of the bagfile to replay - */ - ROSBAG2_PUBLIC - void play(const std::string & file_name, const Rosbag2PlayOptions & options); - -private: - std::shared_ptr - create_subscription( - std::shared_ptr storage, - const std::string & topic_name, const std::string & topic_type) const; - - rosbag2_storage::StorageFactory factory_; - std::shared_ptr node_; - std::vector> subscriptions_; - std::map> publishers_; -}; - -} // namespace rosbag2 - -#endif // ROSBAG2__ROSBAG2_HPP_ diff --git a/rosbag2/include/rosbag2/sequential_reader.hpp b/rosbag2/include/rosbag2/sequential_reader.hpp new file mode 100644 index 0000000000..46641dc1ee --- /dev/null +++ b/rosbag2/include/rosbag2/sequential_reader.hpp @@ -0,0 +1,90 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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__SEQUENTIAL_READER_HPP_ +#define ROSBAG2__SEQUENTIAL_READER_HPP_ + +#include +#include +#include + +#include "rosbag2_storage/storage_factory.hpp" +#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" +#include "rosbag2/storage_options.hpp" +#include "rosbag2/types.hpp" +#include "rosbag2/visibility_control.hpp" + +// This is necessary because of using stl types here. It is completely safe, because +// a) the member is not accessible from the outside +// b) there are no inline functions. +#ifdef _WIN32 +# pragma warning(push) +# pragma warning(disable:4251) +#endif + +namespace rosbag2 +{ +/** + * The SequentialReader allows opening and reading messages of a bag. Messages will be read + * sequentially according to timestamp. + */ +class ROSBAG2_PUBLIC SequentialReader +{ +public: + virtual ~SequentialReader(); + + /** + * Open a rosbag for reading messages sequentially (time-ordered). Throws if file could not be + * opened. This must be called before any other function is used. The rosbag is + * automatically closed on destruction. + * + * \param options Options to configure the storage + */ + virtual void open(const StorageOptions & options); + + /** + * Ask whether the underlying bagfile contains at least one more message. + * + * \return true if storage contains at least one more message + * \throws runtime_error if the Reader is not open. + */ + virtual bool has_next(); + + /** + * Read next message from storage. Will throw if no more messages are available. + * + * Expected usage: + * if (writer.has_next()) message = writer.read_next(); + * + * \return next message in serialized form + * \throws runtime_error if the Reader is not open. + */ + virtual std::shared_ptr read_next(); + + /** + * Ask bagfile for all topics (including their type identifier) that were recorded. + * + * \return vector of topics with topic name and type as std::string + * \throws runtime_error if the Reader is not open. + */ + virtual std::vector get_all_topics_and_types(); + +private: + rosbag2_storage::StorageFactory factory_; + std::shared_ptr storage_; +}; + +} // namespace rosbag2 + +#endif // ROSBAG2__SEQUENTIAL_READER_HPP_ diff --git a/rosbag2/include/rosbag2/rosbag2_play_options.hpp b/rosbag2/include/rosbag2/storage_options.hpp similarity index 76% rename from rosbag2/include/rosbag2/rosbag2_play_options.hpp rename to rosbag2/include/rosbag2/storage_options.hpp index 346a77e809..dc8e37e3fd 100644 --- a/rosbag2/include/rosbag2/rosbag2_play_options.hpp +++ b/rosbag2/include/rosbag2/storage_options.hpp @@ -12,19 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__ROSBAG2_PLAY_OPTIONS_HPP_ -#define ROSBAG2__ROSBAG2_PLAY_OPTIONS_HPP_ +#ifndef ROSBAG2__STORAGE_OPTIONS_HPP_ +#define ROSBAG2__STORAGE_OPTIONS_HPP_ -#include +#include namespace rosbag2 { -struct Rosbag2PlayOptions + +struct StorageOptions { public: - size_t read_ahead_queue_size; + std::string uri; + std::string storage_id; }; } // namespace rosbag2 -#endif // ROSBAG2__ROSBAG2_PLAY_OPTIONS_HPP_ +#endif // ROSBAG2__STORAGE_OPTIONS_HPP_ diff --git a/rosbag2/include/rosbag2/types.hpp b/rosbag2/include/rosbag2/types.hpp new file mode 100644 index 0000000000..71371b0f9b --- /dev/null +++ b/rosbag2/include/rosbag2/types.hpp @@ -0,0 +1,27 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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__TYPES_HPP_ +#define ROSBAG2__TYPES_HPP_ + +#include "rosbag2_storage/serialized_bag_message.hpp" +#include "rosbag2_storage/topic_with_type.hpp" + +namespace rosbag2 +{ +using SerializedBagMessage = rosbag2_storage::SerializedBagMessage; +using TopicWithType = rosbag2_storage::TopicWithType; +} // namespace rosbag2 + +#endif // ROSBAG2__TYPES_HPP_ diff --git a/rosbag2/src/rosbag2/typesupport_helpers.hpp b/rosbag2/include/rosbag2/typesupport_helpers.hpp similarity index 89% rename from rosbag2/src/rosbag2/typesupport_helpers.hpp rename to rosbag2/include/rosbag2/typesupport_helpers.hpp index 6edc8cb7c5..883ba04215 100644 --- a/rosbag2/src/rosbag2/typesupport_helpers.hpp +++ b/rosbag2/include/rosbag2/typesupport_helpers.hpp @@ -19,17 +19,17 @@ #include #include "rosidl_generator_cpp/message_type_support_decl.hpp" +#include "rosbag2/visibility_control.hpp" namespace rosbag2 { -std::string get_typesupport_library_path( - const std::string & package_name, const std::string & typesupport_identifier); +ROSBAG2_PUBLIC +const rosidl_message_type_support_t * get_typesupport(const std::string & type); +ROSBAG2_PUBLIC const std::pair extract_type_and_package(const std::string & full_type); -const rosidl_message_type_support_t * get_typesupport(const std::string & type); - } // namespace rosbag2 #endif // ROSBAG2__TYPESUPPORT_HELPERS_HPP_ diff --git a/rosbag2/include/rosbag2/writer.hpp b/rosbag2/include/rosbag2/writer.hpp new file mode 100644 index 0000000000..8f7e9f1c41 --- /dev/null +++ b/rosbag2/include/rosbag2/writer.hpp @@ -0,0 +1,79 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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__WRITER_HPP_ +#define ROSBAG2__WRITER_HPP_ + +#include +#include + +#include "rosbag2_storage/storage_factory.hpp" +#include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" +#include "rosbag2/storage_options.hpp" +#include "rosbag2/types.hpp" +#include "rosbag2/visibility_control.hpp" + +// This is necessary because of using stl types here. It is completely safe, because +// a) the member is not accessible from the outside +// b) there are no inline functions. +#ifdef _WIN32 +# pragma warning(push) +# pragma warning(disable:4251) +#endif + +namespace rosbag2 +{ + +/** + * The Writer allows writing messages to a new bag. For every topic, information about its type + * needs to be added before writing the first message. + */ +class ROSBAG2_PUBLIC Writer +{ +public: + virtual ~Writer(); + + /** + * Opens a new bagfile and prepare it for writing messages. The bagfile must not exist. + * This must be called before any other function is used. + * + * \param options Options to configure the storage + */ + virtual void open(const StorageOptions & options); + + /** + * Create a new topic in the underlying storage. Needs to be called for every topic used within + * a message which is passed to write(...). + * + * \param topic_with_type name and type identifier of topic to be created + * \throws runtime_error if the Writer is not open. + */ + virtual void create_topic(const TopicWithType & topic_with_type); + + /** + * Write a message to a bagfile. The topic needs to have been created before writing is possible. + * + * \param message to be written to the bagfile + * \throws runtime_error if the Writer is not open. + */ + virtual void write(std::shared_ptr message); + +private: + rosbag2_storage::StorageFactory factory_; + std::shared_ptr storage_; +}; + +} // namespace rosbag2 + +#endif // ROSBAG2__WRITER_HPP_ diff --git a/rosbag2/package.xml b/rosbag2/package.xml index 0a87d5be67..0c591259c2 100644 --- a/rosbag2/package.xml +++ b/rosbag2/package.xml @@ -11,8 +11,6 @@ ament_index_cpp poco_vendor - rcl - rclcpp rcutils rosbag2_storage rosidl_generator_cpp @@ -21,8 +19,6 @@ ament_cmake_gmock ament_lint_auto ament_lint_common - ament_index_cpp - rosbag2_storage_default_plugins test_msgs diff --git a/rosbag2/src/rosbag2/demo_record.cpp b/rosbag2/src/rosbag2/demo_record.cpp deleted file mode 100644 index f95529fcfb..0000000000 --- a/rosbag2/src/rosbag2/demo_record.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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 -#include -#include - -#include "rclcpp/rclcpp.hpp" - -#include "rosbag2/rosbag2.hpp" - -int main(int argc, const char ** argv) -{ - if (argc < 2) { - std::cerr << "\nThe names of topics or `--all` must be given to record as parameter!\n"; - return 0; - } - std::vector topics; - - if (strcmp(argv[1], "--all") != 0) { - for (int i = 1; i < argc; i++) { - topics.emplace_back(argv[i]); - } - } - - // TODO(anhosi): allow output file to be specified by cli argument and do proper checking if - // file already exists - std::string filename("test.bag"); - std::remove(filename.c_str()); - - rclcpp::init(argc, argv); - - rosbag2::Rosbag2 rosbag2; - if (topics.empty()) { - rosbag2.record(filename); - } else { - rosbag2.record(filename, topics); - } - - rclcpp::shutdown(); - - return 0; -} diff --git a/rosbag2/src/rosbag2/rosbag2.cpp b/rosbag2/src/rosbag2/rosbag2.cpp deleted file mode 100644 index 8f80a2d429..0000000000 --- a/rosbag2/src/rosbag2/rosbag2.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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/rosbag2.hpp" - -#include -#include -#include -#include -#include -#include - -#include "rcl/graph.h" -#include "rclcpp/rclcpp.hpp" -#include "rcutils/time.h" - -#include "rosbag2/logging.hpp" -#include "rosbag2_storage/serialized_bag_message.hpp" -#include "rosbag2_storage/storage_factory.hpp" -#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" -#include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" - -#include "generic_subscription.hpp" -#include "rosbag2_node.hpp" -#include "player.hpp" -#include "replayable_message.hpp" -#include "typesupport_helpers.hpp" - -namespace rosbag2 -{ -Rosbag2::Rosbag2() -: node_(std::make_shared("rosbag2")) -{} - -void Rosbag2::record(const std::string & file_name, const std::vector & topic_names) -{ - auto storage = factory_.open_read_write(file_name, "sqlite3"); - - if (!storage) { - throw std::runtime_error("No storage could be initialized. Abort"); - } - - auto topics_and_types = node_->get_topics_with_types(topic_names); - if (topics_and_types.empty()) { - throw std::runtime_error("No topics found. Abort"); - } - - for (const auto & topic_and_type : topics_and_types) { - auto topic_name = topic_and_type.first; - auto topic_type = topic_and_type.second; - - std::shared_ptr subscription = create_subscription( - storage, topic_name, topic_type); - - if (subscription) { - subscriptions_.push_back(subscription); - - storage->create_topic(topic_name, topic_type); - } - } - - if (subscriptions_.empty()) { - throw std::runtime_error("No topics could be subscribed. Abort"); - } - - ROSBAG2_LOG_INFO("Waiting for messages..."); - while (rclcpp::ok()) { - rclcpp::spin(node_); - } - subscriptions_.clear(); -} - - -void Rosbag2::record(const std::string & file_name) -{ - auto topics_and_types = node_->get_all_topics_with_types(); - std::vector topic_names; - topic_names.reserve(topics_and_types.size()); - for (const auto & topic_and_type : topics_and_types) { - topic_names.push_back(topic_and_type.first); - } - - if (topic_names.empty()) { - ROSBAG2_LOG_ERROR("no topics found to record"); - return; - } - - record(file_name, topic_names); -} - -std::shared_ptr -Rosbag2::create_subscription( - std::shared_ptr storage, - const std::string & topic_name, const std::string & topic_type) const -{ - auto subscription = node_->create_generic_subscription( - topic_name, - topic_type, - [storage, topic_name](std::shared_ptr message) { - auto bag_message = std::make_shared(); - bag_message->serialized_data = message; - bag_message->topic_name = topic_name; - rcutils_time_point_value_t time_stamp; - int error = rcutils_system_time_now(&time_stamp); - if (error != RCUTILS_RET_OK) { - ROSBAG2_LOG_ERROR_STREAM( - "Error getting current time. Error:" << rcutils_get_error_string_safe()); - } - bag_message->time_stamp = time_stamp; - - storage->write(bag_message); - }); - return subscription; -} - -void Rosbag2::play(const std::string & file_name, const Rosbag2PlayOptions & options) -{ - auto storage = factory_.open_read_only(file_name, "sqlite3"); - if (!storage) { - throw std::runtime_error("Could not open storage: " + file_name); - } - - Player player(storage); - player.play(options); -} - -} // namespace rosbag2 diff --git a/rosbag2/src/rosbag2/sequential_reader.cpp b/rosbag2/src/rosbag2/sequential_reader.cpp new file mode 100644 index 0000000000..2d73ce1efc --- /dev/null +++ b/rosbag2/src/rosbag2/sequential_reader.cpp @@ -0,0 +1,61 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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/sequential_reader.hpp" + +#include +#include +#include + +namespace rosbag2 +{ + +SequentialReader::~SequentialReader() +{ + storage_.reset(); // Necessary to ensure that the writer is destroyed before the factory +} + +void SequentialReader::open(const StorageOptions & options) +{ + storage_ = factory_.open_read_only(options.uri, options.storage_id); + if (!storage_) { + throw std::runtime_error("No storage could be initialized. Abort"); + } +} + +bool SequentialReader::has_next() +{ + if (storage_) { + return storage_->has_next(); + } + throw std::runtime_error("Bag is not open. Call open() before reading."); +} + +std::shared_ptr SequentialReader::read_next() +{ + if (storage_) { + return storage_->read_next(); + } + throw std::runtime_error("Bag is not open. Call open() before reading."); +} + +std::vector SequentialReader::get_all_topics_and_types() +{ + if (storage_) { + return storage_->get_all_topics_and_types(); + } + throw std::runtime_error("Bag is not open. Call open() before reading."); +} + +} // namespace rosbag2 diff --git a/rosbag2/src/rosbag2/typesupport_helpers.cpp b/rosbag2/src/rosbag2/typesupport_helpers.cpp index cb62712dc6..00f3f5cb69 100644 --- a/rosbag2/src/rosbag2/typesupport_helpers.cpp +++ b/rosbag2/src/rosbag2/typesupport_helpers.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "typesupport_helpers.hpp" +#include "rosbag2/typesupport_helpers.hpp" #include #include diff --git a/rosbag2/src/rosbag2/writer.cpp b/rosbag2/src/rosbag2/writer.cpp new file mode 100644 index 0000000000..b5af1c1f62 --- /dev/null +++ b/rosbag2/src/rosbag2/writer.cpp @@ -0,0 +1,56 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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/writer.hpp" + +#include +#include + +#include "rosbag2/storage_options.hpp" + +namespace rosbag2 +{ + +Writer::~Writer() +{ + storage_.reset(); // Necessary to ensure that the writer is destroyed before the factory +} + +void Writer::open(const StorageOptions & options) +{ + storage_ = factory_.open_read_write(options.uri, options.storage_id); + if (!storage_) { + throw std::runtime_error("No storage could be initialized. Abort"); + } +} + +void Writer::create_topic(const TopicWithType & topic_with_type) +{ + if (!storage_) { + throw std::runtime_error("Bag is not open. Call open() before writing."); + } + + storage_->create_topic(topic_with_type); +} + +void Writer::write(std::shared_ptr message) +{ + if (!storage_) { + throw std::runtime_error("Bag is not open. Call open() before writing."); + } + + storage_->write(message); +} + +} // namespace rosbag2 diff --git a/rosbag2/test/rosbag2/rosbag2_play_integration_test.cpp b/rosbag2/test/rosbag2/rosbag2_play_integration_test.cpp deleted file mode 100644 index 075efafce1..0000000000 --- a/rosbag2/test/rosbag2/rosbag2_play_integration_test.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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 - -#include -#include -#include -#include -#include -#include - -#include "rclcpp/rclcpp.hpp" -#include "rosbag2/rosbag2.hpp" -#include "test_msgs/msg/primitives.hpp" -#include "test_msgs/msg/static_array_primitives.hpp" -#include "test_msgs/message_fixtures.hpp" - -#include "rosbag2_test_fixture.hpp" - -using namespace ::testing; // NOLINT -using namespace rosbag2; // NOLINT -using namespace std::chrono_literals; // NOLINT - -// TODO(Martin-Idel-SI): merge w. rosbag2_write_integration_test once signal handling is sorted out -class RosBag2IntegrationTestFixture : public Rosbag2TestFixture -{ -public: - RosBag2IntegrationTestFixture() - : Rosbag2TestFixture(), messages_stored_counter_(0) - { - rclcpp::init(0, nullptr); - } - - ~RosBag2IntegrationTestFixture() override - { - rclcpp::shutdown(); - } - - template - auto prepare_subscriber(size_t expected_messages_number, const std::string & topic) - { - auto node = rclcpp::Node::make_shared("node_" + topic); - auto messages = std::make_shared>(); - auto messages_received = std::make_shared(0); - rmw_qos_profile_t qos_profile; - qos_profile.history = RMW_QOS_POLICY_HISTORY_KEEP_ALL; - qos_profile.depth = 3; - qos_profile.reliability = RMW_QOS_POLICY_RELIABILITY_SYSTEM_DEFAULT; - qos_profile.durability = RMW_QOS_POLICY_DURABILITY_SYSTEM_DEFAULT; - qos_profile.avoid_ros_namespace_conventions = false; - - auto subscription = node->create_subscription(topic, - [messages, messages_received](typename T::ConstSharedPtr message) { - messages->push_back(message); - ++*messages_received; - }, qos_profile); - subscriptions_.push_back(subscription); - - return [messages, messages_received, node, expected_messages_number]() { - while (*messages_received < expected_messages_number) { - rclcpp::spin_some(node); - } - return *messages; - }; - } - - template - auto launch_subscriber(size_t expected_messages_number, const std::string & topic) - { - auto spin_subscriber = prepare_subscriber(expected_messages_number, topic); - return std::async(std::launch::async, spin_subscriber); - } - - void wait_for_subscribers(size_t count) - { - std::async(std::launch::async, [this, count] { - while (subscriptions_.size() < count) { - std::this_thread::sleep_for(50ms); - } - }).get(); - } - - std::atomic messages_stored_counter_; - std::vector subscriptions_; -}; - -TEST_F(RosBag2IntegrationTestFixture, recorded_messages_are_played_for_all_topics) -{ - auto primitive_message1 = get_messages_primitives()[0]; - primitive_message1->string_value = "Hello World 1"; - - auto primitive_message2 = get_messages_primitives()[1]; - primitive_message2->string_value = "Hello World 2"; - - auto complex_message1 = get_messages_static_array_primitives()[0]; - complex_message1->string_values = {{"Complex Hello1", "Complex Hello2", "Complex Hello3"}}; - complex_message1->bool_values = {{true, false, true}}; - - auto complex_message2 = get_messages_static_array_primitives()[0]; - complex_message2->string_values = {{"Complex Hello4", "Complex Hello5", "Complex Hello6"}}; - complex_message2->bool_values = {{false, false, true}}; - - auto topic_types = std::map{ - {"topic1", "test_msgs/Primitives"}, - {"topic2", "test_msgs/StaticArrayPrimitives"}, - }; - - std::vector> messages = - {serialize_test_message("topic1", primitive_message1), - serialize_test_message("topic1", primitive_message2), - serialize_test_message("topic1", primitive_message2), - serialize_test_message("topic2", complex_message1), - serialize_test_message("topic2", complex_message2), - serialize_test_message("topic2", complex_message2)}; - - write_messages(database_name_, messages, topic_types); - - // Due to a problem related to the subscriber, we play many (3) messages but make the subscriber - // node spin only until 2 have arrived. Hence the 2 as `launch_subscriber()` argument. - auto primitive_subscriber_future = launch_subscriber(2, "topic1"); - auto static_array_subscriber_future = - launch_subscriber(2, "topic2"); - wait_for_subscribers(2); - - Rosbag2 rosbag2; - rosbag2.play(database_name_, options_); - - auto replayed_test_primitives = primitive_subscriber_future.get(); - ASSERT_THAT(replayed_test_primitives, SizeIs(Ge(2u))); - EXPECT_THAT(replayed_test_primitives[0]->string_value, Eq("Hello World 1")); - EXPECT_THAT(replayed_test_primitives[1]->string_value, Eq("Hello World 2")); - auto replayed_test_arrays = static_array_subscriber_future.get(); - ASSERT_THAT(replayed_test_arrays, SizeIs(Ge(2u))); - EXPECT_THAT(replayed_test_arrays[0]->bool_values, ElementsAre(true, false, true)); - EXPECT_THAT(replayed_test_arrays[0]->string_values, - ElementsAre("Complex Hello1", "Complex Hello2", "Complex Hello3")); - EXPECT_THAT(replayed_test_arrays[1]->bool_values, ElementsAre(false, false, true)); - EXPECT_THAT(replayed_test_arrays[1]->string_values, - ElementsAre("Complex Hello4", "Complex Hello5", "Complex Hello6")); -} diff --git a/rosbag2/test/rosbag2/rosbag2_record_integration_fixture.hpp b/rosbag2/test/rosbag2/rosbag2_record_integration_fixture.hpp deleted file mode 100644 index 637c262d99..0000000000 --- a/rosbag2/test/rosbag2/rosbag2_record_integration_fixture.hpp +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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 - -#include -#include -#include -#include -#include -#include - -#include "rclcpp/rclcpp.hpp" -#include "rosbag2/rosbag2.hpp" -#include "rosbag2_storage/serialized_bag_message.hpp" -#include "rosbag2_storage_default_plugins/sqlite/sqlite_exception.hpp" -#include "rosbag2_storage_default_plugins/sqlite/sqlite_wrapper.hpp" - -#include "rosbag2_test_fixture.hpp" -#include "test_memory_management.hpp" - -using namespace ::testing; // NOLINT -using namespace rosbag2; // NOLINT -using namespace std::chrono_literals; // NOLINT - -#ifndef ROSBAG2__ROSBAG2_RECORD_INTEGRATION_FIXTURE_HPP_ -#define ROSBAG2__ROSBAG2_RECORD_INTEGRATION_FIXTURE_HPP_ - -class RosBag2RecordIntegrationTestFixture : public Rosbag2TestFixture -{ -public: - RosBag2RecordIntegrationTestFixture() - : Rosbag2TestFixture() - { - rosbag2_storage_plugins::SqliteWrapper - db(database_name_, rosbag2_storage::storage_interfaces::IOFlag::READ_WRITE); - - rclcpp::init(0, nullptr); - publisher_node_ = rclcpp::Node::make_shared("publisher_node"); - } - - void start_recording(std::vector topics) - { - // the future object returned from std::async needs to be stored not to block the execution - future_ = std::async( - std::launch::async, [this, topics]() { - rosbag2::Rosbag2 rosbag2; - rosbag2.record(database_name_, topics); - }); - } - - void start_recording_all_topics() - { - // the future object returned from std::async needs to be stored not to block the execution - future_ = std::async( - std::launch::async, [this]() { - rosbag2::Rosbag2 rosbag2; - std::this_thread::sleep_for(2s); - rosbag2.record(database_name_); - }); - } - - void stop_recording() - { - rclcpp::shutdown(); - future_.get(); - } - - void wait_for_publishers_to_stop() - { - for (auto & future : publisher_futures_) { - future.get(); - } - } - - template - void start_publishing( - std::shared_ptr message, - const std::string & topic_name, size_t number_expected_messages) - { - publisher_futures_.push_back(std::async( - std::launch::async, [this, message, topic_name, number_expected_messages]() { - auto publisher = publisher_node_->create_publisher(topic_name); - - rosbag2_storage_plugins::SqliteWrapper - db(database_name_, rosbag2_storage::storage_interfaces::IOFlag::READ_ONLY); - while (rclcpp::ok() && count_stored_messages(db, topic_name) < number_expected_messages) { - publisher->publish(message->serialized_data.get()); - // rate limiting - std::this_thread::sleep_for(50ms); - } - } - )); - } - - size_t - count_stored_messages(rosbag2_storage_plugins::SqliteWrapper & db, const std::string & topic_name) - { - // protect against concurrent writes (from recording) that may make the count query throw. - while (true) { - try { - return count_stored_messages_in_db(db, topic_name); - } catch (const rosbag2_storage_plugins::SqliteException & e) { - (void) e; - } - std::this_thread::sleep_for(50ms); - } - } - - size_t count_stored_messages_in_db( - rosbag2_storage_plugins::SqliteWrapper & db, const std::string & topic_name) - { - auto row = db.prepare_statement( - "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = 'topics';") - ->execute_query().get_single_line(); - if (std::get<0>(row) == 0) { - return 0; - } - auto message_count = db.prepare_statement( - "SELECT COUNT(*) " - "FROM messages LEFT JOIN topics ON messages.topic_id = topics.id " - "WHERE topics.name = ?;")->bind(topic_name)->execute_query().get_single_line(); - return static_cast(std::get<0>(message_count)); - } - - template - std::shared_ptr deserialize_message( - std::shared_ptr message) - { - return memory_.deserialize_message(message->serialized_data); - } - - template - std::vector> filter_messages( - std::vector> messages, - const std::string & topic) - { - std::vector> filtered_messages; - for (const auto & message : messages) { - if (message->topic_name == topic) { - filtered_messages.push_back(deserialize_message(message)); - } - } - return filtered_messages; - } - - rclcpp::Node::SharedPtr publisher_node_; - test_helpers::TestMemoryManagement memory_; - std::vector> publisher_futures_; - std::future future_; -}; - -#endif // ROSBAG2__ROSBAG2_RECORD_INTEGRATION_FIXTURE_HPP_ diff --git a/rosbag2/test/rosbag2/rosbag2_test_fixture.hpp b/rosbag2/test/rosbag2/rosbag2_test_fixture.hpp deleted file mode 100644 index 5593ca015a..0000000000 --- a/rosbag2/test/rosbag2/rosbag2_test_fixture.hpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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__ROSBAG2_TEST_FIXTURE_HPP_ -#define ROSBAG2__ROSBAG2_TEST_FIXTURE_HPP_ - -#include - -#include -#include -#include -#include -#include - -#include "rclcpp/rclcpp.hpp" - -#ifdef _WIN32 -# include -# include -#endif - -#include "rosbag2/rosbag2_play_options.hpp" -#include "rosbag2_storage/storage_factory.hpp" -#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" -#include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" -#include "test_memory_management.hpp" - -using namespace ::testing; // NOLINT - -class Rosbag2TestFixture : public Test -{ -public: - Rosbag2TestFixture() - : database_name_(std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".db3") - { - std::string system_separator = "/"; -#ifdef _WIN32 - system_separator = "\\"; -#endif - database_name_ = temporary_dir_path_ + system_separator + database_name_; - std::cout << "Database name: " << database_name_ << std::endl; - - options_ = rosbag2::Rosbag2PlayOptions(); - options_.read_ahead_queue_size = 1000; - } - - ~Rosbag2TestFixture() override - { -#ifdef _WIN32 - DeleteFileA(database_name_.c_str()); -#else - // TODO(botteroa-si): once filesystem::remove_all() can be used, this line can be removed and - // the ful directory can be deleted in remove_temporary_dir() - remove(database_name_.c_str()); -#endif - } - - static void SetUpTestCase() - { - char template_char[] = "tmp_test_dir.XXXXXX"; -#ifdef _WIN32 - char temp_path[255]; - GetTempPathA(255, temp_path); - _mktemp_s(template_char, strnlen(template_char, 20) + 1); - temporary_dir_path_ = std::string(temp_path) + std::string(template_char); - _mkdir(temporary_dir_path_.c_str()); -#else - char * dir_name = mkdtemp(template_char); - temporary_dir_path_ = dir_name; -#endif - } - - static void TearDownTestCase() - { - remove_temporary_dir(); - } - - static void remove_temporary_dir() - { -#ifdef _WIN32 - // TODO(botteroa-si): find a way to delete a not empty directory in Windows, so that we don't - // need the Windows line in the fixture destructor anymore. - RemoveDirectoryA(temporary_dir_path_.c_str()); -#else - remove(temporary_dir_path_.c_str()); -#endif - } - - std::vector> - get_messages(const std::string & db_name) - { - std::vector> table_msgs; - rosbag2_storage::StorageFactory factory; - auto storage = factory.open_read_only(db_name, "sqlite3"); - if (storage == nullptr) { - throw std::runtime_error("failed to open sqlite3 storage"); - } - - while (storage->has_next()) { - table_msgs.push_back(storage->read_next()); - } - - return table_msgs; - } - - void write_messages( - const std::string & db_name, - const std::vector> & messages, - const std::map & topics_and_types) - { - rosbag2_storage::StorageFactory factory; - auto storage = factory.open_read_write(db_name, "sqlite3"); - for (const auto & topic_and_type : topics_and_types) { - storage->create_topic(topic_and_type.first, topic_and_type.second); - } - - for (const auto & msg : messages) { - storage->write(msg); - } - } - - template - std::shared_ptr serialize_message( - const std::string & topic, typename MessageT::_data_type message) - { - auto msg = std::make_shared(); - msg->data = message; - auto bag_msg = std::make_shared(); - bag_msg->serialized_data = memory_management_.serialize_message(msg); - bag_msg->topic_name = topic; - - return bag_msg; - } - - template - std::shared_ptr serialize_test_message( - const std::string & topic, std::shared_ptr message) - { - auto bag_msg = std::make_shared(); - bag_msg->serialized_data = memory_management_.serialize_message(message); - bag_msg->topic_name = topic; - - return bag_msg; - } - - std::string database_name_; - static std::string temporary_dir_path_; - test_helpers::TestMemoryManagement memory_management_; - rosbag2::Rosbag2PlayOptions options_; -}; - -std::string Rosbag2TestFixture::temporary_dir_path_ = ""; // NOLINT - -#endif // ROSBAG2__ROSBAG2_TEST_FIXTURE_HPP_ diff --git a/rosbag2/test/rosbag2/rosbag2_typesupport_helpers_test.cpp b/rosbag2/test/rosbag2/rosbag2_typesupport_helpers_test.cpp index 6474ef50ae..a63ffe41a7 100644 --- a/rosbag2/test/rosbag2/rosbag2_typesupport_helpers_test.cpp +++ b/rosbag2/test/rosbag2/rosbag2_typesupport_helpers_test.cpp @@ -19,7 +19,7 @@ #include #include "ament_index_cpp/get_package_prefix.hpp" -#include "../../src/rosbag2/typesupport_helpers.hpp" +#include "rosbag2/typesupport_helpers.hpp" using namespace ::testing; // NOLINT diff --git a/rosbag2/test/rosbag2/test_memory_management.cpp b/rosbag2/test/rosbag2/test_memory_management.cpp deleted file mode 100644 index 985fe69e16..0000000000 --- a/rosbag2/test/rosbag2/test_memory_management.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018, Bosch Software Innovations GmbH. -// -// 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 "test_memory_management.hpp" - -#include -#include - -namespace test_helpers -{ - -TestMemoryManagement::TestMemoryManagement() -{ - rcutils_allocator_ = rcutils_get_default_allocator(); -} - -std::shared_ptr -TestMemoryManagement::get_initialized_serialized_message(size_t capacity) -{ - auto msg = new rmw_serialized_message_t; - *msg = rcutils_get_zero_initialized_char_array(); - auto ret = rcutils_char_array_init(msg, capacity, &rcutils_allocator_); - if (ret != RCUTILS_RET_OK) { - throw std::runtime_error("Error allocating resources for serialized message: " + - std::string(rcutils_get_error_string_safe())); - } - - auto serialized_message = std::shared_ptr(msg, - [](rmw_serialized_message_t * msg) { - int error = rcutils_char_array_fini(msg); - delete msg; - if (error != RCUTILS_RET_OK) { - RCUTILS_LOG_ERROR_NAMED( - "rosbag2_storage", - "Leaking memory. Error: %s", rcutils_get_error_string_safe()); - } - }); - return serialized_message; -} - - -} // namespace test_helpers diff --git a/rosbag2_storage/CMakeLists.txt b/rosbag2_storage/CMakeLists.txt index 9c9dd7a7e4..141f90e4ce 100644 --- a/rosbag2_storage/CMakeLists.txt +++ b/rosbag2_storage/CMakeLists.txt @@ -23,7 +23,7 @@ add_library( rosbag2_storage SHARED src/rosbag2_storage/ros_helper.cpp - src/storage_factory.cpp) + src/rosbag2_storage/storage_factory.cpp) target_include_directories(rosbag2_storage PUBLIC include) ament_target_dependencies( rosbag2_storage @@ -51,6 +51,9 @@ install( DIRECTORY cmake DESTINATION share/${PROJECT_NAME}) +ament_export_include_directories(include) +ament_export_libraries(${PROJECT_NAME}) + if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) find_package(ament_cmake_gmock REQUIRED) @@ -60,32 +63,30 @@ if(BUILD_TESTING) add_library( test_plugin SHARED - test/test_plugin.cpp - test/test_read_only_plugin.cpp) + test/rosbag2_storage/test_plugin.cpp + test/rosbag2_storage/test_read_only_plugin.cpp) target_link_libraries(test_plugin rosbag2_storage) install( TARGETS test_plugin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) - pluginlib_export_plugin_description_file(rosbag2_storage test/test_plugin.xml) + pluginlib_export_plugin_description_file(rosbag2_storage test/rosbag2_storage/test_plugin.xml) ament_add_gmock(test_storage_factory - test/test_storage_factory.cpp) + test/rosbag2_storage/test_storage_factory.cpp) if(TARGET test_storage_factory) target_include_directories(test_storage_factory PRIVATE include) target_link_libraries(test_storage_factory rosbag2_storage) endif() ament_add_gmock(test_ros_helper - test/test_ros_helper.cpp) + test/rosbag2_storage/test_ros_helper.cpp) if(TARGET test_ros_helper) target_include_directories(test_ros_helper PRIVATE include) target_link_libraries(test_ros_helper rosbag2_storage) endif() endif() -ament_export_include_directories(include) -ament_export_libraries(rosbag2_storage) ament_package( CONFIG_EXTRAS cmake/rosbag2_storage_register_storage_plugin.cmake) diff --git a/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_read_interface.hpp b/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_read_interface.hpp index 0651502af4..0348fd898f 100644 --- a/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_read_interface.hpp +++ b/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_read_interface.hpp @@ -15,11 +15,12 @@ #ifndef ROSBAG2_STORAGE__STORAGE_INTERFACES__BASE_READ_INTERFACE_HPP_ #define ROSBAG2_STORAGE__STORAGE_INTERFACES__BASE_READ_INTERFACE_HPP_ -#include #include #include +#include #include "rosbag2_storage/serialized_bag_message.hpp" +#include "rosbag2_storage/topic_with_type.hpp" #include "rosbag2_storage/visibility_control.hpp" namespace rosbag2_storage @@ -36,7 +37,7 @@ class ROSBAG2_STORAGE_PUBLIC BaseReadInterface virtual std::shared_ptr read_next() = 0; - virtual std::map get_all_topics_and_types() = 0; + virtual std::vector get_all_topics_and_types() = 0; }; } // namespace storage_interfaces diff --git a/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_write_interface.hpp b/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_write_interface.hpp index 2f14f0b4c5..e6125ff71f 100644 --- a/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_write_interface.hpp +++ b/rosbag2_storage/include/rosbag2_storage/storage_interfaces/base_write_interface.hpp @@ -20,6 +20,7 @@ #include "rosbag2_storage/serialized_bag_message.hpp" #include "rosbag2_storage/bag_info.hpp" +#include "rosbag2_storage/topic_with_type.hpp" #include "rosbag2_storage/visibility_control.hpp" namespace rosbag2_storage @@ -34,7 +35,7 @@ class ROSBAG2_STORAGE_PUBLIC BaseWriteInterface virtual void write(std::shared_ptr msg) = 0; - virtual void create_topic(const std::string & name, const std::string & type) = 0; + virtual void create_topic(const TopicWithType & topic) = 0; }; } // namespace storage_interfaces diff --git a/rosbag2/src/rosbag2/demo_play.cpp b/rosbag2_storage/include/rosbag2_storage/topic_with_type.hpp similarity index 63% rename from rosbag2/src/rosbag2/demo_play.cpp rename to rosbag2_storage/include/rosbag2_storage/topic_with_type.hpp index 57c8797894..39612c4118 100644 --- a/rosbag2/src/rosbag2/demo_play.cpp +++ b/rosbag2_storage/include/rosbag2_storage/topic_with_type.hpp @@ -12,23 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#ifndef ROSBAG2_STORAGE__TOPIC_WITH_TYPE_HPP_ +#define ROSBAG2_STORAGE__TOPIC_WITH_TYPE_HPP_ -#include "rclcpp/rclcpp.hpp" -#include "rosbag2/rosbag2_play_options.hpp" -#include "rosbag2/rosbag2.hpp" +#include -int main(int argc, const char ** argv) +namespace rosbag2_storage { - rclcpp::init(argc, argv); - - auto options = rosbag2::Rosbag2PlayOptions(); - options.read_ahead_queue_size = 1000; - rosbag2::Rosbag2 rosbag2; - rosbag2.play("test.bag", options); - - rclcpp::shutdown(); +struct TopicWithType +{ + std::string name; + std::string type; +}; - return 0; } + +#endif // ROSBAG2_STORAGE__TOPIC_WITH_TYPE_HPP_ diff --git a/rosbag2_storage/src/impl/storage_factory_impl.hpp b/rosbag2_storage/src/rosbag2_storage/impl/storage_factory_impl.hpp similarity index 96% rename from rosbag2_storage/src/impl/storage_factory_impl.hpp rename to rosbag2_storage/src/rosbag2_storage/impl/storage_factory_impl.hpp index afaf1d4eeb..33702a10d7 100644 --- a/rosbag2_storage/src/impl/storage_factory_impl.hpp +++ b/rosbag2_storage/src/rosbag2_storage/impl/storage_factory_impl.hpp @@ -13,8 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef IMPL__STORAGE_FACTORY_IMPL_HPP_ -#define IMPL__STORAGE_FACTORY_IMPL_HPP_ +#ifndef ROSBAG2_STORAGE__IMPL__STORAGE_FACTORY_IMPL_HPP_ +#define ROSBAG2_STORAGE__IMPL__STORAGE_FACTORY_IMPL_HPP_ #include #include @@ -142,4 +142,4 @@ class StorageFactoryImpl } // namespace rosbag2_storage -#endif // IMPL__STORAGE_FACTORY_IMPL_HPP_ +#endif // ROSBAG2_STORAGE__IMPL__STORAGE_FACTORY_IMPL_HPP_ diff --git a/rosbag2_storage/src/storage_factory.cpp b/rosbag2_storage/src/rosbag2_storage/storage_factory.cpp similarity index 100% rename from rosbag2_storage/src/storage_factory.cpp rename to rosbag2_storage/src/rosbag2_storage/storage_factory.cpp diff --git a/rosbag2_storage/test/test_plugin.cpp b/rosbag2_storage/test/rosbag2_storage/test_plugin.cpp similarity index 85% rename from rosbag2_storage/test/test_plugin.cpp rename to rosbag2_storage/test/rosbag2_storage/test_plugin.cpp index 423b40450d..f21d155e45 100644 --- a/rosbag2_storage/test/test_plugin.cpp +++ b/rosbag2_storage/test/rosbag2_storage/test_plugin.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "pluginlib/class_list_macros.hpp" @@ -57,9 +58,9 @@ std::shared_ptr TestPlugin::read_next() return std::shared_ptr(); } -void TestPlugin::create_topic(const std::string & name, const std::string & type) +void TestPlugin::create_topic(const rosbag2_storage::TopicWithType & topic) { - std::cout << "Created topic with name =" << name << " and type =" << type << ".\n"; + std::cout << "Created topic with name =" << topic.name << " and type =" << topic.type << ".\n"; } void TestPlugin::write(const std::shared_ptr msg) @@ -68,10 +69,10 @@ void TestPlugin::write(const std::shared_ptr TestPlugin::get_all_topics_and_types() +std::vector TestPlugin::get_all_topics_and_types() { std::cout << "\nreading topics and types\n"; - return std::map(); + return std::vector(); } PLUGINLIB_EXPORT_CLASS(TestPlugin, rosbag2_storage::storage_interfaces::ReadWriteInterface) diff --git a/rosbag2_storage/test/test_plugin.hpp b/rosbag2_storage/test/rosbag2_storage/test_plugin.hpp similarity index 79% rename from rosbag2_storage/test/test_plugin.hpp rename to rosbag2_storage/test/rosbag2_storage/test_plugin.hpp index 048226bc23..dbc1bc5d32 100644 --- a/rosbag2_storage/test/test_plugin.hpp +++ b/rosbag2_storage/test/rosbag2_storage/test_plugin.hpp @@ -13,15 +13,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef TEST_PLUGIN_HPP_ -#define TEST_PLUGIN_HPP_ +#ifndef ROSBAG2_STORAGE__TEST_PLUGIN_HPP_ +#define ROSBAG2_STORAGE__TEST_PLUGIN_HPP_ #include #include #include +#include #include "rosbag2_storage/bag_info.hpp" #include "rosbag2_storage/serialized_bag_message.hpp" +#include "rosbag2_storage/topic_with_type.hpp" #include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" class TestPlugin : public rosbag2_storage::storage_interfaces::ReadWriteInterface @@ -33,7 +35,7 @@ class TestPlugin : public rosbag2_storage::storage_interfaces::ReadWriteInterfac rosbag2_storage::BagInfo info() override; - void create_topic(const std::string & name, const std::string & type) override; + void create_topic(const rosbag2_storage::TopicWithType & topic) override; bool has_next() override; @@ -41,7 +43,7 @@ class TestPlugin : public rosbag2_storage::storage_interfaces::ReadWriteInterfac void write(std::shared_ptr msg) override; - std::map get_all_topics_and_types() override; + std::vector get_all_topics_and_types() override; }; -#endif // TEST_PLUGIN_HPP_ +#endif // ROSBAG2_STORAGE__TEST_PLUGIN_HPP_ diff --git a/rosbag2_storage/test/test_plugin.xml b/rosbag2_storage/test/rosbag2_storage/test_plugin.xml similarity index 100% rename from rosbag2_storage/test/test_plugin.xml rename to rosbag2_storage/test/rosbag2_storage/test_plugin.xml diff --git a/rosbag2_storage/test/test_read_only_plugin.cpp b/rosbag2_storage/test/rosbag2_storage/test_read_only_plugin.cpp similarity index 91% rename from rosbag2_storage/test/test_read_only_plugin.cpp rename to rosbag2_storage/test/rosbag2_storage/test_read_only_plugin.cpp index 04b93c7501..a8f818abaa 100644 --- a/rosbag2_storage/test/test_read_only_plugin.cpp +++ b/rosbag2_storage/test/rosbag2_storage/test_read_only_plugin.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "pluginlib/class_list_macros.hpp" @@ -53,10 +54,10 @@ std::shared_ptr TestReadOnlyPlugin::read_ return std::shared_ptr(); } -std::map TestReadOnlyPlugin::get_all_topics_and_types() +std::vector TestReadOnlyPlugin::get_all_topics_and_types() { std::cout << "\nreading topics and types\n"; - return std::map(); + return std::vector(); } PLUGINLIB_EXPORT_CLASS(TestReadOnlyPlugin, rosbag2_storage::storage_interfaces::ReadOnlyInterface) diff --git a/rosbag2_storage/test/test_read_only_plugin.hpp b/rosbag2_storage/test/rosbag2_storage/test_read_only_plugin.hpp similarity index 81% rename from rosbag2_storage/test/test_read_only_plugin.hpp rename to rosbag2_storage/test/rosbag2_storage/test_read_only_plugin.hpp index 00c9d45e63..c0a5ab9588 100644 --- a/rosbag2_storage/test/test_read_only_plugin.hpp +++ b/rosbag2_storage/test/rosbag2_storage/test_read_only_plugin.hpp @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef TEST_READ_ONLY_PLUGIN_HPP_ -#define TEST_READ_ONLY_PLUGIN_HPP_ +#ifndef ROSBAG2_STORAGE__TEST_READ_ONLY_PLUGIN_HPP_ +#define ROSBAG2_STORAGE__TEST_READ_ONLY_PLUGIN_HPP_ #include #include #include +#include #include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" @@ -34,7 +35,7 @@ class TestReadOnlyPlugin : public rosbag2_storage::storage_interfaces::ReadOnlyI std::shared_ptr read_next() override; - std::map get_all_topics_and_types() override; + std::vector get_all_topics_and_types() override; }; -#endif // TEST_READ_ONLY_PLUGIN_HPP_ +#endif // ROSBAG2_STORAGE__TEST_READ_ONLY_PLUGIN_HPP_ diff --git a/rosbag2_storage/test/test_ros_helper.cpp b/rosbag2_storage/test/rosbag2_storage/test_ros_helper.cpp similarity index 100% rename from rosbag2_storage/test/test_ros_helper.cpp rename to rosbag2_storage/test/rosbag2_storage/test_ros_helper.cpp diff --git a/rosbag2_storage/test/test_storage_factory.cpp b/rosbag2_storage/test/rosbag2_storage/test_storage_factory.cpp similarity index 100% rename from rosbag2_storage/test/test_storage_factory.cpp rename to rosbag2_storage/test/rosbag2_storage/test_storage_factory.cpp diff --git a/rosbag2_storage_default_plugins/CMakeLists.txt b/rosbag2_storage_default_plugins/CMakeLists.txt index 7dcd3014f8..47a5f045dc 100644 --- a/rosbag2_storage_default_plugins/CMakeLists.txt +++ b/rosbag2_storage_default_plugins/CMakeLists.txt @@ -39,19 +39,13 @@ target_include_directories(${PROJECT_NAME} $ ) -ament_export_include_directories(include) -ament_export_libraries(${PROJECT_NAME}) -ament_export_dependencies( - rosbag2_storage - sqlite3_vendor - SQLite3 - rcutils) - # Causes the visibility macros to use dllexport rather than dllimport, # which is appropriate when building the dll but not consuming it. -target_compile_definitions(rosbag2_storage_default_plugins PRIVATE +target_compile_definitions(${PROJECT_NAME} PRIVATE ROSBAG2_STORAGE_DEFAULT_PLUGINS_BUILDING_DLL) +pluginlib_export_plugin_description_file(rosbag2_storage plugin_description.xml) + install( DIRECTORY include/ DESTINATION include) @@ -62,12 +56,14 @@ install( LIBRARY DESTINATION lib RUNTIME DESTINATION bin) -pluginlib_export_plugin_description_file(rosbag2_storage plugin_description.xml) +ament_export_include_directories(include) +ament_export_libraries(${PROJECT_NAME}) +ament_export_dependencies(rosbag2_storage rcutils sqlite3_vendor SQLite3) if(BUILD_TESTING) - find_package(ament_cmake_gtest REQUIRED) find_package(ament_cmake_gmock REQUIRED) find_package(ament_lint_auto REQUIRED) + find_package(rosbag2_test_common REQUIRED) ament_lint_auto_find_test_dependencies() set(TEST_LINK_LIBRARIES @@ -78,18 +74,20 @@ if(BUILD_TESTING) ${pluginlib_LIBRARIES} ) - ament_add_gmock(sqlite_wrapper_integration_test - test/rosbag2_storage_default_plugins/sqlite/sqlite_wrapper_integration_test.cpp + ament_add_gmock(test_sqlite_wrapper + test/rosbag2_storage_default_plugins/sqlite/test_sqlite_wrapper.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET sqlite_wrapper_integration_test) - target_link_libraries(sqlite_wrapper_integration_test ${TEST_LINK_LIBRARIES}) + if(TARGET test_sqlite_wrapper) + target_link_libraries(test_sqlite_wrapper ${TEST_LINK_LIBRARIES}) + ament_target_dependencies(test_sqlite_wrapper rosbag2_test_common) endif() - ament_add_gmock(sqlite_storage_integration_test - test/rosbag2_storage_default_plugins/sqlite/sqlite_storage_integration_test.cpp + ament_add_gmock(test_sqlite_storage + test/rosbag2_storage_default_plugins/sqlite/test_sqlite_storage.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(TARGET sqlite_storage_integration_test) - target_link_libraries(sqlite_storage_integration_test ${TEST_LINK_LIBRARIES}) + if(TARGET test_sqlite_storage) + target_link_libraries(test_sqlite_storage ${TEST_LINK_LIBRARIES}) + ament_target_dependencies(test_sqlite_storage rosbag2_test_common) endif() endif() diff --git a/rosbag2_storage_default_plugins/include/rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp b/rosbag2_storage_default_plugins/include/rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp index f91fa828ff..bb29e3f709 100644 --- a/rosbag2_storage_default_plugins/include/rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp +++ b/rosbag2_storage_default_plugins/include/rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp @@ -23,6 +23,7 @@ #include "rcutils/types.h" #include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" #include "rosbag2_storage/serialized_bag_message.hpp" +#include "rosbag2_storage/topic_with_type.hpp" #include "rosbag2_storage_default_plugins/sqlite/sqlite_wrapper.hpp" #include "rosbag2_storage_default_plugins/visibility_control.hpp" @@ -49,7 +50,7 @@ class ROSBAG2_STORAGE_DEFAULT_PLUGINS_PUBLIC SqliteStorage rosbag2_storage::storage_interfaces::IOFlag io_flag = rosbag2_storage::storage_interfaces::IOFlag::READ_WRITE) override; - void create_topic(const std::string & name, const std::string & type) override; + void create_topic(const rosbag2_storage::TopicWithType & topic) override; void write(std::shared_ptr message) override; @@ -57,7 +58,7 @@ class ROSBAG2_STORAGE_DEFAULT_PLUGINS_PUBLIC SqliteStorage std::shared_ptr read_next() override; - std::map get_all_topics_and_types() override; + std::vector get_all_topics_and_types() override; rosbag2_storage::BagInfo info() override; @@ -78,7 +79,7 @@ class ROSBAG2_STORAGE_DEFAULT_PLUGINS_PUBLIC SqliteStorage ReadQueryResult message_result_; ReadQueryResult::Iterator current_message_row_; std::map topics_; - std::map all_topics_and_types_; + std::vector all_topics_and_types_; }; } // namespace rosbag2_storage_plugins diff --git a/rosbag2_storage_default_plugins/package.xml b/rosbag2_storage_default_plugins/package.xml index b933b70b9c..d326074558 100644 --- a/rosbag2_storage_default_plugins/package.xml +++ b/rosbag2_storage_default_plugins/package.xml @@ -8,24 +8,15 @@ ament_cmake - sqlite3_vendor - rcutils - pluginlib - rosbag2_storage - - sqlite3_vendor - rosbag2_storage - rcutils - pluginlib + pluginlib + rcutils + rosbag2_storage + sqlite3_vendor ament_lint_auto ament_lint_common ament_cmake_gmock - ament_cmake_gtest - rcutils - pluginlib - sqlite3_vendor - rosbag2_storage + rosbag2_test_common ament_cmake diff --git a/rosbag2_storage_default_plugins/src/rosbag2_storage_default_plugins/sqlite/sqlite_storage.cpp b/rosbag2_storage_default_plugins/src/rosbag2_storage_default_plugins/sqlite/sqlite_storage.cpp index a2a6f6cc9d..3c2a2f960c 100644 --- a/rosbag2_storage_default_plugins/src/rosbag2_storage_default_plugins/sqlite/sqlite_storage.cpp +++ b/rosbag2_storage_default_plugins/src/rosbag2_storage_default_plugins/sqlite/sqlite_storage.cpp @@ -101,7 +101,7 @@ std::shared_ptr SqliteStorage::read_next( return bag_message; } -std::map SqliteStorage::get_all_topics_and_types() +std::vector SqliteStorage::get_all_topics_and_types() { if (all_topics_and_types_.empty()) { fill_topics_and_types(); @@ -130,14 +130,14 @@ rosbag2_storage::BagInfo SqliteStorage::info() return bag_info_; } -void SqliteStorage::create_topic(const std::string & name, const std::string & type) +void SqliteStorage::create_topic(const rosbag2_storage::TopicWithType & topic) { - if (topics_.find(name) == std::end(topics_)) { + if (topics_.find(topic.name) == std::end(topics_)) { auto insert_topic = database_->prepare_statement("INSERT INTO topics (name, type) VALUES (?, ?)"); - insert_topic->bind(name, type); + insert_topic->bind(topic.name, topic.type); insert_topic->execute_and_reset(); - topics_.emplace(name, static_cast(database_->get_last_insert_id())); + topics_.emplace(topic.name, static_cast(database_->get_last_insert_id())); } } @@ -164,7 +164,7 @@ void SqliteStorage::fill_topics_and_types() auto query_results = statement->execute_query(); for (auto result : query_results) { - all_topics_and_types_.insert(std::make_pair(std::get<0>(result), std::get<1>(result))); + all_topics_and_types_.push_back({std::get<0>(result), std::get<1>(result)}); } } diff --git a/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/storage_test_fixture.hpp b/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/storage_test_fixture.hpp index 41951cf111..0fc34c3169 100644 --- a/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/storage_test_fixture.hpp +++ b/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/storage_test_fixture.hpp @@ -25,18 +25,15 @@ #include #include -#ifdef _WIN32 -# include -# include -#endif - #include "rcutils/logging_macros.h" #include "rcutils/snprintf.h" #include "rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp" +#include "rosbag2_test_common/temporary_directory_fixture.hpp" using namespace ::testing; // NOLINT +using namespace rosbag2_test_common; // NOLINT -class StorageTestFixture : public Test +class StorageTestFixture : public TemporaryDirectoryFixture { public: StorageTestFixture() @@ -48,48 +45,7 @@ class StorageTestFixture : public Test #endif database_name_ = temporary_dir_path_ + system_separator + database_name_; std::cout << "Database name: " << database_name_ << std::endl; - } - - ~StorageTestFixture() override - { -#ifdef _WIN32 - DeleteFileA(database_name_.c_str()); -#else -// TODO(botteroa-si): once filesystem::remove_all() can be used, this line can be removed and -// the ful directory can be deleted in remove_temporary_dir() - remove(database_name_.c_str()); -#endif - } - - static void SetUpTestCase() - { - char template_char[] = "tmp_test_dir.XXXXXX"; -#ifdef _WIN32 - char temp_path[255]; - GetTempPathA(255, temp_path); - _mktemp_s(template_char, strnlen(template_char, 20) + 1); - temporary_dir_path_ = std::string(temp_path) + std::string(template_char); - _mkdir(temporary_dir_path_.c_str()); -#else - char * dir_name = mkdtemp(template_char); - temporary_dir_path_ = dir_name; -#endif - } - - static void TearDownTestCase() - { - remove_temporary_dir(); - } - - static void remove_temporary_dir() - { -#ifdef _WIN32 - // TODO(botteroa-si): find a way to delete a not empty directory in Windows, so that we don't - // need the Windows line in the fixture destructor anymore. - RemoveDirectoryA(temporary_dir_path_.c_str()); -#else - remove(temporary_dir_path_.c_str()); -#endif + allocator_ = rcutils_get_default_allocator(); } std::shared_ptr make_serialized_message(std::string message) @@ -98,10 +54,9 @@ class StorageTestFixture : public Test message_size++; // need to account for terminating null character assert(message_size > 0); - auto rcutils_allocator = rcutils_get_default_allocator(); auto msg = new rcutils_char_array_t; *msg = rcutils_get_zero_initialized_char_array(); - auto ret = rcutils_char_array_init(msg, message_size, &rcutils_allocator); + auto ret = rcutils_char_array_init(msg, message_size, &allocator_); if (ret != RCUTILS_RET_OK) { throw std::runtime_error("Error allocating resources " + std::to_string(ret)); } @@ -146,7 +101,7 @@ class StorageTestFixture : public Test for (auto msg : messages) { std::string topic_name = std::get<2>(msg); std::string type_name = std::get<3>(msg); - writable_storage->create_topic(topic_name, type_name); + writable_storage->create_topic({topic_name, type_name}); auto bag_message = std::make_shared(); bag_message->serialized_data = make_serialized_message(std::get<0>(msg)); bag_message->time_stamp = std::get<1>(msg); @@ -188,11 +143,10 @@ class StorageTestFixture : public Test message.c_str()); } + rcutils_allocator_t allocator_; + public: std::string database_name_; - static std::string temporary_dir_path_; }; -std::string StorageTestFixture::temporary_dir_path_ = ""; // NOLINT - #endif // ROSBAG2_STORAGE_DEFAULT_PLUGINS__SQLITE__STORAGE_TEST_FIXTURE_HPP_ diff --git a/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/sqlite_storage_integration_test.cpp b/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/test_sqlite_storage.cpp similarity index 91% rename from rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/sqlite_storage_integration_test.cpp rename to rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/test_sqlite_storage.cpp index cca70075ed..03793f8817 100644 --- a/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/sqlite_storage_integration_test.cpp +++ b/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/test_sqlite_storage.cpp @@ -79,12 +79,12 @@ TEST_F(StorageTestFixture, get_next_returns_messages_in_timestamp_order) { EXPECT_FALSE(readable_storage->has_next()); } -TEST_F(StorageTestFixture, get_all_topics_and_types_returns_the_correct_map) { +TEST_F(StorageTestFixture, get_all_topics_and_types_returns_the_correct_vector) { std::unique_ptr writable_storage = std::make_unique(); writable_storage->open(database_name_); - writable_storage->create_topic("topic1", "type1"); - writable_storage->create_topic("topic2", "type2"); + writable_storage->create_topic({"topic1", "type1"}); + writable_storage->create_topic({"topic2", "type2"}); writable_storage.reset(); auto readable_storage = std::make_unique(); @@ -92,6 +92,8 @@ TEST_F(StorageTestFixture, get_all_topics_and_types_returns_the_correct_map) { auto topics_and_types = readable_storage->get_all_topics_and_types(); EXPECT_THAT(topics_and_types, SizeIs(2)); - EXPECT_THAT(topics_and_types["topic1"], Eq("type1")); - EXPECT_THAT(topics_and_types["topic2"], Eq("type2")); + EXPECT_THAT(topics_and_types[0].name, Eq("topic1")); + EXPECT_THAT(topics_and_types[0].type, Eq("type1")); + EXPECT_THAT(topics_and_types[1].name, Eq("topic2")); + EXPECT_THAT(topics_and_types[1].type, Eq("type2")); } diff --git a/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/sqlite_wrapper_integration_test.cpp b/rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/test_sqlite_wrapper.cpp similarity index 100% rename from rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/sqlite_wrapper_integration_test.cpp rename to rosbag2_storage_default_plugins/test/rosbag2_storage_default_plugins/sqlite/test_sqlite_wrapper.cpp diff --git a/rosbag2_test_common/CMakeLists.txt b/rosbag2_test_common/CMakeLists.txt new file mode 100644 index 0000000000..190191c784 --- /dev/null +++ b/rosbag2_test_common/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.5) +project(rosbag2_test_common) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +find_package(ament_cmake REQUIRED) + +if(BUILD_TESTING) + find_package(ament_cmake_gmock REQUIRED) + find_package(ament_lint_auto REQUIRED) + find_package(rclcpp REQUIRED) + + ament_lint_auto_find_test_dependencies() + + add_library(${PROJECT_NAME} INTERFACE) + + target_include_directories(${PROJECT_NAME} INTERFACE + $ + $) + + target_link_libraries(${PROJECT_NAME} INTERFACE rclcpp rcutils) + + ament_export_dependencies(rclcpp) + ament_export_include_directories(include) + + install( + DIRECTORY include/ + DESTINATION include) +endif() + +ament_package() diff --git a/rosbag2/test/rosbag2/test_memory_management.hpp b/rosbag2_test_common/include/rosbag2_test_common/memory_management.hpp similarity index 55% rename from rosbag2/test/rosbag2/test_memory_management.hpp rename to rosbag2_test_common/include/rosbag2_test_common/memory_management.hpp index c55d681cd7..a9baf6cabe 100644 --- a/rosbag2/test/rosbag2/test_memory_management.hpp +++ b/rosbag2_test_common/include/rosbag2_test_common/memory_management.hpp @@ -12,30 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__TEST_MEMORY_MANAGEMENT_HPP_ -#define ROSBAG2__TEST_MEMORY_MANAGEMENT_HPP_ +#ifndef ROSBAG2_TEST_COMMON__MEMORY_MANAGEMENT_HPP_ +#define ROSBAG2_TEST_COMMON__MEMORY_MANAGEMENT_HPP_ #include +#include #include "rclcpp/rclcpp.hpp" -namespace test_helpers +namespace rosbag2_test_common { -class TestMemoryManagement +class MemoryManagement { public: - TestMemoryManagement(); - ~TestMemoryManagement() = default; - - std::shared_ptr get_initialized_serialized_message(size_t capacity); - - template - inline - const rosidl_message_type_support_t * get_message_typesupport(std::shared_ptr) + MemoryManagement() { - return rosidl_typesupport_cpp::get_message_type_support_handle(); + rcutils_allocator_ = rcutils_get_default_allocator(); } + ~MemoryManagement() = default; + template inline std::shared_ptr serialize_message(std::shared_ptr message) @@ -61,16 +57,46 @@ class TestMemoryManagement get_message_typesupport(message), message.get()); if (error != RCL_RET_OK) { - throw std::runtime_error("Failed to deserialize"); + RCUTILS_LOG_ERROR_NAMED("rosbag2_test_common", "Leaking memory. Error: %s", + rcutils_get_error_string_safe()); } return message; } private: + template + inline + const rosidl_message_type_support_t * get_message_typesupport(std::shared_ptr) + { + return rosidl_typesupport_cpp::get_message_type_support_handle(); + } + + std::shared_ptr + get_initialized_serialized_message(size_t capacity) + { + auto msg = new rmw_serialized_message_t; + *msg = rcutils_get_zero_initialized_char_array(); + auto ret = rcutils_char_array_init(msg, capacity, &rcutils_allocator_); + if (ret != RCUTILS_RET_OK) { + throw std::runtime_error("Error allocating resources for serialized message: " + + std::string(rcutils_get_error_string_safe())); + } + + auto serialized_message = std::shared_ptr(msg, + [](rmw_serialized_message_t * msg) { + int error = rcutils_char_array_fini(msg); + delete msg; + if (error != RCUTILS_RET_OK) { + RCUTILS_LOG_ERROR_NAMED("rosbag2_test_common", "Leaking memory. Error: %s", + rcutils_get_error_string_safe()); + } + }); + return serialized_message; + } + rcutils_allocator_t rcutils_allocator_; }; +} // namespace rosbag2_test_common -} // namespace test_helpers - -#endif // ROSBAG2__TEST_MEMORY_MANAGEMENT_HPP_ +#endif // ROSBAG2_TEST_COMMON__MEMORY_MANAGEMENT_HPP_ diff --git a/rosbag2_test_common/include/rosbag2_test_common/publisher_manager.hpp b/rosbag2_test_common/include/rosbag2_test_common/publisher_manager.hpp new file mode 100644 index 0000000000..9c0c41aadb --- /dev/null +++ b/rosbag2_test_common/include/rosbag2_test_common/publisher_manager.hpp @@ -0,0 +1,83 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TEST_COMMON__PUBLISHER_MANAGER_HPP_ +#define ROSBAG2_TEST_COMMON__PUBLISHER_MANAGER_HPP_ + +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" // rclcpp must be included before the Windows specific includes. + +using namespace std::chrono_literals; // NOLINT + +namespace rosbag2_test_common +{ + +using CountFunction = std::function; + +class PublisherManager +{ +public: + template + void add_publisher( + const std::string & topic_name, std::shared_ptr message, size_t expected_messages = 0) + { + static int counter = 0; + auto publisher_node = std::make_shared("publisher" + std::to_string(counter++)); + auto publisher = publisher_node->create_publisher(topic_name); + + // We need to publish one message to set up the topic for discovery + publisher->publish(message); + + publishers_.push_back([publisher, topic_name, message, expected_messages]( + CountFunction count_stored_messages) { + if (expected_messages != 0) { + while (rclcpp::ok() && count_stored_messages(topic_name) < expected_messages) { + publisher->publish(message); + // rate limiting + std::this_thread::sleep_for(50ms); + } + } else { + // Just publish a few messages - they should never be stored + publisher->publish(message); + std::this_thread::sleep_for(50ms); + publisher->publish(message); + std::this_thread::sleep_for(50ms); + publisher->publish(message); + } + }); + } + + void run_publishers(CountFunction count_function) + { + std::vector> futures; + for (const auto & publisher : publishers_) { + futures.push_back(std::async(std::launch::async, publisher, count_function)); + } + for (auto & publisher_future : futures) { + publisher_future.get(); + } + } + +private: + std::vector> publishers_; +}; + +} // namespace rosbag2_test_common + +#endif // ROSBAG2_TEST_COMMON__PUBLISHER_MANAGER_HPP_ diff --git a/rosbag2_test_common/include/rosbag2_test_common/subscription_manager.hpp b/rosbag2_test_common/include/rosbag2_test_common/subscription_manager.hpp new file mode 100644 index 0000000000..b961273f62 --- /dev/null +++ b/rosbag2_test_common/include/rosbag2_test_common/subscription_manager.hpp @@ -0,0 +1,99 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TEST_COMMON__SUBSCRIPTION_MANAGER_HPP_ +#define ROSBAG2_TEST_COMMON__SUBSCRIPTION_MANAGER_HPP_ + +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" // rclcpp must be included before the Windows specific includes. + +#include "memory_management.hpp" + +namespace rosbag2_test_common +{ + +class SubscriptionManager +{ +public: + SubscriptionManager() + { + subscriber_node_ = std::make_shared("subscriber_node"); + } + + template + void add_subscription(const std::string & topic_name, size_t expected_number_of_messages) + { + rmw_qos_profile_t qos_profile; + qos_profile.history = RMW_QOS_POLICY_HISTORY_KEEP_ALL; + qos_profile.depth = 4; + qos_profile.reliability = RMW_QOS_POLICY_RELIABILITY_SYSTEM_DEFAULT; + qos_profile.durability = RMW_QOS_POLICY_DURABILITY_SYSTEM_DEFAULT; + qos_profile.avoid_ros_namespace_conventions = false; + expected_topics_with_size_[topic_name] = expected_number_of_messages; + + subscriptions_.push_back( + subscriber_node_->create_subscription( + topic_name, + [this, topic_name](std::shared_ptr msg) { + subscribed_messages_[topic_name].push_back(msg); + }, qos_profile)); + } + + template + std::vector> get_received_messages(const std::string & topic_name) + { + auto messages = subscribed_messages_[topic_name]; + auto received_messages_on_topic = std::vector>(); + for (const auto & msg : messages) { + received_messages_on_topic.push_back(memory_management_.deserialize_message(msg)); + } + return received_messages_on_topic; + } + + std::future spin_subscriptions() + { + return async( + std::launch::async, [this]() { + while (continue_spinning(expected_topics_with_size_)) { + rclcpp::spin_some(subscriber_node_); + } + }); + } + +private: + bool continue_spinning(std::map expected_topics_with_sizes) + { + for (const auto & topic_expected : expected_topics_with_sizes) { + if (subscribed_messages_[topic_expected.first].size() < topic_expected.second) { + return true; + } + } + return false; + } + + std::vector subscriptions_; + std::map>> subscribed_messages_; + std::map expected_topics_with_size_; + rclcpp::Node::SharedPtr subscriber_node_; + MemoryManagement memory_management_; +}; + +} // namespace rosbag2_test_common + +#endif // ROSBAG2_TEST_COMMON__SUBSCRIPTION_MANAGER_HPP_ diff --git a/rosbag2_test_common/include/rosbag2_test_common/temporary_directory_fixture.hpp b/rosbag2_test_common/include/rosbag2_test_common/temporary_directory_fixture.hpp new file mode 100644 index 0000000000..0ee506cb20 --- /dev/null +++ b/rosbag2_test_common/include/rosbag2_test_common/temporary_directory_fixture.hpp @@ -0,0 +1,108 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TEST_COMMON__TEMPORARY_DIRECTORY_FIXTURE_HPP_ +#define ROSBAG2_TEST_COMMON__TEMPORARY_DIRECTORY_FIXTURE_HPP_ + +#include + +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +# include +#endif + +using namespace ::testing; // NOLINT + +namespace rosbag2_test_common +{ + +class TemporaryDirectoryFixture : public Test +{ +public: + static void SetUpTestCase() + { + char template_char[] = "tmp_test_dir.XXXXXX"; +#ifdef _WIN32 + char temp_path[255]; + GetTempPathA(255, temp_path); + _mktemp_s(template_char, strnlen(template_char, 20) + 1); + temporary_dir_path_ = std::string(temp_path) + std::string(template_char); + _mkdir(temporary_dir_path_.c_str()); +#else + char * dir_name = mkdtemp(template_char); + temporary_dir_path_ = dir_name; +#endif + } + + static void TearDownTestCase() + { + remove_directory_recursively(temporary_dir_path_); + } + + static void remove_directory_recursively(const std::string & directory_path) + { +#ifdef _WIN32 + // We need a string of type PCZZTSTR, which is a double null terminated char ptr + size_t length = strlen(directory_path.c_str()); + TCHAR * temp_dir = new TCHAR[length + 2]; + memcpy(temp_dir, temporary_dir_path_.c_str(), length); + temp_dir[length] = 0; + temp_dir[length + 1] = 0; // double null terminated + + SHFILEOPSTRUCT file_options; + file_options.hwnd = nullptr; + file_options.wFunc = FO_DELETE; // delete (recursively) + file_options.pFrom = temp_dir; + file_options.pTo = nullptr; + file_options.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt user + file_options.fAnyOperationsAborted = FALSE; + file_options.lpszProgressTitle = nullptr; + file_options.hNameMappings = nullptr; + + SHFileOperation(&file_options); + delete[] temp_dir; +#else + DIR * dir = opendir(directory_path.c_str()); + if (!dir) { + return; + } + struct dirent * directory_entry; + while ((directory_entry = readdir(dir)) != nullptr) { + // Make sure to not call ".." or "." entries in directory (might delete everything) + if (strcmp(directory_entry->d_name, ".") != 0 && strcmp(directory_entry->d_name, "..") != 0) { + if (directory_entry->d_type == DT_DIR) { + remove_directory_recursively(directory_path + "/" + directory_entry->d_name); + } + remove((directory_path + "/" + directory_entry->d_name).c_str()); + } + } + closedir(dir); + remove(temporary_dir_path_.c_str()); +#endif + } + + static std::string temporary_dir_path_; +}; + +std::string TemporaryDirectoryFixture::temporary_dir_path_ = ""; // NOLINT + +} // namespace rosbag2_test_common + +#endif // ROSBAG2_TEST_COMMON__TEMPORARY_DIRECTORY_FIXTURE_HPP_ diff --git a/rosbag2_test_common/package.xml b/rosbag2_test_common/package.xml new file mode 100644 index 0000000000..42175aa7f4 --- /dev/null +++ b/rosbag2_test_common/package.xml @@ -0,0 +1,21 @@ + + + + rosbag2_test_common + 0.0.0 + Commonly used test helper classes and fixtures for rosbag2 + Karsten Knese + Apache License 2.0 + + ament_cmake + + ament_cmake_gmock + ament_lint_auto + ament_lint_common + rclcpp + rclcpp + + + ament_cmake + + diff --git a/rosbag2_tests/CMakeLists.txt b/rosbag2_tests/CMakeLists.txt new file mode 100644 index 0000000000..22c13af8ee --- /dev/null +++ b/rosbag2_tests/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.5) +project(rosbag2_tests) + +add_definitions(-D_SRC_RESOURCES_DIR_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources") + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +find_package(ament_cmake REQUIRED) +find_package(ament_index_cpp REQUIRED) + +if(BUILD_TESTING) + find_package(ament_cmake_gmock REQUIRED) + find_package(ament_lint_auto REQUIRED) + find_package(rclcpp REQUIRED) + find_package(test_msgs REQUIRED) + find_package(rosbag2_storage REQUIRED) + find_package(rosbag2_storage_default_plugins REQUIRED) + find_package(rosbag2_test_common REQUIRED) + + ament_lint_auto_find_test_dependencies() + + ament_add_gmock(test_rosbag2_record_end_to_end + test/rosbag2_tests/test_rosbag2_record_end_to_end.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_rosbag2_record_end_to_end) + ament_target_dependencies(test_rosbag2_record_end_to_end + rclcpp + rosbag2_storage + rosbag2_storage_default_plugins + rosbag2_test_common + test_msgs) + endif() + + ament_add_gmock(test_rosbag2_play_end_to_end + test/rosbag2_tests/test_rosbag2_play_end_to_end.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_rosbag2_play_end_to_end) + ament_target_dependencies(test_rosbag2_play_end_to_end + rclcpp + rosbag2_storage + rosbag2_storage_default_plugins + rosbag2_test_common + test_msgs) + endif() + +endif() + +ament_package() diff --git a/rosbag2_tests/package.xml b/rosbag2_tests/package.xml new file mode 100644 index 0000000000..d93394c7a5 --- /dev/null +++ b/rosbag2_tests/package.xml @@ -0,0 +1,27 @@ + + + + rosbag2_tests + 0.0.0 + Tests package for rosbag2 + Karsten Knese + Apache License 2.0 + + ament_cmake + + ament_index_cpp + + ament_cmake_gmock + ament_lint_auto + ament_lint_common + rosbag2_storage_default_plugins + rosbag2_test_common + rosbag2_storage + ros2bag + rclcpp + test_msgs + + + ament_cmake + + diff --git a/rosbag2_tests/resources/test.bag b/rosbag2_tests/resources/test.bag new file mode 100644 index 0000000000000000000000000000000000000000..37d84beb3617dde95b9c5f9bfe1948aa4b48fed4 GIT binary patch literal 12288 zcmeI&PiqrF6aetIn{A?)bPFOihu}aFECd%5JQNCP5|&yTlbWVzFAdXl6SvJKVRtA7 z#ANXw-aH6i`~-sFr6(`_0_rF5BY3D64}x)K<91_fp-}V?-U~Z_-pua4{B8rgdZ%DA zgVfu$MHxxM6v#3x5CQ-)ze4=-L(CO_!h30Ce9};c>r+2sd{v(O3F<4}V4wgBpa2S> z01BW03ZMWApa2S>z<(ETFUpbWL_%`AjBYj!%WxdpFdV-R<;!}uqLWHCSI~){y=HEa zrD8>2)XQY0ymUKTUMI`?dXlv}X01B1SS(dYajj5DGLzrSsMR^V?{JB1QASCwP|6WM zH@huGrsHwR)x4s-$2!AS{-dB_?xb>B)G|m#zsGAhtfSjGlA0A~b49%i>Sy)6`j8JW zPyhu`00mG01yBG5Pyhu`00mG01x_n4qbM+QQ)B$tU-gdZ-pFzqj<#Z(mdVUr!%2k{ z&=$20wfRw<8Ftm!HCoKEYt5?J;`2JK8GOILtc%rztUx@i)tc?vj@@<|HtpQ6de6;^ zlqM-~by=gfO}j^5yvisuYgw@r)F96Pkqa+Cy)yYJ_BHk<`XRc_rx+-J0w{n2D1ZVe zfC4Ch0{>FL-H3$YeE4zl(%Q4l+@mjV#rL%m{|x|o!HZvJ0M6|Pb20kHb0VaD==H^Z z81$geTQT%vFyI*wIgt(WVlG1Dc!)hB`d-VMVLI5a@?5@cb(+Qla?5Bo+cyA?S0+WrYDCiXZM-h}El!3!B5QVDSK>rj`rl4~U?EJ%*4eRfBNtGx@K zFTcCXHJ|jKd!8qkh6(rMV(qDwLaY!fUOC&-@!Y58UueUR3nfhon9yTn!ULNgqijBH1_jRTp>M#}Rc+Fl)P3-5(TEEnyQ{1`Ye7W4> z@Vp@8FM$982q1s}0tg_000IagfB*t%fgO(0%Bm_y(rX#pLZ%gs1p|rH)Yt-JyLfkzy9;-w^d%i={Y3x z0&+qifB*srAb*NCc50%k-Wv!-13Tl<#8}*jZBJeHx2mCn^8)Yp zx{DB>c3$A%eP6DPym-rDl^4)FThe&}xj-O*00Iag zfB*srAb`CX$5c3uiHS+IznGm6lX9vS?U+NPJ6kR98KgQ}VF;;IsV@_V+OyeEv9vtZV%D%rq#R-`gkP`v{ z1Q0*~0R#|0009ILKmdXNg+RB{EgbG-nPy+cZ=CU{%lLewL>{0D{pz@7mlv4&aLdsR zQ=j}`-(R5oteqE-GXenw5I_I{1Q0*~0R#|0pcn*_L#|Bqd;RFtlf_p3CNEHEzd+>l zxyB8XcXg<|fL8u$Ixip>2m}y7009ILKmY**5I_KdMHlGSvZ~+fcfRy);ni>Q0t>&3 z%er4+-@s4%Z+>mMUF8Kz%8tzE1>_=u00IagfB*srAbP3&<4$0R#|0009ILKmY**5GY8X+nrVY zZrU@mvheCRd4YxBk=*Jx{Q~)lEwlR$-nRGQ>8%ZazM=90r8hjAlNXSy1Of;kfB*sr pAb + +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" // rclcpp must be included before the Windows specific includes. + +#ifdef _WIN32 +# include +# include +#else +# include +#endif + +#include "test_msgs/msg/primitives.hpp" +#include "test_msgs/msg/static_array_primitives.hpp" +#include "test_msgs/message_fixtures.hpp" +#include "rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp" +#include "rosbag2_test_common/temporary_directory_fixture.hpp" +#include "rosbag2_test_common/publisher_manager.hpp" +#include "rosbag2_test_common/memory_management.hpp" + +using namespace ::testing; // NOLINT +using namespace std::chrono_literals; // NOLINT +using namespace rosbag2_test_common; // NOLINT + +#ifdef _WIN32 +using ProcessHandle = HANDLE; +#else +using ProcessHandle = int; +#endif + +class RecordFixture : public TemporaryDirectoryFixture +{ +public: + RecordFixture() + { + std::string system_separator = "/"; +#ifdef _WIN32 + system_separator = "\\"; +#endif + database_name_ = temporary_dir_path_ + system_separator + "test.bag"; + std::cout << "Database name: " << database_name_ << std::endl; + rclcpp::init(0, nullptr); + } + + ~RecordFixture() override + { + rclcpp::shutdown(); + } + + ProcessHandle start_execution(const std::string & command) + { +#ifdef _WIN32 + auto h_job = CreateJobObject(nullptr, nullptr); + JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{}; + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject(h_job, JobObjectExtendedLimitInformation, &info, sizeof(info)); + + STARTUPINFO start_up_info{}; + PROCESS_INFORMATION process_info{}; + + size_t length = strlen(command.c_str()); + TCHAR * command_char = new TCHAR[length + 1]; + memcpy(command_char, command.c_str(), length + 1); + + CreateProcess( + nullptr, + command_char, + nullptr, + nullptr, + false, + 0, + nullptr, + temporary_dir_path_.c_str(), + &start_up_info, + &process_info); + + AssignProcessToJobObject(h_job, process_info.hProcess); + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + delete[] command_char; + return h_job; +#else + auto process_id = fork(); + if (process_id == 0) { + setpgid(getpid(), getpid()); + chdir(temporary_dir_path_.c_str()); + system(command.c_str()); + } + return process_id; +#endif + } + + void wait_for_db() + { + while (true) { + try { + std::this_thread::sleep_for(50ms); // wait a bit to not query constantly + rosbag2_storage_plugins::SqliteWrapper + db(database_name_, rosbag2_storage::storage_interfaces::IOFlag::READ_ONLY); + return; + } catch (const rosbag2_storage_plugins::SqliteException & ex) { + (void) ex; // still waiting + } + } + } + + void stop_execution(ProcessHandle handle) + { +#ifdef _WIN32 + CloseHandle(handle); +#else + kill(-handle, SIGTERM); +#endif + } + + size_t + count_stored_messages(rosbag2_storage_plugins::SqliteWrapper & db, const std::string & topic_name) + { + // protect against concurrent writes (from recording) that may make the count query throw. + while (true) { + try { + return count_stored_messages_in_db(db, topic_name); + } catch (const rosbag2_storage_plugins::SqliteException & e) { + (void) e; + } + std::this_thread::sleep_for(50ms); + } + } + + size_t count_stored_messages_in_db( + rosbag2_storage_plugins::SqliteWrapper & db, const std::string & topic_name) + { + auto row = db.prepare_statement( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = 'topics';") + ->execute_query().get_single_line(); + if (std::get<0>(row) == 0) { + return 0; + } + auto message_count = db.prepare_statement( + "SELECT COUNT(*) " + "FROM messages LEFT JOIN topics ON messages.topic_id = topics.id " + "WHERE topics.name = ?;")->bind(topic_name)->execute_query().get_single_line(); + return static_cast(std::get<0>(message_count)); + } + + template + std::vector> get_messages_for_topic(const std::string & topic) + { + auto all_messages = get_messages(); + auto topic_messages = std::vector>(); + for (const auto & msg : all_messages) { + if (msg->topic_name == topic) { + topic_messages.push_back( + memory_management_.deserialize_message(msg->serialized_data)); + } + } + return topic_messages; + } + + std::vector> get_messages() + { + std::vector> table_msgs; + auto storage = std::make_shared(); + storage->open(database_name_, rosbag2_storage::storage_interfaces::IOFlag::READ_ONLY); + + while (storage->has_next()) { + table_msgs.push_back(storage->read_next()); + } + + return table_msgs; + } + + std::string database_name_; + PublisherManager pub_man_; + MemoryManagement memory_management_; +}; + + +#endif // ROSBAG2_TESTS__RECORD_FIXTURE_HPP_ diff --git a/rosbag2_tests/test/rosbag2_tests/test_rosbag2_play_end_to_end.cpp b/rosbag2_tests/test/rosbag2_tests/test_rosbag2_play_end_to_end.cpp new file mode 100644 index 0000000000..7f024fa53d --- /dev/null +++ b/rosbag2_tests/test/rosbag2_tests/test_rosbag2_play_end_to_end.cpp @@ -0,0 +1,114 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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 + +#include +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" // rclcpp must be included before the Windows specific includes. + +#ifdef _WIN32 +# include +# include +#endif + +#include "test_msgs/msg/primitives.hpp" +#include "test_msgs/msg/static_array_primitives.hpp" +#include "test_msgs/message_fixtures.hpp" +#include "rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp" +#include "rosbag2_test_common/subscription_manager.hpp" + +using namespace ::testing; // NOLINT +using namespace std::chrono_literals; // NOLINT +using namespace rosbag2_test_common; // NOLINT + +class EndToEndTestFixture : public Test +{ +public: + EndToEndTestFixture() + { + database_path_ = _SRC_RESOURCES_DIR_PATH; // variable defined in CMakeLists.txt + rclcpp::init(0, nullptr); + sub_ = std::make_unique(); + } + + ~EndToEndTestFixture() override + { + rclcpp::shutdown(); + } + + void execute(const std::string & command) + { +#ifdef _WIN32 + size_t length = strlen(command.c_str()); + TCHAR * command_char = new TCHAR[length + 1]; + memcpy(command_char, command.c_str(), length + 1); + + STARTUPINFO start_up_info{}; + PROCESS_INFORMATION process_info{}; + CreateProcess( + nullptr, + command_char, + nullptr, + nullptr, + false, + 0, + nullptr, + database_path_.c_str(), + &start_up_info, + &process_info); + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + delete[] command_char; +#else + chdir(database_path_.c_str()); + system(command.c_str()); +#endif + } + + std::string database_path_; + std::unique_ptr sub_; +}; + +TEST_F(EndToEndTestFixture, play_end_to_end_test) { + sub_->add_subscription("/array_topic", 2); + sub_->add_subscription("/test_topic", 3); + + auto subscription_future = sub_->spin_subscriptions(); + + execute("ros2 bag play test.bag"); + + subscription_future.get(); + + auto primitive_messages = sub_->get_received_messages("/test_topic"); + auto array_messages = sub_->get_received_messages( + "/array_topic"); + + EXPECT_THAT(primitive_messages, SizeIs(Ge(3u))); + EXPECT_THAT(primitive_messages, + Each(Pointee(Field(&test_msgs::msg::Primitives::string_value, "test")))); + + EXPECT_THAT(array_messages, SizeIs(Ge(2u))); + EXPECT_THAT(array_messages, + Each(Pointee(Field(&test_msgs::msg::StaticArrayPrimitives::bool_values, + ElementsAre(true, false, true))))); + EXPECT_THAT(array_messages, + Each(Pointee(Field(&test_msgs::msg::StaticArrayPrimitives::string_values, + ElementsAre("Complex Hello1", "Complex Hello2", "Complex Hello3"))))); +} diff --git a/rosbag2_tests/test/rosbag2_tests/test_rosbag2_record_end_to_end.cpp b/rosbag2_tests/test/rosbag2_tests/test_rosbag2_record_end_to_end.cpp new file mode 100644 index 0000000000..fc33643b50 --- /dev/null +++ b/rosbag2_tests/test/rosbag2_tests/test_rosbag2_record_end_to_end.cpp @@ -0,0 +1,49 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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 + +#include + +#include "record_fixture.hpp" + +TEST_F(RecordFixture, record_end_to_end_test) { + auto message = get_messages_primitives()[0]; + message->string_value = "test"; + size_t expected_test_messages = 3; + pub_man_.add_publisher("/test_topic", message, expected_test_messages); + + auto wrong_message = get_messages_primitives()[0]; + wrong_message->string_value = "wrong_content"; + pub_man_.add_publisher("/wrong_topic", wrong_message); + + auto process_handle = start_execution("ros2 bag record /test_topic"); + wait_for_db(); + + rosbag2_storage_plugins::SqliteWrapper + db(database_name_, rosbag2_storage::storage_interfaces::IOFlag::READ_ONLY); + pub_man_.run_publishers([this, &db](const std::string & topic_name) { + return count_stored_messages(db, topic_name); + }); + + stop_execution(process_handle); + + auto test_topic_messages = get_messages_for_topic("/test_topic"); + EXPECT_THAT(test_topic_messages, SizeIs(Ge(expected_test_messages))); + EXPECT_THAT(test_topic_messages, + Each(Pointee(Field(&test_msgs::msg::Primitives::string_value, "test")))); + + auto wrong_topic_messages = get_messages_for_topic("/wrong_topic"); + EXPECT_THAT(wrong_topic_messages, IsEmpty()); +} diff --git a/rosbag2_transport/CMakeLists.txt b/rosbag2_transport/CMakeLists.txt new file mode 100644 index 0000000000..5931e872db --- /dev/null +++ b/rosbag2_transport/CMakeLists.txt @@ -0,0 +1,133 @@ +cmake_minimum_required(VERSION 3.5) +project(rosbag2_transport) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(ament_cmake_ros REQUIRED) +find_package(rcl REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rcutils REQUIRED) +find_package(rosbag2 REQUIRED) +find_package(shared_queues_vendor REQUIRED) + +add_library(${PROJECT_NAME} SHARED + src/rosbag2_transport/player.cpp + src/rosbag2_transport/generic_publisher.cpp + src/rosbag2_transport/generic_subscription.cpp + src/rosbag2_transport/rosbag2_node.cpp + src/rosbag2_transport/rosbag2_transport.cpp) +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $) + +ament_target_dependencies(${PROJECT_NAME} + rcl + rclcpp + rcutils + rosbag2 + rosidl_generator_cpp + shared_queues_vendor +) + +include(cmake/configure_python.cmake) +ament_python_install_package(${PROJECT_NAME}) +add_library(rosbag2_transport_py + SHARED + src/rosbag2_transport/rosbag2_transport_python.cpp) +configure_python_c_extension_library(rosbag2_transport_py) +target_link_libraries(rosbag2_transport_py rosbag2_transport) + +# Causes the visibility macros to use dllexport rather than dllimport, +# which is appropriate when building the dll but not consuming it. +target_compile_definitions(${PROJECT_NAME} PRIVATE "ROSBAG2_TRANSPORT_BUILDING_LIBRARY") + +install( + DIRECTORY include/ + DESTINATION include +) +install( + TARGETS ${PROJECT_NAME} + EXPORT export_${PROJECT_NAME} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +ament_export_include_directories(include) +ament_export_interfaces(export_${PROJECT_NAME}) +ament_export_libraries(${PROJECT_NAME}) +ament_export_dependencies(rosbag2) + +if(BUILD_TESTING) + find_package(ament_cmake_gmock REQUIRED) + find_package(ament_lint_auto REQUIRED) + find_package(test_msgs REQUIRED) + find_package(rosbag2_test_common REQUIRED) + ament_lint_auto_find_test_dependencies() + + ament_add_gmock(test_record + test/rosbag2_transport/test_record.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_record) + target_link_libraries(test_record rosbag2_transport) + ament_target_dependencies(test_record test_msgs rosbag2_test_common) + endif() + + ament_add_gmock(test_record_all + test/rosbag2_transport/test_record_all.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_record_all) + target_link_libraries(test_record_all rosbag2_transport) + ament_target_dependencies(test_record_all test_msgs rosbag2_test_common) + endif() + + ament_add_gmock(test_play + test/rosbag2_transport/test_play.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_play) + target_link_libraries(test_play rosbag2_transport) + ament_target_dependencies(test_play test_msgs rosbag2_test_common) + endif() + + ament_add_gmock(test_play_timing + test/rosbag2_transport/test_play_timing.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_play_timing) + target_link_libraries(test_play_timing rosbag2_transport) + ament_target_dependencies(test_play_timing test_msgs rosbag2_test_common) + endif() + + ament_add_gmock(test_rosbag2_node + test/rosbag2_transport/test_rosbag2_node.cpp + src/rosbag2_transport/generic_publisher.cpp + src/rosbag2_transport/generic_subscription.cpp + src/rosbag2_transport/rosbag2_node.cpp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(TARGET test_rosbag2_node) + target_include_directories(test_rosbag2_node + PUBLIC + $ + $) + ament_target_dependencies(test_rosbag2_node + ament_index_cpp + rosbag2 + rclcpp + test_msgs + rosbag2_test_common) + endif() +endif() + +ament_package() diff --git a/rosbag2_transport/cmake/configure_python.cmake b/rosbag2_transport/cmake/configure_python.cmake new file mode 100644 index 0000000000..78e8b56668 --- /dev/null +++ b/rosbag2_transport/cmake/configure_python.cmake @@ -0,0 +1,56 @@ +# Copyright 2018 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. + +find_package(ament_cmake_python REQUIRED) +find_package(python_cmake_module REQUIRED) +find_package(PythonExtra MODULE REQUIRED) + +set(_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}") + +if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set(PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE_DEBUG}") +endif() + +function(set_properties _targetname _build_type) + + set_target_properties(${_targetname} PROPERTIES + PREFIX "" + LIBRARY_OUTPUT_DIRECTORY${_build_type} "${CMAKE_CURRENT_BINARY_DIR}/test_${PROJECT_NAME}" + RUNTIME_OUTPUT_DIRECTORY${_build_type} "${CMAKE_CURRENT_BINARY_DIR}/test_${PROJECT_NAME}" + OUTPUT_NAME "_${_targetname}${PythonExtra_EXTENSION_SUFFIX}" + SUFFIX "${PythonExtra_EXTENSION_EXTENSION}") +endfunction() + +function(configure_python_c_extension_library _library_name) + set_properties(${_library_name} "") + if(WIN32) + set_properties(${_library_name} "_DEBUG") + set_properties(${_library_name} "_MINSIZEREL") + set_properties(${_library_name} "_RELEASE") + set_properties(${_library_name} "_RELWITHDEBINFO") + endif() + + target_link_libraries(${_library_name} + ${PythonExtra_LIBRARIES} + ) + + target_include_directories(${_library_name} + PUBLIC + ${PythonExtra_INCLUDE_DIRS} + ) + + install(TARGETS ${_library_name} + DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}" + ) +endfunction() diff --git a/rosbag2_transport/include/rosbag2_transport/logging.hpp b/rosbag2_transport/include/rosbag2_transport/logging.hpp new file mode 100644 index 0000000000..41aa111080 --- /dev/null +++ b/rosbag2_transport/include/rosbag2_transport/logging.hpp @@ -0,0 +1,61 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__LOGGING_HPP_ +#define ROSBAG2_TRANSPORT__LOGGING_HPP_ + +#include +#include + +#include "rcutils/logging_macros.h" + +#define ROSBAG2_TRANSPORT_PACKAGE_NAME "rosbag2_transport" + +#define ROSBAG2_TRANSPORT_LOG_INFO(...) \ + RCUTILS_LOG_INFO_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __VA_ARGS__) + +#define ROSBAG2_TRANSPORT_LOG_INFO_STREAM(args) do { \ + std::stringstream __ss; \ + __ss << args; \ + RCUTILS_LOG_INFO_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __ss.str().c_str()); \ +} while (0) + +#define ROSBAG2_TRANSPORT_LOG_ERROR(...) \ + RCUTILS_LOG_ERROR_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __VA_ARGS__) + +#define ROSBAG2_TRANSPORT_LOG_ERROR_STREAM(args) do { \ + std::stringstream __ss; \ + __ss << args; \ + RCUTILS_LOG_ERROR_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __ss.str().c_str()); \ +} while (0) + +#define ROSBAG2_TRANSPORT_LOG_WARN(...) \ + RCUTILS_LOG_WARN_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __VA_ARGS__) + +#define ROSBAG2_TRANSPORT_LOG_WARN_STREAM(args) do { \ + std::stringstream __ss; \ + __ss << args; \ + RCUTILS_LOG_WARN_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __ss.str().c_str()); \ +} while (0) + +#define ROSBAG2_TRANSPORT_LOG_DEBUG(...) \ + RCUTILS_LOG_DEBUG_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __VA_ARGS__) + +#define ROSBAG2_TRANSPORT_LOG_DEBUG_STREAM(args) do { \ + std::stringstream __ss; \ + __ss << args; \ + RCUTILS_LOG_DEBUG_NAMED(ROSBAG2_TRANSPORT_PACKAGE_NAME, __ss.str().c_str()); \ +} while (0) + +#endif // ROSBAG2_TRANSPORT__LOGGING_HPP_ diff --git a/rosbag2_transport/include/rosbag2_transport/play_options.hpp b/rosbag2_transport/include/rosbag2_transport/play_options.hpp new file mode 100644 index 0000000000..e4259ae78b --- /dev/null +++ b/rosbag2_transport/include/rosbag2_transport/play_options.hpp @@ -0,0 +1,31 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__PLAY_OPTIONS_HPP_ +#define ROSBAG2_TRANSPORT__PLAY_OPTIONS_HPP_ + +#include + +namespace rosbag2_transport +{ + +struct PlayOptions +{ +public: + size_t read_ahead_queue_size; +}; + +} // namespace rosbag2_transport + +#endif // ROSBAG2_TRANSPORT__PLAY_OPTIONS_HPP_ diff --git a/rosbag2_transport/include/rosbag2_transport/record_options.hpp b/rosbag2_transport/include/rosbag2_transport/record_options.hpp new file mode 100644 index 0000000000..2616d35e2c --- /dev/null +++ b/rosbag2_transport/include/rosbag2_transport/record_options.hpp @@ -0,0 +1,32 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__RECORD_OPTIONS_HPP_ +#define ROSBAG2_TRANSPORT__RECORD_OPTIONS_HPP_ + +#include +#include + +namespace rosbag2_transport +{ +struct RecordOptions +{ +public: + bool all; + std::vector topics; +}; + +} // namespace rosbag2_transport + +#endif // ROSBAG2_TRANSPORT__RECORD_OPTIONS_HPP_ diff --git a/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp b/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp new file mode 100644 index 0000000000..7b13894d9f --- /dev/null +++ b/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp @@ -0,0 +1,94 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__ROSBAG2_TRANSPORT_HPP_ +#define ROSBAG2_TRANSPORT__ROSBAG2_TRANSPORT_HPP_ + +#include +#include +#include +#include +#include + +#include "rosbag2/sequential_reader.hpp" +#include "rosbag2/writer.hpp" +#include "rosbag2_transport/play_options.hpp" +#include "rosbag2_transport/record_options.hpp" +#include "rosbag2_transport/storage_options.hpp" +#include "rosbag2_transport/visibility_control.hpp" + +namespace rosbag2_transport +{ + +class GenericPublisher; +class GenericSubscription; +class Rosbag2Node; +class Player; + +class Rosbag2Transport +{ +public: + /// Default constructor + ROSBAG2_TRANSPORT_PUBLIC + Rosbag2Transport(); + + /// Constructor for testing, allows to set the reader and writer to use + ROSBAG2_TRANSPORT_PUBLIC + Rosbag2Transport( + std::shared_ptr reader, std::shared_ptr writer); + + ROSBAG2_TRANSPORT_PUBLIC + void init(); + + ROSBAG2_TRANSPORT_PUBLIC + void shutdown(); + + /** + * Records topics to a bagfile. Subscription happens at startup time, hence the topics must + * exist when "record" is called. + * + * \param storage_options Options regarding the storage (e.g. bag file name) + * \param record_options Options regarding the recording (e.g. the topics to record) + */ + ROSBAG2_TRANSPORT_PUBLIC + void record(const StorageOptions & storage_options, const RecordOptions & record_options); + + /** + * Replay all topics in a bagfile. + * + * \param storage_options Option regarding the storage (e.g. bag file name) + * \param play_options Options regarding the playback (e.g. queue size) + */ + ROSBAG2_TRANSPORT_PUBLIC + void play(const StorageOptions & storage_options, const PlayOptions & play_options); + +private: + std::shared_ptr + create_subscription( + std::shared_ptr & node, + const std::string & topic_name, const std::string & topic_type) const; + + std::shared_ptr reader_; + std::shared_ptr writer_; + + std::shared_ptr transport_node_; + + std::vector> subscriptions_; + + std::shared_ptr setup_node(); +}; + +} // namespace rosbag2_transport + +#endif // ROSBAG2_TRANSPORT__ROSBAG2_TRANSPORT_HPP_ diff --git a/rosbag2_transport/include/rosbag2_transport/storage_options.hpp b/rosbag2_transport/include/rosbag2_transport/storage_options.hpp new file mode 100644 index 0000000000..dd571baac2 --- /dev/null +++ b/rosbag2_transport/include/rosbag2_transport/storage_options.hpp @@ -0,0 +1,29 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__STORAGE_OPTIONS_HPP_ +#define ROSBAG2_TRANSPORT__STORAGE_OPTIONS_HPP_ + +#include + +#include "rosbag2/storage_options.hpp" + +namespace rosbag2_transport +{ + +using StorageOptions = rosbag2::StorageOptions; + +} // namespace rosbag2_transport + +#endif // ROSBAG2_TRANSPORT__STORAGE_OPTIONS_HPP_ diff --git a/rosbag2_transport/include/rosbag2_transport/visibility_control.hpp b/rosbag2_transport/include/rosbag2_transport/visibility_control.hpp new file mode 100644 index 0000000000..05e8991bd4 --- /dev/null +++ b/rosbag2_transport/include/rosbag2_transport/visibility_control.hpp @@ -0,0 +1,56 @@ +// Copyright 2018 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. + +/* This header must be included by all rclcpp headers which declare symbols + * which are defined in the rclcpp library. When not building the rclcpp + * library, i.e. when using the headers in other package's code, the contents + * of this header change the visibility of certain symbols which the rclcpp + * library cannot have, but the consuming code must have inorder to link. + */ + +#ifndef ROSBAG2_TRANSPORT__VISIBILITY_CONTROL_HPP_ +#define ROSBAG2_TRANSPORT__VISIBILITY_CONTROL_HPP_ + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define ROSBAG2_TRANSPORT_EXPORT __attribute__ ((dllexport)) + #define ROSBAG2_TRANSPORT_IMPORT __attribute__ ((dllimport)) + #else + #define ROSBAG2_TRANSPORT_EXPORT __declspec(dllexport) + #define ROSBAG2_TRANSPORT_IMPORT __declspec(dllimport) + #endif + #ifdef ROSBAG2_TRANSPORT_BUILDING_LIBRARY + #define ROSBAG2_TRANSPORT_PUBLIC ROSBAG2_TRANSPORT_EXPORT + #else + #define ROSBAG2_TRANSPORT_PUBLIC ROSBAG2_TRANSPORT_IMPORT + #endif + #define ROSBAG2_TRANSPORT_PUBLIC_TYPE ROSBAG2_TRANSPORT_PUBLIC + #define ROSBAG2_TRANSPORT_LOCAL +#else + #define ROSBAG2_TRANSPORT_EXPORT __attribute__ ((visibility("default"))) + #define ROSBAG2_TRANSPORT_IMPORT + #if __GNUC__ >= 4 + #define ROSBAG2_TRANSPORT_PUBLIC __attribute__ ((visibility("default"))) + #define ROSBAG2_TRANSPORT_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define ROSBAG2_TRANSPORT_PUBLIC + #define ROSBAG2_TRANSPORT_LOCAL + #endif + #define ROSBAG2_TRANSPORT_PUBLIC_TYPE +#endif + +#endif // ROSBAG2_TRANSPORT__VISIBILITY_CONTROL_HPP_ diff --git a/rosbag2_transport/package.xml b/rosbag2_transport/package.xml new file mode 100644 index 0000000000..be16362d48 --- /dev/null +++ b/rosbag2_transport/package.xml @@ -0,0 +1,25 @@ + + + + rosbag2_transport + 0.0.0 + Layer encapsulating ROS middleware to allow rosbag2 to be used with or without middleware + karsten + Apache License 2.0 + + ament_cmake_ros + + rclcpp + rosbag2 + shared_queues_vendor + + ament_cmake_gmock + ament_lint_auto + ament_lint_common + test_msgs + rosbag2_test_common + + + ament_cmake + + diff --git a/rosbag2_transport/rosbag2_transport/__init__.py b/rosbag2_transport/rosbag2_transport/__init__.py new file mode 100644 index 0000000000..d5835797e4 --- /dev/null +++ b/rosbag2_transport/rosbag2_transport/__init__.py @@ -0,0 +1,31 @@ +# Copyright 2018 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. + +import importlib +import os + + +def _import(name): + try: + return importlib.import_module(name, package='rosbag2_transport') + except ImportError as e: + if e.path is not None and os.path.isfile(e.path): + e.msg += \ + "\nThe C extension '%s' failed to be imported while being present on the system." \ + " Please refer to '%s' for possible solutions" % \ + (e.path, 'https://github.com/ros2/ros2/wiki/Rclpy-Import-error-hint') + raise + + +rosbag2_transport_py = _import('._rosbag2_transport_py') diff --git a/rosbag2/src/rosbag2/generic_publisher.cpp b/rosbag2_transport/src/rosbag2_transport/generic_publisher.cpp similarity index 95% rename from rosbag2/src/rosbag2/generic_publisher.cpp rename to rosbag2_transport/src/rosbag2_transport/generic_publisher.cpp index cc61c194ac..0f4b53da3f 100644 --- a/rosbag2/src/rosbag2/generic_publisher.cpp +++ b/rosbag2_transport/src/rosbag2_transport/generic_publisher.cpp @@ -17,7 +17,7 @@ #include #include -namespace rosbag2 +namespace rosbag2_transport { GenericPublisher::GenericPublisher( @@ -37,4 +37,4 @@ void GenericPublisher::publish(std::shared_ptr message } } -} // namespace rosbag2 +} // namespace rosbag2_transport diff --git a/rosbag2/src/rosbag2/generic_publisher.hpp b/rosbag2_transport/src/rosbag2_transport/generic_publisher.hpp similarity index 82% rename from rosbag2/src/rosbag2/generic_publisher.hpp rename to rosbag2_transport/src/rosbag2_transport/generic_publisher.hpp index 61dcd57c18..8f1a2993da 100644 --- a/rosbag2/src/rosbag2/generic_publisher.hpp +++ b/rosbag2_transport/src/rosbag2_transport/generic_publisher.hpp @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__GENERIC_PUBLISHER_HPP_ -#define ROSBAG2__GENERIC_PUBLISHER_HPP_ +#ifndef ROSBAG2_TRANSPORT__GENERIC_PUBLISHER_HPP_ +#define ROSBAG2_TRANSPORT__GENERIC_PUBLISHER_HPP_ #include #include #include "rclcpp/rclcpp.hpp" -namespace rosbag2 +namespace rosbag2_transport { class GenericPublisher : public rclcpp::PublisherBase @@ -36,6 +36,6 @@ class GenericPublisher : public rclcpp::PublisherBase void publish(std::shared_ptr message); }; -} // namespace rosbag2 +} // namespace rosbag2_transport -#endif // ROSBAG2__GENERIC_PUBLISHER_HPP_ +#endif // ROSBAG2_TRANSPORT__GENERIC_PUBLISHER_HPP_ diff --git a/rosbag2/src/rosbag2/generic_subscription.cpp b/rosbag2_transport/src/rosbag2_transport/generic_subscription.cpp similarity index 95% rename from rosbag2/src/rosbag2/generic_subscription.cpp rename to rosbag2_transport/src/rosbag2_transport/generic_subscription.cpp index 43e27d1bdd..aa49cdc2fc 100644 --- a/rosbag2/src/rosbag2/generic_subscription.cpp +++ b/rosbag2_transport/src/rosbag2_transport/generic_subscription.cpp @@ -20,9 +20,9 @@ #include "rclcpp/any_subscription_callback.hpp" #include "rclcpp/subscription.hpp" -#include "rosbag2/logging.hpp" +#include "rosbag2_transport/logging.hpp" -namespace rosbag2 +namespace rosbag2_transport { GenericSubscription::GenericSubscription( @@ -100,7 +100,7 @@ GenericSubscription::borrow_serialized_message(size_t capacity) auto fini_return = rmw_serialized_message_fini(msg); delete msg; if (fini_return != RCL_RET_OK) { - ROSBAG2_LOG_ERROR_STREAM( + ROSBAG2_TRANSPORT_LOG_ERROR_STREAM( "Failed to destroy serialized message: " << rcl_get_error_string_safe()); } }); @@ -108,4 +108,4 @@ GenericSubscription::borrow_serialized_message(size_t capacity) return serialized_msg; } -} // namespace rosbag2 +} // namespace rosbag2_transport diff --git a/rosbag2/src/rosbag2/generic_subscription.hpp b/rosbag2_transport/src/rosbag2_transport/generic_subscription.hpp similarity index 93% rename from rosbag2/src/rosbag2/generic_subscription.hpp rename to rosbag2_transport/src/rosbag2_transport/generic_subscription.hpp index 84e1351e33..d2b23591f6 100644 --- a/rosbag2/src/rosbag2/generic_subscription.hpp +++ b/rosbag2_transport/src/rosbag2_transport/generic_subscription.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__GENERIC_SUBSCRIPTION_HPP_ -#define ROSBAG2__GENERIC_SUBSCRIPTION_HPP_ +#ifndef ROSBAG2_TRANSPORT__GENERIC_SUBSCRIPTION_HPP_ +#define ROSBAG2_TRANSPORT__GENERIC_SUBSCRIPTION_HPP_ #include #include @@ -21,7 +21,7 @@ #include "rclcpp/any_subscription_callback.hpp" #include "rclcpp/subscription.hpp" -namespace rosbag2 +namespace rosbag2_transport { /** @@ -80,6 +80,6 @@ class GenericSubscription : public rclcpp::SubscriptionBase std::function)> callback_; }; -} // namespace rosbag2 +} // namespace rosbag2_transport -#endif // ROSBAG2__GENERIC_SUBSCRIPTION_HPP_ +#endif // ROSBAG2_TRANSPORT__GENERIC_SUBSCRIPTION_HPP_ diff --git a/rosbag2/src/rosbag2/player.cpp b/rosbag2_transport/src/rosbag2_transport/player.cpp similarity index 73% rename from rosbag2/src/rosbag2/player.cpp rename to rosbag2_transport/src/rosbag2_transport/player.cpp index 4c73ea1786..4a8f2babe3 100644 --- a/rosbag2/src/rosbag2/player.cpp +++ b/rosbag2_transport/src/rosbag2_transport/player.cpp @@ -26,19 +26,20 @@ #include "rcl/graph.h" #include "rcutils/time.h" -#include "rosbag2_storage/serialized_bag_message.hpp" -#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" -#include "rosbag2_storage/storage_factory.hpp" -#include "rosbag2/logging.hpp" +#include "rosbag2_transport/logging.hpp" #include "rosbag2_node.hpp" #include "replayable_message.hpp" -#include "typesupport_helpers.hpp" +#include "rosbag2/typesupport_helpers.hpp" -namespace rosbag2 +namespace rosbag2_transport { -Player::Player(std::shared_ptr storage) -: storage_(storage), node_(std::make_shared("rosbag2_node")) +const std::chrono::milliseconds +Player::queue_read_wait_period_ = std::chrono::milliseconds(100); + +Player::Player( + std::shared_ptr reader, std::shared_ptr rosbag2_transport) +: reader_(std::move(reader)), rosbag2_transport_(rosbag2_transport) {} bool Player::is_storage_completely_loaded() const @@ -51,7 +52,7 @@ bool Player::is_storage_completely_loaded() const return !storage_loading_future_.valid(); } -void Player::play(const Rosbag2PlayOptions & options) +void Player::play(const PlayOptions & options) { prepare_publishers(); @@ -63,23 +64,23 @@ void Player::play(const Rosbag2PlayOptions & options) play_messages_from_queue(); } -void Player::wait_for_filled_queue(const Rosbag2PlayOptions & options) const +void Player::wait_for_filled_queue(const PlayOptions & options) const { while ( message_queue_.size_approx() < options.read_ahead_queue_size && !is_storage_completely_loaded()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(queue_read_wait_period_); } } -void Player::load_storage_content(const Rosbag2PlayOptions & options) +void Player::load_storage_content(const PlayOptions & options) { TimePoint time_first_message; ReplayableMessage message; - if (storage_->has_next()) { - message.message = storage_->read_next(); + if (reader_->has_next()) { + message.message = reader_->read_next(); message.time_since_start = std::chrono::nanoseconds(0); time_first_message = TimePoint(std::chrono::nanoseconds(message.message->time_stamp)); message_queue_.enqueue(message); @@ -89,7 +90,7 @@ void Player::load_storage_content(const Rosbag2PlayOptions & options) static_cast(options.read_ahead_queue_size * read_ahead_lower_bound_percentage_); auto queue_upper_boundary = options.read_ahead_queue_size; - while (storage_->has_next()) { + while (reader_->has_next()) { if (message_queue_.size_approx() < queue_lower_boundary) { enqueue_up_to_boundary(time_first_message, queue_upper_boundary); } else { @@ -102,10 +103,10 @@ void Player::enqueue_up_to_boundary(const TimePoint & time_first_message, uint64 { ReplayableMessage message; for (size_t i = message_queue_.size_approx(); i < boundary; i++) { - if (!storage_->has_next()) { + if (!reader_->has_next()) { break; } - message.message = storage_->read_next(); + message.message = reader_->read_next(); message.time_since_start = TimePoint(std::chrono::nanoseconds(message.message->time_stamp)) - time_first_message; @@ -128,11 +129,11 @@ void Player::play_messages_from_queue() void Player::prepare_publishers() { - auto all_topics_and_types = storage_->get_all_topics_and_types(); - for (const auto & element : all_topics_and_types) { + auto topics = reader_->get_all_topics_and_types(); + for (const auto & topic : topics) { publishers_.insert(std::make_pair( - element.first, node_->create_generic_publisher(element.first, element.second))); + topic.name, rosbag2_transport_->create_generic_publisher(topic.name, topic.type))); } } -} // namespace rosbag2 +} // namespace rosbag2_transport diff --git a/rosbag2/src/rosbag2/player.hpp b/rosbag2_transport/src/rosbag2_transport/player.hpp similarity index 64% rename from rosbag2/src/rosbag2/player.hpp rename to rosbag2_transport/src/rosbag2_transport/player.hpp index c30f360748..fd3e4031f2 100644 --- a/rosbag2/src/rosbag2/player.hpp +++ b/rosbag2_transport/src/rosbag2_transport/player.hpp @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__PLAYER_HPP_ -#define ROSBAG2__PLAYER_HPP_ +#ifndef ROSBAG2_TRANSPORT__PLAYER_HPP_ +#define ROSBAG2_TRANSPORT__PLAYER_HPP_ +#include #include #include #include @@ -23,13 +24,13 @@ #include "moodycamel/readerwriterqueue.h" #include "replayable_message.hpp" -#include "rosbag2_storage/storage_interfaces/read_only_interface.hpp" -#include "rosbag2_storage/storage_interfaces/read_write_interface.hpp" -#include "rosbag2/rosbag2_play_options.hpp" +#include "rosbag2/sequential_reader.hpp" +#include "rosbag2/types.hpp" +#include "rosbag2_transport/play_options.hpp" using TimePoint = std::chrono::time_point; -namespace rosbag2 +namespace rosbag2_transport { class GenericPublisher; @@ -38,27 +39,30 @@ class Rosbag2Node; class Player { public: - explicit Player(std::shared_ptr storage); + explicit Player( + std::shared_ptr reader, + std::shared_ptr rosbag2_transport); - void play(const Rosbag2PlayOptions & options); + void play(const PlayOptions & options); private: - void load_storage_content(const Rosbag2PlayOptions & options); + void load_storage_content(const PlayOptions & options); bool is_storage_completely_loaded() const; void enqueue_up_to_boundary(const TimePoint & time_first_message, uint64_t boundary); - void wait_for_filled_queue(const Rosbag2PlayOptions & options) const; + void wait_for_filled_queue(const PlayOptions & options) const; void play_messages_from_queue(); void prepare_publishers(); static constexpr double read_ahead_lower_bound_percentage_ = 0.9; + static const std::chrono::milliseconds queue_read_wait_period_; - std::shared_ptr storage_; + std::shared_ptr reader_; moodycamel::ReaderWriterQueue message_queue_; mutable std::future storage_loading_future_; - std::shared_ptr node_; + std::shared_ptr rosbag2_transport_; std::map> publishers_; }; -} // namespace rosbag2 +} // namespace rosbag2_transport -#endif // ROSBAG2__PLAYER_HPP_ +#endif // ROSBAG2_TRANSPORT__PLAYER_HPP_ diff --git a/rosbag2/src/rosbag2/replayable_message.hpp b/rosbag2_transport/src/rosbag2_transport/replayable_message.hpp similarity index 70% rename from rosbag2/src/rosbag2/replayable_message.hpp rename to rosbag2_transport/src/rosbag2_transport/replayable_message.hpp index abb18c9ea9..2def918f2d 100644 --- a/rosbag2/src/rosbag2/replayable_message.hpp +++ b/rosbag2_transport/src/rosbag2_transport/replayable_message.hpp @@ -12,23 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__REPLAYABLE_MESSAGE_HPP_ -#define ROSBAG2__REPLAYABLE_MESSAGE_HPP_ +#ifndef ROSBAG2_TRANSPORT__REPLAYABLE_MESSAGE_HPP_ +#define ROSBAG2_TRANSPORT__REPLAYABLE_MESSAGE_HPP_ #include #include -#include "rosbag2_storage/serialized_bag_message.hpp" +#include "rosbag2/types.hpp" -namespace rosbag2 +namespace rosbag2_transport { struct ReplayableMessage { - std::shared_ptr message; + std::shared_ptr message; std::chrono::nanoseconds time_since_start; }; -} // namespace rosbag2 +} // namespace rosbag2_transport -#endif // ROSBAG2__REPLAYABLE_MESSAGE_HPP_ +#endif // ROSBAG2_TRANSPORT__REPLAYABLE_MESSAGE_HPP_ diff --git a/rosbag2/src/rosbag2/rosbag2_node.cpp b/rosbag2_transport/src/rosbag2_transport/rosbag2_node.cpp similarity index 86% rename from rosbag2/src/rosbag2/rosbag2_node.cpp rename to rosbag2_transport/src/rosbag2_transport/rosbag2_node.cpp index a9ab8432a0..17fec3c149 100644 --- a/rosbag2/src/rosbag2/rosbag2_node.cpp +++ b/rosbag2_transport/src/rosbag2_transport/rosbag2_node.cpp @@ -22,10 +22,10 @@ #include #include "rcl/expand_topic_name.h" -#include "rosbag2/logging.hpp" -#include "typesupport_helpers.hpp" +#include "rosbag2_transport/logging.hpp" +#include "rosbag2/typesupport_helpers.hpp" -namespace rosbag2 +namespace rosbag2_transport { Rosbag2Node::Rosbag2Node(const std::string & node_name) @@ -35,7 +35,7 @@ Rosbag2Node::Rosbag2Node(const std::string & node_name) std::shared_ptr Rosbag2Node::create_generic_publisher( const std::string & topic, const std::string & type) { - auto type_support = get_typesupport(type); + auto type_support = rosbag2::get_typesupport(type); return std::make_shared(get_node_base_interface().get(), topic, *type_support); } @@ -44,7 +44,7 @@ std::shared_ptr Rosbag2Node::create_generic_subscription( const std::string & type, std::function)> callback) { - auto type_support = get_typesupport(type); + auto type_support = rosbag2::get_typesupport(type); auto subscription = std::shared_ptr(); @@ -57,7 +57,8 @@ std::shared_ptr Rosbag2Node::create_generic_subscription( get_node_topics_interface()->add_subscription(subscription, nullptr); } catch (const std::runtime_error & ex) { - ROSBAG2_LOG_ERROR_STREAM("Error subscribing to topic '" << topic << "'. Error: " << ex.what()); + ROSBAG2_TRANSPORT_LOG_ERROR_STREAM( + "Error subscribing to topic '" << topic << "'. Error: " << ex.what()); } return subscription; @@ -70,7 +71,7 @@ std::shared_ptr get_initialized_string_map() *substitutions_map = rcutils_get_zero_initialized_string_map(); rcutils_ret_t map_init = rcutils_string_map_init(substitutions_map, 0, allocator); if (map_init != RCUTILS_RET_OK) { - ROSBAG2_LOG_ERROR("Failed to initialize string map within rcutils."); + ROSBAG2_TRANSPORT_LOG_ERROR("Failed to initialize string map within rcutils."); return std::shared_ptr(); } return std::shared_ptr(substitutions_map, @@ -78,7 +79,7 @@ std::shared_ptr get_initialized_string_map() rcl_ret_t cleanup = rcutils_string_map_fini(map); delete map; if (cleanup != RCL_RET_OK) { - ROSBAG2_LOG_ERROR("Failed to deallocate string map when expanding topic."); + ROSBAG2_TRANSPORT_LOG_ERROR("Failed to deallocate string map when expanding topic."); } }); } @@ -88,12 +89,12 @@ std::string Rosbag2Node::expand_topic_name(const std::string & topic_name) rcl_allocator_t allocator = rcl_get_default_allocator(); auto substitutions_map = get_initialized_string_map(); if (!substitutions_map) { - ROSBAG2_LOG_ERROR("Failed to initialize string map within rcutils."); + ROSBAG2_TRANSPORT_LOG_ERROR("Failed to initialize string map within rcutils."); return ""; } rcl_ret_t ret = rcl_get_default_topic_name_substitutions(substitutions_map.get()); if (ret != RCL_RET_OK) { - ROSBAG2_LOG_ERROR("Failed to initialize map with default values."); + ROSBAG2_TRANSPORT_LOG_ERROR("Failed to initialize map with default values."); return ""; } char * expanded_topic_name = nullptr; @@ -106,7 +107,7 @@ std::string Rosbag2Node::expand_topic_name(const std::string & topic_name) &expanded_topic_name); if (ret != RCL_RET_OK) { - ROSBAG2_LOG_ERROR_STREAM( + ROSBAG2_TRANSPORT_LOG_ERROR_STREAM( "Failed to expand topic name " << topic_name << " with error: " << rcutils_get_error_string_safe()); return ""; @@ -159,7 +160,7 @@ std::map Rosbag2Node::filter_topics_with_more_than_one std::map filtered_topics_and_types; for (const auto & topic_and_type : topics_and_types) { if (topic_and_type.second.size() > 1) { - ROSBAG2_LOG_ERROR_STREAM("Topic '" << topic_and_type.first << + ROSBAG2_TRANSPORT_LOG_ERROR_STREAM("Topic '" << topic_and_type.first << "' has several types associated. Only topics with one type are supported"); } else { filtered_topics_and_types.insert({topic_and_type.first, topic_and_type.second[0]}); @@ -168,4 +169,4 @@ std::map Rosbag2Node::filter_topics_with_more_than_one return filtered_topics_and_types; } -} // namespace rosbag2 +} // namespace rosbag2_transport diff --git a/rosbag2/src/rosbag2/rosbag2_node.hpp b/rosbag2_transport/src/rosbag2_transport/rosbag2_node.hpp similarity index 89% rename from rosbag2/src/rosbag2/rosbag2_node.hpp rename to rosbag2_transport/src/rosbag2_transport/rosbag2_node.hpp index efbb5d4c7a..ec41e4d044 100644 --- a/rosbag2/src/rosbag2/rosbag2_node.hpp +++ b/rosbag2_transport/src/rosbag2_transport/rosbag2_node.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ROSBAG2__ROSBAG2_NODE_HPP_ -#define ROSBAG2__ROSBAG2_NODE_HPP_ +#ifndef ROSBAG2_TRANSPORT__ROSBAG2_NODE_HPP_ +#define ROSBAG2_TRANSPORT__ROSBAG2_NODE_HPP_ #include #include @@ -26,7 +26,7 @@ #include "generic_publisher.hpp" #include "generic_subscription.hpp" -namespace rosbag2 +namespace rosbag2_transport { class Rosbag2Node : public rclcpp::Node @@ -54,6 +54,6 @@ class Rosbag2Node : public rclcpp::Node std::map> topics_and_types); }; -} // namespace rosbag2 +} // namespace rosbag2_transport -#endif // ROSBAG2__ROSBAG2_NODE_HPP_ +#endif // ROSBAG2_TRANSPORT__ROSBAG2_NODE_HPP_ diff --git a/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp new file mode 100644 index 0000000000..fc1d59e20c --- /dev/null +++ b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp @@ -0,0 +1,142 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_transport/rosbag2_transport.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "rcl/graph.h" +#include "rclcpp/rclcpp.hpp" +#include "rcutils/time.h" + +#include "rosbag2_transport/logging.hpp" +#include "rosbag2/sequential_reader.hpp" +#include "rosbag2/types.hpp" +#include "rosbag2/typesupport_helpers.hpp" +#include "rosbag2/writer.hpp" + +#include "generic_subscription.hpp" +#include "player.hpp" +#include "replayable_message.hpp" +#include "rosbag2_node.hpp" + +namespace rosbag2_transport +{ + +Rosbag2Transport::Rosbag2Transport() +: reader_(std::make_shared()), + writer_(std::make_shared()) +{} + +Rosbag2Transport::Rosbag2Transport( + std::shared_ptr reader, std::shared_ptr writer) +: reader_(std::move(reader)), writer_(std::move(writer)) {} + +void Rosbag2Transport::init() +{ + rclcpp::init(0, nullptr); +} + +void Rosbag2Transport::shutdown() +{ + rclcpp::shutdown(); +} + +void Rosbag2Transport::record( + const StorageOptions & storage_options, const RecordOptions & record_options) +{ + writer_->open(storage_options); + + auto transport_node = setup_node(); + + auto topics_and_types = record_options.all ? + transport_node->get_all_topics_with_types() : + transport_node->get_topics_with_types(record_options.topics); + + if (topics_and_types.empty()) { + throw std::runtime_error("No topics found. Abort"); + } + + for (const auto & topic_and_type : topics_and_types) { + auto topic_name = topic_and_type.first; + auto topic_type = topic_and_type.second; + + auto subscription = create_subscription(transport_node, topic_name, topic_type); + if (subscription) { + subscriptions_.push_back(subscription); + writer_->create_topic({topic_name, topic_type}); + ROSBAG2_TRANSPORT_LOG_INFO_STREAM("Subscribed to topic '" << topic_name << "'"); + } + } + + if (subscriptions_.empty()) { + throw std::runtime_error("No topics could be subscribed. Abort"); + } + + ROSBAG2_TRANSPORT_LOG_INFO("Subscription setup complete."); + rclcpp::spin(transport_node); + subscriptions_.clear(); +} + +std::shared_ptr Rosbag2Transport::setup_node() +{ + if (!transport_node_) { + transport_node_ = std::make_shared("rosbag2"); + } + return transport_node_; +} + +std::shared_ptr +Rosbag2Transport::create_subscription( + std::shared_ptr & node, + const std::string & topic_name, const std::string & topic_type) const +{ + auto subscription = node->create_generic_subscription( + topic_name, + topic_type, + [this, topic_name](std::shared_ptr message) { + auto bag_message = std::make_shared(); + bag_message->serialized_data = message; + bag_message->topic_name = topic_name; + rcutils_time_point_value_t time_stamp; + int error = rcutils_system_time_now(&time_stamp); + if (error != RCUTILS_RET_OK) { + ROSBAG2_TRANSPORT_LOG_ERROR_STREAM( + "Error getting current time. Error:" << rcutils_get_error_string_safe()); + } + bag_message->time_stamp = time_stamp; + + writer_->write(bag_message); + }); + return subscription; +} + +void Rosbag2Transport::play( + const StorageOptions & storage_options, const PlayOptions & play_options) +{ + reader_->open(storage_options); + + auto transport_node = setup_node(); + + Player player(reader_, transport_node); + player.play(play_options); +} + +} // namespace rosbag2_transport diff --git a/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp new file mode 100644 index 0000000000..438c3d460f --- /dev/null +++ b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp @@ -0,0 +1,134 @@ +// Copyright 2018 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 +#include +#include + +#include "rosbag2_transport/rosbag2_transport.hpp" +#include "rosbag2_transport/record_options.hpp" +#include "rosbag2_transport/storage_options.hpp" + +static PyObject * +rosbag2_transport_record(PyObject * Py_UNUSED(self), PyObject * args, PyObject * kwargs) +{ + rosbag2_transport::StorageOptions storage_options{}; + rosbag2_transport::RecordOptions record_options{}; + + static const char * kwlist[] = {"uri", "storage_id", "all", "topics", nullptr}; + + char * uri = nullptr; + char * storage_id = nullptr; + bool all = false; + PyObject * topics = nullptr; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|bO", const_cast(kwlist), + &uri, + &storage_id, + &all, + &topics)) + { + return nullptr; + } + + storage_options.uri = std::string(uri); + storage_options.storage_id = std::string(storage_id); + record_options.all = all; + + if (topics) { + PyObject * topic_iterator = PyObject_GetIter(topics); + if (topic_iterator != nullptr) { + PyObject * topic; + while ((topic = PyIter_Next(topic_iterator))) { + record_options.topics.emplace_back(PyUnicode_AsUTF8(topic)); + + Py_DECREF(topic); + } + Py_DECREF(topic_iterator); + } + } + + rosbag2_transport::Rosbag2Transport transport; + transport.init(); + transport.record(storage_options, record_options); + transport.shutdown(); + + Py_RETURN_NONE; +} + +static PyObject * +rosbag2_transport_play(PyObject * Py_UNUSED(self), PyObject * args, PyObject * kwargs) +{ + rosbag2_transport::PlayOptions play_options{}; + rosbag2_transport::StorageOptions storage_options{}; + + static const char * kwlist[] = {"uri", "storage_id", "read_ahead_queue_size", nullptr}; + + char * uri; + char * storage_id; + size_t read_ahead_queue_size; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|k", const_cast(kwlist), + &uri, + &storage_id, + &read_ahead_queue_size)) + { + return nullptr; + } + + storage_options.uri = std::string(uri); + storage_options.storage_id = std::string(storage_id); + + play_options.read_ahead_queue_size = read_ahead_queue_size ? read_ahead_queue_size : 1000; + + rosbag2_transport::Rosbag2Transport transport; + transport.init(); + transport.play(storage_options, play_options); + transport.shutdown(); + + Py_RETURN_NONE; +} + +/// Define the public methods of this module +static PyMethodDef rosbag2_transport_methods[] = { + { + "record", reinterpret_cast(rosbag2_transport_record), METH_VARARGS | METH_KEYWORDS, + "Record to bag" + }, + { + "play", reinterpret_cast(rosbag2_transport_play), METH_VARARGS | METH_KEYWORDS, + "Play bag" + }, + {nullptr, nullptr, 0, nullptr} /* sentinel */ +}; + +PyDoc_STRVAR(rosbag2_transport__doc__, + "Python module for rosbag2 transport"); + +/// Define the Python module +static struct PyModuleDef _rosbag2_transport_module = { + PyModuleDef_HEAD_INIT, + "_rosbag2_transport", + rosbag2_transport__doc__, + -1, /* -1 means that the module keeps state in global variables */ + rosbag2_transport_methods, + nullptr, + nullptr, + nullptr, + nullptr +}; + +/// Init function of this module +PyMODINIT_FUNC PyInit__rosbag2_transport_py(void) +{ + return PyModule_Create(&_rosbag2_transport_module); +} diff --git a/rosbag2_transport/test/rosbag2_transport/mock_sequential_reader.hpp b/rosbag2_transport/test/rosbag2_transport/mock_sequential_reader.hpp new file mode 100644 index 0000000000..5cf69924a9 --- /dev/null +++ b/rosbag2_transport/test/rosbag2_transport/mock_sequential_reader.hpp @@ -0,0 +1,61 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__MOCK_SEQUENTIAL_READER_HPP_ +#define ROSBAG2_TRANSPORT__MOCK_SEQUENTIAL_READER_HPP_ + +#include +#include +#include + +#include "rosbag2/sequential_reader.hpp" + +class MockSequentialReader : public rosbag2::SequentialReader +{ +public: + void open(const rosbag2::StorageOptions & options) override + { + (void) options; + } + + bool has_next() override + { + return num_read_ < messages_.size(); + } + + std::shared_ptr read_next() override + { + return messages_[num_read_++]; + } + + std::vector get_all_topics_and_types() override + { + return topics_; + } + + void prepare( + std::vector> messages, + std::vector topics) + { + messages_ = std::move(messages); + topics_ = std::move(topics); + } + +private: + std::vector> messages_; + std::vector topics_; + size_t num_read_; +}; + +#endif // ROSBAG2_TRANSPORT__MOCK_SEQUENTIAL_READER_HPP_ diff --git a/rosbag2_transport/test/rosbag2_transport/mock_writer.hpp b/rosbag2_transport/test/rosbag2_transport/mock_writer.hpp new file mode 100644 index 0000000000..607942b77b --- /dev/null +++ b/rosbag2_transport/test/rosbag2_transport/mock_writer.hpp @@ -0,0 +1,65 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__MOCK_WRITER_HPP_ +#define ROSBAG2_TRANSPORT__MOCK_WRITER_HPP_ + +#include +#include +#include +#include + +#include "rosbag2/writer.hpp" + +class MockWriter : public rosbag2::Writer +{ +public: + void open(const rosbag2::StorageOptions & options) override + { + (void) options; + } + + void create_topic(const rosbag2::TopicWithType & topic_with_type) override + { + topics_.emplace(topic_with_type.name, topic_with_type); + } + + void write(std::shared_ptr message) override + { + messages_.push_back(message); + messages_per_topic_[message->topic_name] += 1; + } + + std::vector> get_messages() + { + return messages_; + } + + std::map messages_per_topic() + { + return messages_per_topic_; + } + + std::map get_topics() + { + return topics_; + } + +private: + std::map topics_; + std::vector> messages_; + std::map messages_per_topic_; +}; + +#endif // ROSBAG2_TRANSPORT__MOCK_WRITER_HPP_ diff --git a/rosbag2_transport/test/rosbag2_transport/record_integration_fixture.hpp b/rosbag2_transport/test/rosbag2_transport/record_integration_fixture.hpp new file mode 100644 index 0000000000..1c40694398 --- /dev/null +++ b/rosbag2_transport/test/rosbag2_transport/record_integration_fixture.hpp @@ -0,0 +1,92 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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 + +#include +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "rosbag2/types.hpp" +#include "rosbag2_transport/record_options.hpp" +#include "rosbag2_transport/rosbag2_transport.hpp" +#include "rosbag2_test_common/memory_management.hpp" +#include "rosbag2_test_common/publisher_manager.hpp" + +#include "rosbag2_transport_test_fixture.hpp" + +using namespace ::testing; // NOLINT +using namespace rosbag2_transport; // NOLINT +using namespace std::chrono_literals; // NOLINT + +#ifndef ROSBAG2_TRANSPORT__RECORD_INTEGRATION_FIXTURE_HPP_ +#define ROSBAG2_TRANSPORT__RECORD_INTEGRATION_FIXTURE_HPP_ + +class RecordIntegrationTestFixture : public Rosbag2TransportTestFixture +{ +public: + RecordIntegrationTestFixture() + : Rosbag2TransportTestFixture() + { + rclcpp::init(0, nullptr); + } + + void start_recording(const RecordOptions & options) + { + // the future object returned from std::async needs to be stored not to block the execution + future_ = std::async( + std::launch::async, [this, options]() { + rosbag2_transport::Rosbag2Transport rosbag2_transport(reader_, writer_); + rosbag2_transport.record(storage_options_, options); + }); + } + + void stop_recording() + { + rclcpp::shutdown(); + future_.get(); + } + + void run_publishers() + { + pub_man_.run_publishers([this](const std::string & topic_name) { + return writer_->messages_per_topic()[topic_name]; + }); + } + + template + std::vector> filter_messages( + std::vector> messages, + const std::string & topic) + { + std::vector> filtered_messages; + for (const auto & message : messages) { + if (message->topic_name == topic) { + filtered_messages.push_back( + memory_.deserialize_message(message->serialized_data)); + } + } + return filtered_messages; + } + + MemoryManagement memory_; + PublisherManager pub_man_; + std::future future_; +}; + +#endif // ROSBAG2_TRANSPORT__RECORD_INTEGRATION_FIXTURE_HPP_ diff --git a/rosbag2_transport/test/rosbag2_transport/rosbag2_transport_test_fixture.hpp b/rosbag2_transport/test/rosbag2_transport/rosbag2_transport_test_fixture.hpp new file mode 100644 index 0000000000..58f52deb20 --- /dev/null +++ b/rosbag2_transport/test/rosbag2_transport/rosbag2_transport_test_fixture.hpp @@ -0,0 +1,86 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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_TRANSPORT__ROSBAG2_TRANSPORT_TEST_FIXTURE_HPP_ +#define ROSBAG2_TRANSPORT__ROSBAG2_TRANSPORT_TEST_FIXTURE_HPP_ + +#include + +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" + +#ifdef _WIN32 +# include +# include +#endif + +#include "rosbag2_transport/play_options.hpp" +#include "rosbag2_transport/storage_options.hpp" +#include "rosbag2/sequential_reader.hpp" +#include "rosbag2/types.hpp" +#include "rosbag2/writer.hpp" +#include "rosbag2_test_common/memory_management.hpp" +#include "mock_sequential_reader.hpp" +#include "mock_writer.hpp" + +using namespace ::testing; // NOLINT +using namespace rosbag2_test_common; // NOLINT + +inline char separator() +{ +#ifdef _WIN32 + return '\\'; +#else + return '/'; +#endif +} + +class Rosbag2TransportTestFixture : public Test +{ +public: + Rosbag2TransportTestFixture() + : storage_options_({"uri", "storage_id"}), play_options_({1000}), + reader_(std::make_shared()), + writer_(std::make_shared()) {} + + template + std::shared_ptr + serialize_test_message( + const std::string & topic, + int64_t milliseconds, + std::shared_ptr message) + { + auto bag_msg = std::make_shared(); + bag_msg->serialized_data = memory_management_.serialize_message(message); + bag_msg->time_stamp = milliseconds * 1000000; + bag_msg->topic_name = topic; + + return bag_msg; + } + + MemoryManagement memory_management_; + + rosbag2_transport::StorageOptions storage_options_; + rosbag2_transport::PlayOptions play_options_; + + std::shared_ptr reader_; + std::shared_ptr writer_; +}; + +#endif // ROSBAG2_TRANSPORT__ROSBAG2_TRANSPORT_TEST_FIXTURE_HPP_ diff --git a/rosbag2_transport/test/rosbag2_transport/test_play.cpp b/rosbag2_transport/test/rosbag2_transport/test_play.cpp new file mode 100644 index 0000000000..0e16bb559e --- /dev/null +++ b/rosbag2_transport/test/rosbag2_transport/test_play.cpp @@ -0,0 +1,108 @@ +// Copyright 2018, Bosch Software Innovations GmbH. +// +// 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 + +#include +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "rosbag2_transport/rosbag2_transport.hpp" +#include "test_msgs/msg/primitives.hpp" +#include "test_msgs/msg/static_array_primitives.hpp" +#include "test_msgs/message_fixtures.hpp" + +#include "rosbag2_transport_test_fixture.hpp" +#include "rosbag2_test_common/subscription_manager.hpp" + +using namespace ::testing; // NOLINT +using namespace rosbag2_transport; // NOLINT +using namespace std::chrono_literals; // NOLINT +using namespace rosbag2_test_common; // NOLINT + +class RosBag2PlayTestFixture : public Rosbag2TransportTestFixture +{ +public: + RosBag2PlayTestFixture() + : Rosbag2TransportTestFixture() + { + rclcpp::init(0, nullptr); + sub_ = std::make_shared(); + } + + ~RosBag2PlayTestFixture() override + { + rclcpp::shutdown(); + } + + std::shared_ptr sub_; +}; + +TEST_F(RosBag2PlayTestFixture, recorded_messages_are_played_for_all_topics) +{ + auto primitive_message1 = get_messages_primitives()[0]; + primitive_message1->string_value = "Hello World"; + + auto complex_message1 = get_messages_static_array_primitives()[0]; + complex_message1->string_values = {{"Complex Hello1", "Complex Hello2", "Complex Hello3"}}; + complex_message1->bool_values = {{true, false, true}}; + + auto topic_types = std::vector{ + {"topic1", "test_msgs/Primitives"}, + {"topic2", "test_msgs/StaticArrayPrimitives"}, + }; + + std::vector> messages = + {serialize_test_message("topic1", 0, primitive_message1), + serialize_test_message("topic1", 100, primitive_message1), + serialize_test_message("topic1", 200, primitive_message1), + serialize_test_message("topic2", 50, complex_message1), + serialize_test_message("topic2", 150, complex_message1), + serialize_test_message("topic2", 250, complex_message1)}; + + reader_->prepare(messages, topic_types); + + // Due to a problem related to the subscriber, we play many (3) messages but make the subscriber + // node spin only until 2 have arrived. Hence the 2 as `launch_subscriber()` argument. + sub_->add_subscription("/topic1", 2); + sub_->add_subscription( + "/topic2", 2); + + auto await_received_messages = sub_->spin_subscriptions(); + + Rosbag2Transport rosbag2_transport(reader_, writer_); + rosbag2_transport.play(storage_options_, play_options_); + + await_received_messages.get(); + + auto replayed_test_primitives = sub_->get_received_messages( + "/topic1"); + EXPECT_THAT(replayed_test_primitives, SizeIs(Ge(2u))); + EXPECT_THAT(replayed_test_primitives, + Each(Pointee(Field(&test_msgs::msg::Primitives::string_value, "Hello World")))); + + auto replayed_test_arrays = sub_->get_received_messages( + "/topic2"); + EXPECT_THAT(replayed_test_arrays, SizeIs(Ge(2u))); + EXPECT_THAT(replayed_test_arrays, + Each(Pointee(Field(&test_msgs::msg::StaticArrayPrimitives::bool_values, + ElementsAre(true, false, true))))); + EXPECT_THAT(replayed_test_arrays, + Each(Pointee(Field(&test_msgs::msg::StaticArrayPrimitives::string_values, + ElementsAre("Complex Hello1", "Complex Hello2", "Complex Hello3"))))); +} diff --git a/rosbag2/test/rosbag2/rosbag2_play_timing_integration_test.cpp b/rosbag2_transport/test/rosbag2_transport/test_play_timing.cpp similarity index 71% rename from rosbag2/test/rosbag2/rosbag2_play_timing_integration_test.cpp rename to rosbag2_transport/test/rosbag2_transport/test_play_timing.cpp index 94ecdd817b..862bc2d4eb 100644 --- a/rosbag2/test/rosbag2/rosbag2_play_timing_integration_test.cpp +++ b/rosbag2_transport/test/rosbag2_transport/test_play_timing.cpp @@ -21,16 +21,15 @@ #include #include "rclcpp/rclcpp.hpp" -#include "rosbag2/rosbag2.hpp" -#include "rosbag2_test_fixture.hpp" +#include "rosbag2_transport/rosbag2_transport.hpp" +#include "rosbag2_transport_test_fixture.hpp" #include "test_msgs/msg/primitives.hpp" #include "test_msgs/message_fixtures.hpp" - using namespace ::testing; // NOLINT -using namespace rosbag2; // NOLINT +using namespace rosbag2_transport; // NOLINT -TEST_F(Rosbag2TestFixture, playing_respects_relative_timing_of_stored_messages) +TEST_F(Rosbag2TransportTestFixture, playing_respects_relative_timing_of_stored_messages) { rclcpp::init(0, nullptr); auto primitive_message1 = get_messages_primitives()[0]; @@ -40,22 +39,23 @@ TEST_F(Rosbag2TestFixture, playing_respects_relative_timing_of_stored_messages) primitive_message2->string_value = "Hello World 2"; auto message_time_difference = std::chrono::seconds(1); - auto topics_and_types = std::map{{"topic1", "test_msgs/Primitives"}}; - std::vector> messages = - {serialize_test_message("topic1", primitive_message1), - serialize_test_message("topic1", primitive_message2)}; + auto topics_and_types = std::vector{{"topic1", "test_msgs/Primitives"}}; + std::vector> messages = + {serialize_test_message("topic1", 0, primitive_message1), + serialize_test_message("topic1", 0, primitive_message2)}; messages[0]->time_stamp = 100; messages[1]->time_stamp = messages[0]->time_stamp + std::chrono::nanoseconds(message_time_difference).count(); - write_messages(database_name_, messages, topics_and_types); + + reader_->prepare(messages, topics_and_types); // We can only assert indirectly that the relative times are respected when playing a bag. So // we check that time elapsed during playing is at least the time difference between the two // messages auto start = std::chrono::steady_clock::now(); - Rosbag2 rosbag2; - rosbag2.play(database_name_, options_); + Rosbag2Transport rosbag2_transport(reader_, writer_); + rosbag2_transport.play(storage_options_, play_options_); auto replay_time = std::chrono::steady_clock::now() - start; ASSERT_THAT(replay_time, Gt(message_time_difference)); diff --git a/rosbag2/test/rosbag2/rosbag2_record_all_integration_test.cpp b/rosbag2_transport/test/rosbag2_transport/test_record.cpp similarity index 68% rename from rosbag2/test/rosbag2/rosbag2_record_all_integration_test.cpp rename to rosbag2_transport/test/rosbag2_transport/test_record.cpp index b23af19fff..e33f6e1595 100644 --- a/rosbag2/test/rosbag2/rosbag2_record_all_integration_test.cpp +++ b/rosbag2_transport/test/rosbag2_transport/test_record.cpp @@ -19,37 +19,34 @@ #include #include "rclcpp/rclcpp.hpp" -#include "rosbag2/rosbag2.hpp" -#include "rosbag2_record_integration_fixture.hpp" -#include "rosbag2_storage/serialized_bag_message.hpp" +#include "record_integration_fixture.hpp" +#include "rosbag2_transport/rosbag2_transport.hpp" +#include "rosbag2/types.hpp" #include "test_msgs/msg/primitives.hpp" #include "test_msgs/msg/static_array_primitives.hpp" #include "test_msgs/message_fixtures.hpp" -#include "test_memory_management.hpp" -// TODO(Martin-Idel-SI): merge with other write and read tests once signal handling is sorted out -TEST_F(RosBag2RecordIntegrationTestFixture, published_messages_from_multiple_topics_are_recorded) +TEST_F(RecordIntegrationTestFixture, published_messages_from_multiple_topics_are_recorded) { auto array_message = get_messages_static_array_primitives()[0]; array_message->string_values = {{"Complex Hello1", "Complex Hello2", "Complex Hello3"}}; array_message->bool_values = {{true, false, true}}; std::string array_topic = "/array_topic"; - auto serialized_array_bag_message = serialize_test_message(array_topic, array_message); auto string_message = get_messages_primitives()[0]; string_message->string_value = "Hello World"; std::string string_topic = "/string_topic"; - auto serialized_string_bag_message = serialize_test_message(string_topic, string_message); - start_publishing(serialized_string_bag_message, string_topic, 2); - start_publishing( - serialized_array_bag_message, array_topic, 2); + pub_man_.add_publisher( + string_topic, string_message, 2); + pub_man_.add_publisher( + array_topic, array_message, 2); - start_recording_all_topics(); - wait_for_publishers_to_stop(); + start_recording({false, {string_topic, array_topic}}); + run_publishers(); stop_recording(); - auto recorded_messages = get_messages(database_name_); + auto recorded_messages = writer_->get_messages(); ASSERT_THAT(recorded_messages, SizeIs(4)); auto string_messages = filter_messages( diff --git a/rosbag2/test/rosbag2/rosbag2_record_integration_test.cpp b/rosbag2_transport/test/rosbag2_transport/test_record_all.cpp similarity index 68% rename from rosbag2/test/rosbag2/rosbag2_record_integration_test.cpp rename to rosbag2_transport/test/rosbag2_transport/test_record_all.cpp index 6fe7770fb7..3dca120275 100644 --- a/rosbag2/test/rosbag2/rosbag2_record_integration_test.cpp +++ b/rosbag2_transport/test/rosbag2_transport/test_record_all.cpp @@ -19,37 +19,32 @@ #include #include "rclcpp/rclcpp.hpp" -#include "rosbag2/rosbag2.hpp" -#include "rosbag2_record_integration_fixture.hpp" -#include "rosbag2_storage/serialized_bag_message.hpp" -#include "test_memory_management.hpp" +#include "record_integration_fixture.hpp" +#include "rosbag2_transport/rosbag2_transport.hpp" +#include "rosbag2/types.hpp" #include "test_msgs/msg/primitives.hpp" #include "test_msgs/msg/static_array_primitives.hpp" #include "test_msgs/message_fixtures.hpp" -// TODO(Martin-Idel-SI): merge with other write and read tests once signal handling is sorted out -TEST_F(RosBag2RecordIntegrationTestFixture, published_messages_from_multiple_topics_are_recorded) +TEST_F(RecordIntegrationTestFixture, published_messages_from_multiple_topics_are_recorded) { auto array_message = get_messages_static_array_primitives()[0]; array_message->string_values = {{"Complex Hello1", "Complex Hello2", "Complex Hello3"}}; array_message->bool_values = {{true, false, true}}; std::string array_topic = "/array_topic"; - auto serialized_array_bag_message = serialize_test_message(array_topic, array_message); auto string_message = get_messages_primitives()[0]; string_message->string_value = "Hello World"; std::string string_topic = "/string_topic"; - auto serialized_string_bag_message = serialize_test_message(string_topic, string_message); - start_publishing(serialized_string_bag_message, string_topic, 2); - start_publishing( - serialized_array_bag_message, array_topic, 2); + pub_man_.add_publisher(string_topic, string_message, 2); + pub_man_.add_publisher(array_topic, array_message, 2); - start_recording({string_topic, array_topic}); - wait_for_publishers_to_stop(); + start_recording({true, {}}); + run_publishers(); stop_recording(); - auto recorded_messages = get_messages(database_name_); + auto recorded_messages = writer_->get_messages(); ASSERT_THAT(recorded_messages, SizeIs(4)); auto string_messages = filter_messages( diff --git a/rosbag2/test/rosbag2/rosbag2_rosbag_node_test.cpp b/rosbag2_transport/test/rosbag2_transport/test_rosbag2_node.cpp similarity index 89% rename from rosbag2/test/rosbag2/rosbag2_rosbag_node_test.cpp rename to rosbag2_transport/test/rosbag2_transport/test_rosbag2_node.cpp index 0bef371517..2b0167c3c7 100644 --- a/rosbag2/test/rosbag2/rosbag2_rosbag_node_test.cpp +++ b/rosbag2_transport/test/rosbag2_transport/test_rosbag2_node.cpp @@ -21,20 +21,20 @@ #include #include "rclcpp/rclcpp.hpp" -#include "test_memory_management.hpp" +#include "rosbag2_test_common/memory_management.hpp" #include "test_msgs/message_fixtures.hpp" #include "test_msgs/msg/primitives.hpp" -#include "../../src/rosbag2/rosbag2_node.hpp" -#include "../../src/rosbag2/typesupport_helpers.hpp" +#include "../../src/rosbag2_transport/rosbag2_node.hpp" using namespace ::testing; // NOLINT +using namespace rosbag2_test_common; // NOLINT class RosBag2NodeFixture : public Test { public: RosBag2NodeFixture() { - node_ = std::make_shared("rosbag2"); + node_ = std::make_shared("rosbag2"); publisher_node_ = std::make_shared("publisher_node"); } @@ -80,8 +80,8 @@ class RosBag2NodeFixture : public Test return memory_management_.serialize_message(string_message); } - test_helpers::TestMemoryManagement memory_management_; - std::shared_ptr node_; + MemoryManagement memory_management_; + std::shared_ptr node_; rclcpp::Node::SharedPtr publisher_node_; std::vector> publishers_; }; @@ -169,12 +169,8 @@ TEST_F(RosBag2NodeFixture, get_all_topics_with_types_returns_all_topics) auto topics_and_types = node_->get_all_topics_with_types(); - ASSERT_THAT(topics_and_types, SizeIs(5)); + ASSERT_THAT(topics_and_types, SizeIs(Ge(3u))); EXPECT_THAT(topics_and_types.find(first_topic)->second, StrEq("test_msgs/Primitives")); EXPECT_THAT(topics_and_types.find(second_topic)->second, StrEq("test_msgs/Primitives")); EXPECT_THAT(topics_and_types.find(third_topic)->second, StrEq("test_msgs/Primitives")); - // The latter two topics can usually be found on any node, so they should be subscribed here - EXPECT_THAT(topics_and_types.find("/clock")->second, StrEq("rosgraph_msgs/Clock")); - EXPECT_THAT( - topics_and_types.find("/parameter_events")->second, StrEq("rcl_interfaces/ParameterEvent")); }