diff --git a/score/mw/com/impl/bindings/lola/proxy_event.h b/score/mw/com/impl/bindings/lola/proxy_event.h index 625375315..863a88ec0 100644 --- a/score/mw/com/impl/bindings/lola/proxy_event.h +++ b/score/mw/com/impl/bindings/lola/proxy_event.h @@ -14,7 +14,11 @@ #define SCORE_MW_COM_IMPL_BINDINGS_LOLA_PROXY_EVENT_H #include "score/mw/com/impl/bindings/lola/event_data_storage.h" +#include "score/mw/com/impl/bindings/lola/event_meta_info.h" #include "score/mw/com/impl/bindings/lola/proxy_event_common.h" + +#include "score/language/safecpp/safe_math/safe_math.h" +#include "score/memory/shared/pointer_arithmetic_util.h" #include "score/mw/com/impl/proxy_event_binding.h" #include "score/mw/com/impl/sample_reference_tracker.h" #include "score/mw/com/impl/subscription_state.h" @@ -64,7 +68,7 @@ class ProxyEvent final : public ProxyEventBinding ProxyEvent(Proxy& parent, const ElementFqId element_fq_id, const std::string_view event_name) : ProxyEventBinding{}, proxy_event_common_{parent, element_fq_id, event_name}, - samples_{parent.GetEventDataStorage(element_fq_id)} + meta_info_{parent.GetEventMetaInfo(element_fq_id)} { } @@ -126,7 +130,7 @@ class ProxyEvent final : public ProxyEventBinding Result GetNumNewSamplesAvailableImpl() const noexcept; ProxyEventCommon proxy_event_common_; - const EventDataStorage& samples_; + const EventMetaInfo& meta_info_; }; template @@ -184,9 +188,40 @@ inline Result ProxyEvent::GetNewSamplesImpl(Callback&& auto& event_data_control_local = proxy_event_common_.GetConsumerEventDataControlLocal(); + const std::size_t aligned_sample_size = + memory::shared::CalculateAlignedSize(sizeof(SampleType), alignof(SampleType)); + const auto event_slots_raw_array_size = + safe_math::Multiply(aligned_sample_size, event_data_control_local.GetMaxSampleSlots()); + if (!event_slots_raw_array_size.has_value()) + { + score::mw::log::LogFatal("lola") << "Could not calculate the event slots raw array size. Terminating."; + std::terminate(); + } + + const void* const event_slots_raw_array = meta_info_.event_slots_raw_array_.get(event_slots_raw_array_size.value()); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(nullptr != event_slots_raw_array, "Null event slot array"); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(meta_info_.data_type_info_.size == sizeof(SampleType), + "Event sample size mismatch"); + SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(meta_info_.data_type_info_.alignment == alignof(SampleType), + "Event sample alignment mismatch"); + for (auto slot_index_it = slot_indices.begin; slot_index_it != slot_indices.end; ++slot_index_it) { - const SampleType& sample_data{samples_.at(static_cast(*slot_index_it))}; + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) The pointer event_slots_raw_array points to + // the first byte of the type-erased event sample storage in shared memory. Samples may originate from either a + // typed SkeletonEvent or a GenericSkeletonEvent, therefore slot lookup must use the stable EventMetaInfo raw + // storage address and SampleType stride instead of interpreting the shared-memory DynamicArray object type. + const auto* const event_slots_array = static_cast(event_slots_raw_array); + const auto* const object_start_address = &event_slots_array[aligned_sample_size * (*slot_index_it)]; + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + // Suppress "AUTOSAR C++14 M5-2-8" rule finding: "An object with integer type or pointer to void type shall + // not be converted to an object with pointer type.". + // The raw storage address is provided through EventMetaInfo. The regular typed proxy validates the expected + // type at compile time and calculates the slot offset with sizeof(SampleType)/alignof(SampleType). + // coverity[autosar_cpp14_m5_2_8_violation] + const auto* const typed_sample_data = reinterpret_cast(object_start_address); + const SampleType& sample_data{*typed_sample_data}; const EventSlotStatus event_slot_status{event_data_control_local[*slot_index_it]}; const EventSlotStatus::EventTimeStamp sample_timestamp{event_slot_status.GetTimeStamp()}; diff --git a/score/mw/com/impl/bindings/lola/proxy_event_test.cpp b/score/mw/com/impl/bindings/lola/proxy_event_test.cpp index 06aea0d11..2ac985421 100644 --- a/score/mw/com/impl/bindings/lola/proxy_event_test.cpp +++ b/score/mw/com/impl/bindings/lola/proxy_event_test.cpp @@ -693,6 +693,35 @@ TYPED_TEST(LolaProxyEventDeathTest, FailOnEventNotFound) } using LoLaTypedProxyEventTestFixture = LolaProxyEventFixture; +TEST_F(LoLaTypedProxyEventTestFixture, ReadsSamplesFromGenericSkeletonRawStorage) +{ + RecordProperty("Verifies", "SCR-6225206"); + RecordProperty("Description", + "Checks that a typed ProxyEvent reads samples from EventMetaInfo raw storage so it is compatible " + "with a GenericSkeletonEvent storage representation."); + RecordProperty("TestType", "Regression test"); + RecordProperty("DerivationTechnique", "Analysis of bug 311"); + + this->ReplaceDummySkeletonEventStorageWithGenericSkeletonLayout(); + const std::size_t max_sample_count_subscription{5U}; + this->GivenAProxyEvent(this->element_fq_id_, this->event_name_) + .ThatIsSubscribedWithMaxSamples(max_sample_count_subscription) + .WithSkeletonEventData({{kDummySampleValue, kDummyInputTimestamp}}); + + const std::size_t max_samples{1U}; + TestSampleType received_sample{0U}; + const auto receiver = [&received_sample](impl::SamplePtr sample, + const tracing::ITracingRuntime::TracePointDataId) { + received_sample = *sample; + }; + + const auto num_callbacks_result = this->GetNewSamples(receiver, max_samples); + + ASSERT_TRUE(num_callbacks_result.has_value()); + EXPECT_EQ(num_callbacks_result.value(), 1U); + EXPECT_EQ(received_sample, kDummySampleValue); +} + TEST_F(LoLaTypedProxyEventTestFixture, SampleConstness) { RecordProperty("Verifies", "SCR-6340729"); @@ -703,8 +732,8 @@ TEST_F(LoLaTypedProxyEventTestFixture, SampleConstness) this->GivenAProxyEvent(this->element_fq_id_, this->event_name_); ProxyEventAttorney proxy_event_attorney{*test_proxy_event_}; - using SamplesMemberType = typename std::remove_reference::type; - static_assert(std::is_const::value, "Proxy should hold const slot data."); + using MetaInfoMemberType = typename std::remove_reference::type; + static_assert(std::is_const::value, "Proxy should hold const event meta info."); } } // namespace diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 1f0982fce..87ccae82c 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -518,8 +518,9 @@ auto Skeleton::RegisterGeneric(const ElementFqId element_fq_id, memory_manager_.RollbackSkeletonTracingTransactions(*event_data_control_asil_b); } - auto& event_data_storage = memory_manager_.RetrieveEventDataFromOpenedSharedMemory(element_fq_id); - return {static_cast(&event_data_storage), event_data_control_qm, event_data_control_asil_b}; + auto* const event_data_storage = + memory_manager_.RetrieveGenericEventDataFromOpenedSharedMemory(element_fq_id, element_properties); + return {event_data_storage, event_data_control_qm, event_data_control_asil_b}; } auto* const type_erased_event_data_storage = memory_manager_.CreateGenericEventDataInCreatedSharedMemory( diff --git a/score/mw/com/impl/bindings/lola/skeleton_memory_manager.cpp b/score/mw/com/impl/bindings/lola/skeleton_memory_manager.cpp index b776e8e55..405581d34 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_memory_manager.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton_memory_manager.cpp @@ -22,6 +22,7 @@ #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_event_binding.h" +#include "score/language/safecpp/safe_math/safe_math.h" #include "score/memory/shared/managed_memory_resource.h" #include "score/memory/shared/new_delete_delegate_resource.h" #include "score/memory/shared/shared_memory_factory.h" @@ -232,7 +233,35 @@ void* SkeletonMemoryManager::CreateGenericEventDataInCreatedSharedMemory( SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section."); - return data_storage; + return event_data_raw_array; +} + +void* SkeletonMemoryManager::RetrieveGenericEventDataFromOpenedSharedMemory( + const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties) noexcept +{ + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(storage_ != nullptr, "Service data storage is not available."); + + const auto event_meta_info_it = storage_->events_metainfo_.find(element_fq_id); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_meta_info_it != storage_->events_metainfo_.cend(), + "Could not find element fq id in meta info map"); + + const auto sample_size = event_meta_info_it->second.data_type_info_.size; + const auto sample_alignment = event_meta_info_it->second.data_type_info_.alignment; + const auto aligned_sample_size = + memory::shared::CalculateAlignedSize(sample_size, static_cast(sample_alignment)); + const auto total_event_slots_size = safe_math::Multiply(aligned_sample_size, element_properties.number_of_slots); + if (!total_event_slots_size.has_value()) + { + score::mw::log::LogFatal("lola") << "Could not calculate the event slots raw array size. Terminating."; + std::terminate(); + } + + void* const event_slots_raw_array = + event_meta_info_it->second.event_slots_raw_array_.get(total_event_slots_size.value()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_slots_raw_array != nullptr, + "Could not get generic EventDataStorage raw array"); + return event_slots_raw_array; } auto SkeletonMemoryManager::RetrieveEventControlsFromOpenedSharedMemory(const ElementFqId element_fq_id) diff --git a/score/mw/com/impl/bindings/lola/skeleton_memory_manager.h b/score/mw/com/impl/bindings/lola/skeleton_memory_manager.h index 732cfbfb9..64dcf83d5 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_memory_manager.h +++ b/score/mw/com/impl/bindings/lola/skeleton_memory_manager.h @@ -127,6 +127,14 @@ class SkeletonMemoryManager final template auto RetrieveEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id) -> EventDataStorage&; + /// \brief Retrieves the raw event sample storage pointer for a generic event from opened shared memory. + /// + /// Generic events use EventMetaInfo as the stable type-erased contract. This keeps generic skeleton restarts + /// independent from the concrete DynamicArray representation used by typed skeletons and proxies. + auto RetrieveGenericEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id, + const SkeletonEventProperties& element_properties) noexcept + -> void*; + /// \brief Rolls back any existing operations in the TransactionLog corresponding to a SkeletonEvent /// /// The TransactionLog would only exist if a SkeletonEvent in a crashed process had tracing enabled. If tracing was diff --git a/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.cpp b/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.cpp index 884d739c8..b5f6f5ba8 100644 --- a/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.cpp +++ b/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.cpp @@ -13,10 +13,12 @@ #include "score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.h" #include "score/memory/shared/memory_resource_registry.h" +#include "score/memory/shared/pointer_arithmetic_util.h" #include #include +#include #include #include @@ -129,6 +131,27 @@ void ProxyMockedMemoryFixture::InitialiseDummySkeletonEvent(const ElementFqId el provider_event_data_control_local_.emplace(event_control_->data_control); } +void LolaProxyEventResources::ReplaceDummySkeletonEventStorageWithGenericSkeletonLayout() +{ + const auto aligned_sample_size = memory::shared::CalculateAlignedSize(sizeof(SampleType), alignof(SampleType)); + const auto total_data_size_bytes = aligned_sample_size * max_num_slots_; + const auto num_max_align_elements = + (total_data_size_bytes + sizeof(std::max_align_t) - 1U) / sizeof(std::max_align_t); + + auto* const generic_event_data_slots = fake_data_->data_memory->construct>( + num_max_align_elements, + memory::shared::PolymorphicOffsetPtrAllocator(*fake_data_->data_memory)); + generic_event_data_storage_ = static_cast(static_cast(generic_event_data_slots->data())); + + auto event_data_it = fake_data_->data_storage->events_.find(element_fq_id_); + SCORE_LANGUAGE_FUTURECPP_ASSERT(event_data_it != fake_data_->data_storage->events_.end()); + event_data_it->second = memory::shared::OffsetPtr{static_cast(generic_event_data_slots)}; + + auto event_meta_info_it = fake_data_->data_storage->events_metainfo_.find(element_fq_id_); + SCORE_LANGUAGE_FUTURECPP_ASSERT(event_meta_info_it != fake_data_->data_storage->events_metainfo_.end()); + event_meta_info_it->second.event_slots_raw_array_ = memory::shared::OffsetPtr{generic_event_data_storage_}; +} + LolaProxyEventResources::LolaProxyEventResources() : ProxyMockedMemoryFixture{} { InitialiseDummySkeletonEvent(element_fq_id_, SkeletonEventProperties{max_num_slots_, max_subscribers_, true}); @@ -187,7 +210,17 @@ SlotIndexType LolaProxyEventResources::PutData(const std::uint32_t value, auto slot_result = provider_event_data_control_local_->AllocateNextSlot(); EXPECT_TRUE(slot_result.has_value()); auto slot_index = slot_result.value(); - event_data_storage_->at(slot_index) = value; + if (generic_event_data_storage_ != nullptr) + { + const auto aligned_sample_size = memory::shared::CalculateAlignedSize(sizeof(SampleType), alignof(SampleType)); + auto* const slot_address = + memory::shared::AddOffsetToPointer(generic_event_data_storage_, aligned_sample_size * slot_index); + std::memcpy(slot_address, &value, sizeof(value)); + } + else + { + event_data_storage_->at(slot_index) = value; + } provider_event_data_control_local_->EventReady(slot_index, timestamp); return slot_index; } diff --git a/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.h b/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.h index a8240c6cc..cd92c1d65 100644 --- a/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.h +++ b/score/mw/com/impl/bindings/lola/test/proxy_event_test_resources.h @@ -13,6 +13,7 @@ #ifndef SCORE_MW_COM_IMPL_BINDINGS_LOLA_TEST_PROXY_EVENT_TEST_RESOURCES_H #define SCORE_MW_COM_IMPL_BINDINGS_LOLA_TEST_PROXY_EVENT_TEST_RESOURCES_H +#include "score/mw/com/impl/bindings/lola/event_data_storage.h" #include "score/mw/com/impl/bindings/lola/event_subscription_control.h" #include "score/mw/com/impl/bindings/lola/generic_proxy_event.h" #include "score/mw/com/impl/bindings/lola/i_runtime.h" @@ -94,9 +95,9 @@ class ProxyEventAttorney ProxyEventAttorney(ProxyEvent& proxy_event) noexcept : proxy_event_{proxy_event} {} - auto& GetSamplesMember() + auto& GetMetaInfoMember() { - return proxy_event_.samples_; + return proxy_event_.meta_info_; } private: @@ -207,6 +208,7 @@ class ProxyMockedMemoryFixture : public ::testing::Test std::optional> provider_event_data_control_local_{}; std::optional> consumer_event_data_control_local_{}; EventDataStorage* event_data_storage_{nullptr}; + std::uint8_t* generic_event_data_storage_{nullptr}; RollbackSynchronization rollback_synchronization_{}; std::shared_ptr mock_service_{std::make_shared()}; @@ -226,6 +228,8 @@ class LolaProxyEventResources : public ProxyMockedMemoryFixture void ExpectReregisterEventNotification(score::cpp::optional pid = {}); void ExpectUnregisterEventNotification(score::cpp::optional pid = {}); + void ReplaceDummySkeletonEventStorageWithGenericSkeletonLayout(); + SlotIndexType PutData(const std::uint32_t value = 42, const EventSlotStatus::EventTimeStamp timestamp = 1); const std::size_t max_num_slots_{5U}; diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/BUILD b/score/mw/com/test/generic_skeleton_typed_proxy/BUILD new file mode 100644 index 000000000..c0232269e --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/BUILD @@ -0,0 +1,49 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_binary") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//score/mw/com/test:pkg_application.bzl", "pkg_application") + +cc_binary( + name = "generic_skeleton_typed_proxy", + srcs = [ + "generic_skeleton_typed_proxy_application.cpp", + "generic_skeleton_typed_proxy_application.h", + ], + data = ["mw_com_config.json"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:bigdata_type", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + "@score_baselibs//score/language/futurecpp", + ], +) + +pkg_application( + name = "generic_skeleton_typed_proxy-pkg", + app_name = "generic_skeleton_typed_proxy", + bin = [":generic_skeleton_typed_proxy"], + etc = [ + "mw_com_config.json", + "logging.json", + ], + visibility = [ + "//platform/aas/test/mw/com:__pkg__", + "//score/mw/com/test/generic_skeleton_typed_proxy:__subpackages__", + ], +) diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.cpp b/score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.cpp new file mode 100644 index 000000000..5f46aa3dc --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.cpp @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +#include "score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.h" + +#include "score/mw/com/impl/bindings/lola/proxy_event.h" +#include "score/mw/com/impl/data_type_meta_info.h" +#include "score/mw/com/impl/generic_skeleton.h" +#include "score/mw/com/impl/generic_skeleton_event.h" +#include "score/mw/com/impl/instance_specifier.h" +#include "score/mw/com/test/common_test_resources/big_datatype.h" +#include "score/mw/com/test/common_test_resources/sample_sender_receiver.h" +#include "score/mw/com/test/common_test_resources/sctf_test_runner.h" + +#include "score/result/result.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +using score::mw::com::impl::DataTypeMetaInfo; +using score::mw::com::impl::EventInfo; +using score::mw::com::impl::GenericSkeleton; +using score::mw::com::impl::GenericSkeletonEvent; +using score::mw::com::impl::GenericSkeletonServiceElementInfo; +using score::mw::com::impl::SampleAllocateePtr; +using score::mw::com::test::DummyDataStamped; +using score::mw::com::test::LaneIdType; +using score::mw::com::test::MapApiLaneData; +using score::mw::com::test::MapApiLanesStamped; + +constexpr std::size_t kStartHash{64738U}; +constexpr const char* kMapApiLanesStampedEventName{"map_api_lanes_stamped"}; +constexpr const char* kInstanceSpecifier{"score/cp60/MapApiLanesStamped"}; + +void HashArray(const std::array& array, std::size_t& seed) +{ + const std::ptrdiff_t buffer_size = + reinterpret_cast(&*array.cend()) - reinterpret_cast(&*array.cbegin()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(buffer_size > 0); + seed = score::cpp::hash_bytes_fnv1a( + static_cast(array.data()), static_cast(buffer_size), seed); +} + +score::Result> PrepareMapApiLanesStampedSample(GenericSkeletonEvent& event, + const std::size_t cycle) +{ + const std::default_random_engine::result_type seed{static_cast( + std::chrono::steady_clock::now().time_since_epoch().count())}; + std::default_random_engine rng{seed}; + + auto sample_result = event.Allocate(); + if (!sample_result.has_value()) + { + return sample_result; + } + + auto sample = std::move(sample_result).value(); + auto* const typed_sample = static_cast(sample.Get()); + typed_sample->hash_value = kStartHash; + typed_sample->x = static_cast(cycle); + + for (MapApiLaneData& lane : typed_sample->lanes) + { + for (LaneIdType& successor : lane.successor_lanes) + { + successor = std::uniform_int_distribution()(rng); + } + HashArray(lane.successor_lanes, typed_sample->hash_value); + } + + std::cout << "GenericSkeleton sent sample " << typed_sample->x << std::endl; + return sample; +} + +int RunAsGenericSkeleton(const score::mw::com::InstanceSpecifier& instance_specifier, + const std::chrono::milliseconds cycle_time, + const std::size_t num_cycles, + const score::cpp::stop_token& stop_token) +{ + const DataTypeMetaInfo map_api_lanes_stamped_meta_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)}; + const DataTypeMetaInfo dummy_data_stamped_meta_info{sizeof(DummyDataStamped), alignof(DummyDataStamped)}; + const std::vector events{{kMapApiLanesStampedEventName, map_api_lanes_stamped_meta_info}, + {"dummy_data_stamped", dummy_data_stamped_meta_info}}; + GenericSkeletonServiceElementInfo service_element_info{}; + service_element_info.events = events; + + auto skeleton_result = GenericSkeleton::Create(instance_specifier, service_element_info); + if (!skeleton_result.has_value()) + { + std::cerr << "Unable to construct GenericSkeleton: " << skeleton_result.error() << std::endl; + return EXIT_FAILURE; + } + + auto& skeleton = skeleton_result.value(); + auto event_it = skeleton.GetEvents().find(kMapApiLanesStampedEventName); + if (event_it == skeleton.GetEvents().cend()) + { + std::cerr << "GenericSkeleton event not found: " << kMapApiLanesStampedEventName << std::endl; + return EXIT_FAILURE; + } + auto& event = const_cast(event_it->second); + + const auto offer_result = skeleton.OfferService(); + if (!offer_result.has_value()) + { + std::cerr << "Unable to offer GenericSkeleton service: " << offer_result.error() << std::endl; + return EXIT_FAILURE; + } + + for (std::size_t cycle = 0U; (cycle < num_cycles || num_cycles == 0U) && !stop_token.stop_requested(); ++cycle) + { + auto sample_result = PrepareMapApiLanesStampedSample(event, cycle); + if (!sample_result.has_value()) + { + std::cerr << "Unable to allocate GenericSkeleton sample: " << sample_result.error() << std::endl; + skeleton.StopOfferService(); + return EXIT_FAILURE; + } + + const auto send_result = event.Send(std::move(sample_result).value()); + if (!send_result.has_value()) + { + std::cerr << "Unable to send GenericSkeleton sample: " << send_result.error() << std::endl; + skeleton.StopOfferService(); + return EXIT_FAILURE; + } + + std::this_thread::sleep_for(cycle_time); + } + + skeleton.StopOfferService(); + return EXIT_SUCCESS; +} + +} // namespace + +int main(int argc, const char** argv) +{ + using Parameters = score::mw::com::test::SctfTestRunner::RunParameters::Parameters; + + const std::vector allowed_parameters{Parameters::MODE, Parameters::NUM_CYCLES, Parameters::CYCLE_TIME}; + score::mw::com::test::SctfTestRunner test_runner(argc, argv, allowed_parameters); + const auto& run_parameters = test_runner.GetRunParameters(); + const auto mode = run_parameters.GetMode(); + const auto num_cycles = run_parameters.GetNumCycles(); + const auto stop_token = test_runner.GetStopToken(); + + const auto instance_specifier_result = score::mw::com::InstanceSpecifier::Create(std::string{kInstanceSpecifier}); + if (!instance_specifier_result.has_value()) + { + std::cerr << "Invalid instance specifier, terminating." << std::endl; + return EXIT_FAILURE; + } + const auto& instance_specifier = instance_specifier_result.value(); + + if (mode == "send" || mode == "skeleton" || mode == "generic_skeleton") + { + const auto cycle_time = run_parameters.GetCycleTime(); + return RunAsGenericSkeleton(instance_specifier, cycle_time, num_cycles, stop_token); + } + else if (mode == "recv" || mode == "proxy" || mode == "typed_proxy") + { + score::mw::com::test::EventSenderReceiver event_sender_receiver; + const score::cpp::optional cycle_time = {}; + return event_sender_receiver + .RunAsProxy>( + instance_specifier, cycle_time, num_cycles, std::cref(stop_token)); + } + else + { + std::cerr << "Unknown mode " << mode << ", terminating." << std::endl; + return EXIT_FAILURE; + } +} diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.h b/score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.h new file mode 100644 index 000000000..a52fb8f08 --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/generic_skeleton_typed_proxy_application.h @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +#ifndef SCORE_MW_COM_TEST_GENERIC_SKELETON_TYPED_PROXY_GENERIC_SKELETON_TYPED_PROXY_APPLICATION_H +#define SCORE_MW_COM_TEST_GENERIC_SKELETON_TYPED_PROXY_GENERIC_SKELETON_TYPED_PROXY_APPLICATION_H + +#endif // SCORE_MW_COM_TEST_GENERIC_SKELETON_TYPED_PROXY_GENERIC_SKELETON_TYPED_PROXY_APPLICATION_H diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/integration_test/BUILD b/score/mw/com/test/generic_skeleton_typed_proxy/integration_test/BUILD new file mode 100644 index 000000000..b0fc8fb9c --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/integration_test/BUILD @@ -0,0 +1,29 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +pkg_tar( + name = "filesystem", + deps = [ + "//score/mw/com/test/generic_skeleton_typed_proxy:generic_skeleton_typed_proxy-pkg", + ], +) + +integration_test( + name = "generic_skeleton_typed_proxy", + timeout = "moderate", + srcs = ["test_generic_skeleton_typed_proxy.py"], + filesystem = ":filesystem", +) diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/integration_test/test_generic_skeleton_typed_proxy.py b/score/mw/com/test/generic_skeleton_typed_proxy/integration_test/test_generic_skeleton_typed_proxy.py new file mode 100644 index 000000000..b3a933573 --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/integration_test/test_generic_skeleton_typed_proxy.py @@ -0,0 +1,40 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +"""Integration test for GenericSkeleton publishing to a typed proxy.""" + + +def generic_skeleton_typed_proxy(target, mode, cycle_time=None, num_cycles=None, **kwargs): + """Run the GenericSkeleton/typed-proxy verification binary.""" + args = ["--mode", mode] + if num_cycles is not None: + args += ["--num-cycles", str(num_cycles)] + if cycle_time is not None: + args += ["--cycle-time", str(cycle_time)] + + wait_on_exit = mode in ["recv", "proxy", "typed_proxy"] + return target.wrap_exec( + "bin/generic_skeleton_typed_proxy", + args, + cwd="/opt/generic_skeleton_typed_proxy", + wait_on_exit=wait_on_exit, + **kwargs, + ) + + +def test_generic_skeleton_typed_proxy(target): + """Verify that a typed proxy can consume samples from GenericSkeleton-created event storage.""" + # num_cycles = 0 keeps the GenericSkeleton alive until the receiving side finishes. + with generic_skeleton_typed_proxy(target, "generic_skeleton", cycle_time=40, num_cycles=0), \ + generic_skeleton_typed_proxy(target, "typed_proxy", num_cycles=25, wait_timeout=15): + pass diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/logging.json b/score/mw/com/test/generic_skeleton_typed_proxy/logging.json new file mode 100644 index 000000000..7a10a54ca --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/logging.json @@ -0,0 +1,8 @@ +{ + "appId": "GENP", + "appDesc": "generic_proxy", + "logLevel": "kDebug", + "logLevelThresholdConsole": "kDebug", + "logMode": "kRemote|kConsole", + "dynamicDatarouterIdentifiers" : true +} diff --git a/score/mw/com/test/generic_skeleton_typed_proxy/mw_com_config.json b/score/mw/com/test/generic_skeleton_typed_proxy/mw_com_config.json new file mode 100644 index 000000000..d2917ccf9 --- /dev/null +++ b/score/mw/com/test/generic_skeleton_typed_proxy/mw_com_config.json @@ -0,0 +1,68 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/score/adp/MapApiLanesStamped", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 6432, + "events": [ + { + "eventName": "map_api_lanes_stamped", + "eventId": 1 + }, + { + "eventName": "dummy_data_stamped", + "eventId": 2 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "score/cp60/MapApiLanesStamped", + "serviceTypeName": "/score/adp/MapApiLanesStamped", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "allowedConsumer": { + "QM": [ + 1000, + 0 + ] + }, + "allowedProvider": { + "QM": [ + 1001, + 0 + ] + }, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "map_api_lanes_stamped", + "maxSamples": 10, + "maxSubscribers": 3 + }, + { + "eventName": "dummy_data_stamped", + "maxSamples": 10, + "maxSubscribers": 3 + } + ] + } + ] + } + ] +}