diff --git a/rosidl_buffer_backend_registry/CMakeLists.txt b/rosidl_buffer_backend_registry/CMakeLists.txt new file mode 100644 index 000000000..342c39d85 --- /dev/null +++ b/rosidl_buffer_backend_registry/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.5) +project(rosidl_buffer_backend_registry) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# Find dependencies +find_package(ament_cmake REQUIRED) +find_package(rosidl_buffer REQUIRED) +find_package(rosidl_buffer_backend REQUIRED) +find_package(rmw REQUIRED) +find_package(rosidl_runtime_cpp REQUIRED) +find_package(pluginlib REQUIRED) + +# Library +add_library(${PROJECT_NAME} SHARED + src/buffer_backend_registry.cpp +) + +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) + +target_compile_definitions(${PROJECT_NAME} + PRIVATE "ROSIDL_BUFFER_BACKEND_REGISTRY_BUILDING_DLL" +) + +target_link_libraries(${PROJECT_NAME} PUBLIC + rosidl_buffer_backend::rosidl_buffer_backend + rmw::rmw + rosidl_runtime_cpp::rosidl_runtime_cpp + pluginlib::pluginlib +) + +# Install headers +install( + DIRECTORY include/ + DESTINATION include/${PROJECT_NAME} +) + +# Install library +install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include/${PROJECT_NAME} +) + +# Export dependencies +ament_export_targets(${PROJECT_NAME} HAS_LIBRARY_TARGET) +ament_export_dependencies(rosidl_buffer_backend rmw rosidl_runtime_cpp pluginlib) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + + find_package(ament_cmake_gtest REQUIRED) + + # Test backend registry and dummy backend (direct instantiation, not via pluginlib) + ament_add_gtest(test_buffer_backend_registry + test/test_buffer_backend_registry.cpp + ) + if(TARGET test_buffer_backend_registry) + target_include_directories(test_buffer_backend_registry PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/test + ) + target_link_libraries(test_buffer_backend_registry + ${PROJECT_NAME} + rosidl_buffer::rosidl_buffer + rosidl_buffer_backend::rosidl_buffer_backend + rmw::rmw + rosidl_runtime_cpp::rosidl_runtime_cpp + pluginlib::pluginlib + ) + endif() +endif() + +ament_package() + diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp new file mode 100644 index 000000000..cc328a9ec --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp @@ -0,0 +1,127 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BACKEND_UTILS_HPP_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__BACKEND_UTILS_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rosidl_buffer_backend/buffer_backend.hpp" +#include "rmw/topic_endpoint_info.h" + +namespace rosidl_buffer_backend_registry +{ + +/// Collect metadata strings from every loaded backend instance. +/// @param[in] backend_instances Map of backend name to backend instance. +/// @return Map of backend name to its metadata string (null backends are skipped). +inline std::unordered_map get_all_backend_metadata( + const std::unordered_map> & backend_instances) +{ + std::unordered_map backend_metadata; + for (const auto & [backend_name, backend] : backend_instances) { + if (backend) { + backend_metadata[backend_name] = backend->get_backend_metadata(); + } + } + return backend_metadata; +} + +/// Notify every loaded backend that a local endpoint has been created. +/// @param[in] backend_instances Map of backend name to backend instance. +/// @param[in] endpoint_info Endpoint that was just created. +inline void notify_endpoint_created( + const std::unordered_map> & backend_instances, + const rmw_topic_endpoint_info_t & endpoint_info) +{ + for (const auto & [_, backend] : backend_instances) { + if (backend) { + backend->on_creating_endpoint(endpoint_info); + } + } +} + +/// Notify every loaded backend that a remote endpoint has been discovered and +/// collect compatibility / grouping results. +/// @param[in] backend_instances Map of backend name to backend instance. +/// @param[in] endpoint_info Information about the discovered endpoint. +/// @param[in] existing_endpoints Endpoints already known on this topic. +/// @param[out] backend_endpoint_groups Updated per-backend endpoint GID-hash +/// groupings (cleared to empty for null backends). +/// @param[in] endpoint_supported_backends Backend-type-to-metadata map +/// advertised by the discovered endpoint. +/// @return Map of backend name to compatibility flag (false for null backends). +inline std::unordered_map notify_endpoint_discovered( + const std::unordered_map> & backend_instances, + const rmw_topic_endpoint_info_t & endpoint_info, + const std::vector & existing_endpoints, + std::unordered_map>> & backend_endpoint_groups, + const std::unordered_map & endpoint_supported_backends) +{ + std::unordered_map backend_compatibility; + for (const auto & [backend_name, backend] : backend_instances) { + if (!backend) { + backend_compatibility[backend_name] = false; + backend_endpoint_groups[backend_name] = {}; + continue; + } + auto result = backend->on_discovering_endpoint( + endpoint_info, existing_endpoints, endpoint_supported_backends); + backend_compatibility[backend_name] = result.first; + backend_endpoint_groups[backend_name] = std::move(result.second); + } + return backend_compatibility; +} + +/// Check whether two backend name lists share at least one common entry. +/// @return true if any backend name appears in both \p a and \p b. +inline bool backends_compatible( + const std::vector & a, + const std::vector & b) +{ + for (const auto & entry : a) { + if (std::find(b.begin(), b.end(), entry) != b.end()) { + return true; + } + } + return false; +} + +/// Return the de-duplicated intersection of two backend name lists, +/// preserving the order in which names appear in \p a. +inline std::vector get_common_backends( + const std::vector & a, + const std::vector & b) +{ + std::vector common; + for (const auto & entry : a) { + if (std::find(b.begin(), b.end(), entry) != b.end() && + std::find(common.begin(), common.end(), entry) == common.end()) + { + common.push_back(entry); + } + } + return common; +} + +} // namespace rosidl_buffer_backend_registry + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__BACKEND_UTILS_HPP_ diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp new file mode 100644 index 000000000..d8ac3ab50 --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp @@ -0,0 +1,71 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ + +#include +#include +#include + +#include "rosidl_buffer_backend/buffer_backend.hpp" +#include "rosidl_buffer_backend_registry/visibility_control.h" + +// Forward declare pluginlib ClassLoader to avoid header dependency +namespace pluginlib +{ +template +class ClassLoader; +} // namespace pluginlib + +namespace rosidl_buffer_backend_registry +{ + +/// Registry for discovering and managing buffer backend plugins. +/// Uses pluginlib for dynamic plugin discovery and loading. +class BufferBackendRegistry +{ +public: + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + BufferBackendRegistry(); + + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + ~BufferBackendRegistry(); + + /// Create a backend instance by plugin class name. + /// Backends loaded through pluginlib are instantiated per call. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::shared_ptr create_backend_instance(const std::string & name); + + /// Get names of all registered backends. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::vector get_backend_names() const; + + // Non-copyable, non-movable + BufferBackendRegistry(const BufferBackendRegistry &) = delete; + BufferBackendRegistry & operator=(const BufferBackendRegistry &) = delete; + BufferBackendRegistry(BufferBackendRegistry &&) = delete; + BufferBackendRegistry & operator=(BufferBackendRegistry &&) = delete; + +private: + /// Query pluginlib for declared backend classes and populate plugin_backend_classes_. + void load_plugins(); + + std::vector plugin_backend_classes_; + std::unique_ptr> loader_; +}; + +} // namespace rosidl_buffer_backend_registry + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h new file mode 100644 index 000000000..5c53f070d --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h @@ -0,0 +1,58 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__VISIBILITY_CONTROL_H_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__VISIBILITY_CONTROL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// 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 ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT __attribute__((dllexport)) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT __attribute__((dllimport)) + #else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT __declspec(dllexport) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT __declspec(dllimport) + #endif + #ifdef ROSIDL_BUFFER_BACKEND_REGISTRY_BUILDING_DLL + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT + #else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT + #endif + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC_TYPE ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + #define ROSIDL_BUFFER_BACKEND_REGISTRY_LOCAL +#else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT __attribute__((visibility("default"))) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT + #if __GNUC__ >= 4 + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC __attribute__((visibility("default"))) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_LOCAL __attribute__((visibility("hidden"))) + #else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + #define ROSIDL_BUFFER_BACKEND_REGISTRY_LOCAL + #endif + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__VISIBILITY_CONTROL_H_ diff --git a/rosidl_buffer_backend_registry/package.xml b/rosidl_buffer_backend_registry/package.xml new file mode 100644 index 000000000..27d392275 --- /dev/null +++ b/rosidl_buffer_backend_registry/package.xml @@ -0,0 +1,29 @@ + + + + rosidl_buffer_backend_registry + 5.1.2 + Backend discovery and plugin loading for ROS2 buffer types + + CY Chen + + Apache License 2.0 + + CY Chen + + ament_cmake + + rosidl_buffer_backend + rmw + rosidl_runtime_cpp + pluginlib + + ament_lint_auto + ament_lint_common + rosidl_buffer + + + ament_cmake + + + diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp new file mode 100644 index 000000000..7f9037616 --- /dev/null +++ b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp @@ -0,0 +1,112 @@ +// Copyright 2026 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 "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" + +#include +#include + +#include +#include "rcutils/logging_macros.h" + +namespace rosidl_buffer_backend_registry +{ + +BufferBackendRegistry::BufferBackendRegistry() +{ + try { + loader_ = std::make_unique>( + "rosidl_buffer_backend", + "rosidl::BufferBackend"); + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Failed to create ClassLoader: %s", e.what()); + loader_ = nullptr; + } + load_plugins(); +} + +BufferBackendRegistry::~BufferBackendRegistry() = default; + +void BufferBackendRegistry::load_plugins() +{ + if (!loader_) { + return; + } + + try { + auto declared_classes = loader_->getDeclaredClasses(); + if (declared_classes.empty()) { + RCUTILS_LOG_DEBUG_NAMED( + "rosidl_buffer_backend_registry", + "No buffer backend plugins found"); + } else { + RCUTILS_LOG_DEBUG_NAMED( + "rosidl_buffer_backend_registry", + "Discovered %zu buffer backend plugin(s)", + declared_classes.size()); + for (const auto & class_name : declared_classes) { + plugin_backend_classes_.push_back(class_name); + RCUTILS_LOG_DEBUG_NAMED( + "rosidl_buffer_backend_registry", + "Discovered buffer backend plugin class: %s", + class_name.c_str()); + } + } + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Buffer backend plugin discovery error: %s", + e.what()); + } +} + +std::shared_ptr BufferBackendRegistry::create_backend_instance( + const std::string & name) +{ + if (!loader_) { + return nullptr; + } + + auto plugin_it = std::find( + plugin_backend_classes_.begin(), plugin_backend_classes_.end(), name); + if (plugin_it == plugin_backend_classes_.end()) { + return nullptr; + } + + try { + return loader_->createSharedInstance(name); + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Failed to instantiate backend '%s': %s", + name.c_str(), e.what()); + return nullptr; + } +} + +std::vector BufferBackendRegistry::get_backend_names() const +{ + std::vector names; + names.reserve(plugin_backend_classes_.size()); + for (const auto & class_name : plugin_backend_classes_) { + if (std::find(names.begin(), names.end(), class_name) == names.end()) { + names.push_back(class_name); + } + } + return names; +} + +} // namespace rosidl_buffer_backend_registry diff --git a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp new file mode 100644 index 000000000..90081190a --- /dev/null +++ b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp @@ -0,0 +1,133 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DUMMY_BUFFER_BACKEND_HPP_ +#define DUMMY_BUFFER_BACKEND_HPP_ + +#include +#include +#include +#include + +#include "rosidl_buffer_backend/buffer_backend.hpp" +#include "rosidl_buffer/buffer_impl_base.hpp" +#include "rosidl_buffer/cpu_buffer_impl.hpp" + +// For pluginlib export +#include + +namespace rosidl_buffer_backend_registry +{ +namespace test +{ + +/// Dummy backend implementation for testing. +/// Acts like CPU backend but adds a marker to verify it's being used. +template +class DummyBufferImpl : public rosidl::BufferImplBase +{ +public: + DummyBufferImpl() + : marker_(0xDEADBEEF) {} + + explicit DummyBufferImpl(size_t size) + : marker_(0xDEADBEEF) + { + data_.resize(size); + } + + std::string get_backend_type() const override {return "dummy";} + + size_t size() const override {return data_.size();} + + std::unique_ptr> to_cpu() const override + { + auto cpu = std::make_unique>(); + cpu->get_storage() = data_; + return cpu; + } + + std::unique_ptr> clone() const override + { + auto cloned = std::make_unique>(); + cloned->data_ = data_; + cloned->marker_ = marker_; + return cloned; + } + + std::vector & get_data() {return data_;} + const std::vector & get_data() const {return data_;} + + uint32_t get_marker() const {return marker_;} + +private: + std::vector data_; + uint32_t marker_; // Marker to identify this backend +}; + +/// Dummy backend for testing the BufferBackend interface. +/// Implements all 4+4 methods with test markers. +class DummyBufferBackend : public rosidl::BufferBackend +{ +public: + DummyBufferBackend() + : call_count_(0) {} + + // Track which methods were called + mutable size_t call_count_; + mutable std::string last_method_; + + std::string get_backend_type() const override + { + return "dummy"; + } + + const rosidl_message_type_support_t * get_descriptor_type_support() const override + { + return nullptr; + } + + std::shared_ptr create_empty_descriptor() const override + { + return nullptr; + } + + std::shared_ptr create_descriptor_with_endpoint( + const void * impl, + const rmw_topic_endpoint_info_t & endpoint_info) const override + { + (void)impl; + (void)endpoint_info; + return nullptr; + } + + std::unique_ptr from_descriptor_with_endpoint( + const void * descriptor, + const rmw_topic_endpoint_info_t & endpoint_info) const override + { + (void)descriptor; + (void)endpoint_info; + return {nullptr, [](void *) {}}; + } +}; + +} // namespace test +} // namespace rosidl_buffer_backend_registry + +// Export the DummyBufferBackend as a pluginlib plugin +PLUGINLIB_EXPORT_CLASS( + rosidl_buffer_backend_registry::test::DummyBufferBackend, + rosidl::BufferBackend) + +#endif // DUMMY_BUFFER_BACKEND_HPP_ diff --git a/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp new file mode 100644 index 000000000..a05b107ba --- /dev/null +++ b/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp @@ -0,0 +1,89 @@ +// Copyright 2026 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 "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" +#include "dummy_buffer_backend.hpp" + +using rosidl_buffer_backend_registry::BufferBackendRegistry; +using rosidl_buffer_backend_registry::test::DummyBufferImpl; + +// Test registries are ordinary context-owned objects. +TEST(TestBufferBackendRegistry, independent_instances) { + auto registry1 = std::make_unique(); + auto registry2 = std::make_unique(); + + ASSERT_NE(nullptr, registry1); + ASSERT_NE(nullptr, registry2); + EXPECT_NE(registry1.get(), registry2.get()); +} + +// Test creating a non-existent backend instance +TEST(TestBufferBackendRegistry, create_nonexistent_backend_instance) { + BufferBackendRegistry registry; + auto backend = registry.create_backend_instance("nonexistent_backend_12345"); + EXPECT_EQ(nullptr, backend); +} + +// Test DummyBufferImpl to_cpu() conversion +TEST(TestDummyBufferImpl, to_cpu_conversion) { + DummyBufferImpl dummy(4); + dummy.get_data()[0] = 10; + dummy.get_data()[1] = 20; + dummy.get_data()[2] = 30; + dummy.get_data()[3] = 40; + + auto cpu_impl = dummy.to_cpu(); + ASSERT_NE(nullptr, cpu_impl); + + auto * cpu = static_cast *>(cpu_impl.get()); + EXPECT_EQ(4u, cpu->get_storage().size()); + EXPECT_EQ(10, cpu->get_storage()[0]); + EXPECT_EQ(20, cpu->get_storage()[1]); + EXPECT_EQ(30, cpu->get_storage()[2]); + EXPECT_EQ(40, cpu->get_storage()[3]); +} + +// Test marker preservation +TEST(TestDummyBufferImpl, marker_verification) { + DummyBufferImpl impl1; + DummyBufferImpl impl2; + + EXPECT_EQ(0xDEADBEEFu, impl1.get_marker()); + EXPECT_EQ(0xDEADBEEFu, impl2.get_marker()); +} + +// Test data access via get_data() +TEST(TestDummyBufferImpl, data_access) { + DummyBufferImpl impl; + + EXPECT_TRUE(impl.get_data().empty()); + + impl.get_data().resize(3); + impl.get_data()[0] = 100; + + const int * ptr = impl.get_data().data(); + ASSERT_NE(nullptr, ptr); + EXPECT_EQ(100, ptr[0]); +} + +int main(int argc, char ** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}