diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml b/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml index 86d5ca4a3..fea0732a7 100644 --- a/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml +++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml @@ -111,12 +111,16 @@ class "mw::com::impl::GenericSkeletonEvent" #yellow { +Send(SampleAllocateePtr sample): Result +Allocate(): Result> +GetSizeInfo() const : DataTypeMetaInfo + +SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback callback): Result + +UnsetReceiveHandlerRegistrationChangedHandler(): Result } abstract class "GenericSkeletonEventBinding" #yellow { +{abstract} Send(SampleAllocateePtr sample) = 0: Result +{abstract} Allocate() = 0: Result> +{abstract} GetSizeInfo() const = 0: std::pair + +{abstract} SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback callback) = 0: Result + +{abstract} UnsetReceiveHandlerRegistrationChangedHandler() = 0: Result } class "lola::SkeletonEventCommon" #yellow { @@ -124,6 +128,7 @@ class "lola::SkeletonEventCommon" #yellow { -element_fq_id_: ElementFqId -control_: score::cpp::optional& -current_timestamp_: EventSlotStatus::EventTimeStamp& + -receive_handler_registration_changed_callback_: std::optional +SkeletonEventCommon(lola::Skeleton&, const ElementFqId&, score::cpp::optional&, EventSlotStatus::EventTimeStamp&, impl::tracing::SkeletonEventTracingData) +PrepareOfferCommon(): void +PrepareStopOfferCommon(): void @@ -132,6 +137,8 @@ class "lola::SkeletonEventCommon" #yellow { +IsQmNotificationsRegistered(): bool +IsAsilBNotificationsRegistered(): bool +GetTracingData(): impl::tracing::SkeletonEventTracingData& + +SetReceiveHandlerRegistrationChangedHandler(score::cpp::callback): Result + +UnsetReceiveHandlerRegistrationChangedHandler(): Result } class "lola::GenericSkeletonEvent" #yellow { @@ -144,6 +151,8 @@ class "lola::GenericSkeletonEvent" #yellow { +GetBindingType(): BindingType +SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data): void +GetMaxSize() const : std::size_t + +SetReceiveHandlerNotificationCallback(score::cpp::callback): Result + +UnsetReceiveHandlerRegistrationChangedHandler(): Result -skeleton_event_common_ : lola::SkeletonEventCommon } diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 50a22fd59..d3244ab6a 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -211,6 +211,7 @@ cc_library( "//score/mw/com/impl/plumbing:__pkg__", ], deps = [ + ":receive_handler_registration_changed_handler", ":skeleton_event_binding", "@score_baselibs//score/result", ], @@ -835,6 +836,18 @@ cc_library( deps = ["@score_baselibs//score/language/futurecpp"], ) +cc_library( + name = "receive_handler_registration_changed_handler", + srcs = ["receive_handler_registration_changed_handler.cpp"], + hdrs = ["receive_handler_registration_changed_handler.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = ["@score_baselibs//score/language/futurecpp"], +) + cc_library( name = "subscription_state_change_handler", srcs = ["subscription_state_change_handler.cpp"], diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD index 36e80889c..1a9b32ffe 100644 --- a/score/mw/com/impl/bindings/lola/BUILD +++ b/score/mw/com/impl/bindings/lola/BUILD @@ -1104,6 +1104,16 @@ cc_unit_test( ], ) +cc_unit_test( + name = "skeleton_event_common_test", + srcs = ["skeleton_event_common_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":skeleton", + "//score/mw/com/impl/bindings/lola/test:skeleton_event_test_resources", + ], +) + cc_unit_test( name = "proxy_test", srcs = ["proxy_test.cpp"], diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h index 3dc63fa6c..35797bcc8 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -59,6 +59,21 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding return size_info_.size; } + /// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last + /// event-notification has been unregistered. + /// \detail This extension has been added to GenericSkeletonEvent, because we are only using it so far in the LoLa + /// gateway use case. + Result SetReceiveHandlerRegistrationChangedHandler( + ReceiveHandlerRegistrationChangedCallback callback) noexcept override + { + return skeleton_event_common_.SetReceiveHandlerRegistrationChangedHandler(std::move(callback)); + } + + Result UnsetReceiveHandlerRegistrationChangedHandler() noexcept override + { + return skeleton_event_common_.UnsetReceiveHandlerRegistrationChangedHandler(); + } + private: DataTypeMetaInfo size_info_; std::uint8_t* event_data_storage_; diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.h b/score/mw/com/impl/bindings/lola/skeleton_event_common.h index d95f54f24..e506904e4 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_common.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.h @@ -24,6 +24,7 @@ #include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h" #include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h" #include "score/mw/com/impl/configuration/quality_type.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_event_binding.h" @@ -56,6 +57,8 @@ class SkeletonEventCommon // private members and used for testing purposes only. friend class SkeletonEventAttorney; + using ReceiveHandlerRegistrationChangedCallback = lola::IMessagePassingService::HandlerStatusChangeCallback; + public: SkeletonEventCommon(Skeleton& parent, const std::string_view event_name, @@ -109,6 +112,25 @@ class SkeletonEventCommon return consumer_control_local_view_qm_.value(); } + /// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last + /// event-notification has been unregistered. + /// \detail This extension has been added to GenericSkeletonEvent, because we are only using it so far in the LoLa + /// gateway use case. + Result SetReceiveHandlerRegistrationChangedHandler( + ReceiveHandlerRegistrationChangedCallback callback) noexcept + { + static_assert(std::is_same_v, + "Callback type mismatch between GenericSkeletonEvent and lola::GenericSkeletonEvent"); + receive_handler_registration_changed_callback_ = std::move(callback); + return {}; + } + + Result UnsetReceiveHandlerRegistrationChangedHandler() + { + receive_handler_registration_changed_callback_.reset(); + return {}; + } + private: Skeleton& parent_; std::string_view event_name_; @@ -142,6 +164,8 @@ class SkeletonEventCommon /// PrepareStopOfferCommon(). std::optional transaction_log_registration_guard_{}; std::optional type_erased_sample_ptrs_guard_{}; + // ReceiveHandlerRegistrationChangedCallback + std::optional receive_handler_registration_changed_callback_; void EmplaceTransactionLogRegistrationGuard(TransactionLogSet& transaction_log_set); void EmplaceTypeErasedSamplePtrsGuard(); @@ -214,6 +238,11 @@ void SkeletonEventCommon::PrepareOfferCommon(EventControl& event_con .RegisterEventNotificationExistenceChangedCallback( QualityType::kASIL_QM, element_fq_id_, [this](const bool has_handlers) noexcept { SetQmNotificationsRegistered(has_handlers); + if (receive_handler_registration_changed_callback_.has_value()) + { + const bool qm_registered = qm_event_update_notifications_registered_.load(); + receive_handler_registration_changed_callback_.value()(qm_registered); + } }); if (parent_.GetInstanceQualityType() == QualityType::kASIL_B) @@ -223,6 +252,11 @@ void SkeletonEventCommon::PrepareOfferCommon(EventControl& event_con .RegisterEventNotificationExistenceChangedCallback( QualityType::kASIL_B, element_fq_id_, [this](const bool has_handlers) noexcept { SetAsilBNotificationsRegistered(has_handlers); + if (receive_handler_registration_changed_callback_.has_value()) + { + const bool asil_b_registered = asil_b_event_update_notifications_registered_.load(); + receive_handler_registration_changed_callback_.value()(asil_b_registered); + } }); } } diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_common_test.cpp new file mode 100644 index 000000000..63b4bc058 --- /dev/null +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common_test.cpp @@ -0,0 +1,157 @@ +/******************************************************************************** + * 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/impl/bindings/lola/skeleton_event_common.h" +#include "score/mw/com/impl/bindings/lola/test/skeleton_event_test_resources.h" + +#include +#include + +namespace score::mw::com::impl::lola +{ +namespace +{ + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::ReturnRef; + +class SkeletonEventCommonFixture : public SkeletonEventFixture +{ + public: + SkeletonEventCommonFixture() + { + ON_CALL(runtime_mock_, GetServiceDiscovery()).WillByDefault(ReturnRef(service_discovery_mock_)); + } + + void InitialiseSkeletonEventCommon(const ElementFqId element_fq_id, + const std::string& service_element_name, + const std::size_t max_samples, + const std::uint8_t max_subscribers, + const bool enforce_max_samples, + const QualityType quality_type, + impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data) + { + // We defer initialisation of the Skeleton to InitialiseSkeletonEventCommon to allow test fixtures to set any + // mocked expectations before creating the skeleton. + InitialiseSkeleton(GetValidInstanceIdentifierForEventCommon(quality_type)); + + SkeletonBinding::SkeletonEventBindings events{}; + SkeletonBinding::SkeletonFieldBindings fields{}; + std::optional register_shm_object_trace_callback{}; + + std::ignore = skeleton_->PrepareOffer(events, fields, std::move(register_shm_object_trace_callback)); + + skeleton_event_ = std::make_unique>( + *skeleton_, + element_fq_id, + service_element_name, + SkeletonEventProperties{max_samples, max_subscribers, enforce_max_samples}, + skeleton_event_tracing_data); + + // Call PrepareOffer on the skeleton event to trigger Skeleton::Register, which populates + // the event_controls_ maps in ServiceDataControl for both QM and (if ASIL-B) ASIL-B. + std::ignore = skeleton_event_->PrepareOffer(); + + skeleton_event_common_ = + std::make_unique>( + *skeleton_, + event_name_, + SkeletonEventProperties{max_samples, max_subscribers, enforce_max_samples}, + element_fq_id, + skeleton_event_tracing_data); + } + + protected: + std::string_view event_name_{"test_event"}; + std::unique_ptr> skeleton_event_common_; + + InstanceIdentifier GetValidInstanceIdentifierForEventCommon(QualityType quality_type) const + { + if (quality_type == QualityType::kASIL_B) + { + return make_InstanceIdentifier(valid_asil_instance_deployment_, valid_type_deployment_); + } + else + { + return make_InstanceIdentifier(valid_qm_instance_deployment_, valid_type_deployment_); + } + } + + private: + ServiceInstanceDeployment valid_qm_instance_deployment_{make_ServiceIdentifierType(service_type_name_), + binding_info_, + QualityType::kASIL_QM, + instance_specifier_}; +}; + +TEST_F(SkeletonEventCommonFixture, RegisterEventNotificationCallbacksForAsilBTriggersMessagePassingRegistration) +{ + const bool enforce_max_samples{true}; + InitialiseSkeletonEventCommon(fake_element_fq_id_, + fake_event_name_, + max_samples_, + max_subscribers_, + enforce_max_samples, + QualityType::kASIL_B, + {}); + + auto* const event_control_qm = GetEventControl(fake_element_fq_id_, QualityType::kASIL_QM); + auto* const event_control_asil_b = GetEventControl(fake_element_fq_id_, QualityType::kASIL_B); + ASSERT_NE(event_control_qm, nullptr); + ASSERT_NE(event_control_asil_b, nullptr); + + // Expect that RegisterEventNotificationExistenceChangedCallback is called once per quality type with correct ASIL + // level and element ID + EXPECT_CALL(message_passing_mock_, + RegisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_B, fake_element_fq_id_, _)) + .Times(1); + EXPECT_CALL(message_passing_mock_, + RegisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_QM, fake_element_fq_id_, _)) + .Times(1); + // ... when PrepareOfferCommon is called + skeleton_event_common_->PrepareOfferCommon(*event_control_qm, event_control_asil_b); +} + +TEST_F(SkeletonEventCommonFixture, UnregisterEventNotificationCallbacksForAsilBTriggersMessagePassingUnregistration) +{ + const bool enforce_max_samples{true}; + InitialiseSkeletonEventCommon(fake_element_fq_id_, + fake_event_name_, + max_samples_, + max_subscribers_, + enforce_max_samples, + QualityType::kASIL_B, + {}); + + auto* const event_control_qm = GetEventControl(fake_element_fq_id_, QualityType::kASIL_QM); + auto* const event_control_asil_b = GetEventControl(fake_element_fq_id_, QualityType::kASIL_B); + ASSERT_NE(event_control_qm, nullptr); + ASSERT_NE(event_control_asil_b, nullptr); + + // Expect that RegisterEventNotificationExistenceChangedCallback is called once per quality type with correct ASIL + // level and element ID + EXPECT_CALL(message_passing_mock_, + UnregisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_B, fake_element_fq_id_)) + .Times(1); + EXPECT_CALL(message_passing_mock_, + UnregisterEventNotificationExistenceChangedCallback(impl::QualityType::kASIL_QM, fake_element_fq_id_)) + .Times(1); + // ... when PrepareStopOfferCommon is called + skeleton_event_common_->PrepareStopOfferCommon(); +} + +} // namespace + +} // namespace score::mw::com::impl::lola diff --git a/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h index 253df6175..f3e8f9a42 100644 --- a/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h @@ -37,6 +37,11 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override)); MOCK_METHOD(void, SetSkeletonEventTracingData, (impl::tracing::SkeletonEventTracingData), (noexcept, override)); MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override)); + MOCK_METHOD(Result, + SetReceiveHandlerRegistrationChangedHandler, + (ReceiveHandlerRegistrationChangedCallback), + (noexcept, override)); + MOCK_METHOD(Result, UnsetReceiveHandlerRegistrationChangedHandler, (), (noexcept, override)); }; } // namespace score::mw::com::impl::mock_binding diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index eb5e049a7..2c2e4b1c4 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -97,4 +97,26 @@ DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept return {size_info_pair.first, size_info_pair.second}; } +Result GenericSkeletonEvent::SetReceiveHandlerRegistrationChangedHandler( + ReceiveHandlerRegistrationChangedCallback callback) noexcept +{ + auto* const binding = dynamic_cast(binding_.get()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + binding != nullptr, + "Cast to GenericSkeletonEventBinding failed, but ReceiveHandlerNotification is only supported for " + "GenericSkeletonEvents."); + + return binding->SetReceiveHandlerRegistrationChangedHandler(std::move(callback)); +} + +Result GenericSkeletonEvent::UnsetReceiveHandlerRegistrationChangedHandler() noexcept +{ + auto* const binding = dynamic_cast(binding_.get()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + binding != nullptr, + "Cast to GenericSkeletonEventBinding failed, but ReceiveHandlerNotification is only supported for " + "GenericSkeletonEvents."); + + return binding->UnsetReceiveHandlerRegistrationChangedHandler(); +} } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h index 72c08534b..638398472 100644 --- a/score/mw/com/impl/generic_skeleton_event.h +++ b/score/mw/com/impl/generic_skeleton_event.h @@ -14,7 +14,9 @@ #define SCORE_MW_COM_IMPL_GENERIC_SKELETON_EVENT_H_ #include "score/mw/com/impl/data_type_meta_info.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" +#include "score/mw/com/impl/receive_handler_registration_changed_handler.h" #include "score/mw/com/impl/skeleton_event_base.h" #include "score/result/result.h" @@ -37,6 +39,16 @@ class GenericSkeletonEvent : public SkeletonEventBase Result> Allocate() noexcept; DataTypeMetaInfo GetSizeInfo() const noexcept; + + /// \brief Set callback, to get notified, when either the 1st event-notification has been registered or the last + /// event-notification has been unregistered. + /// \detail This extension has been added to GenericSkeletonEvent, because we are only using it so far in the LoLa + /// gateway use case. + Result SetReceiveHandlerRegistrationChangedHandler( + ReceiveHandlerRegistrationChangedCallback callback) noexcept; + + /// \brief Unset the callback for receive handler registration change notifications. + Result UnsetReceiveHandlerRegistrationChangedHandler() noexcept; }; } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h index 1c22856e0..0489a03f7 100644 --- a/score/mw/com/impl/generic_skeleton_event_binding.h +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -13,6 +13,7 @@ #ifndef SCORE_MW_COM_IMPL_GENERIC_SKELETON_EVENT_BINDING_H_ #define SCORE_MW_COM_IMPL_GENERIC_SKELETON_EVENT_BINDING_H_ +#include "score/mw/com/impl/receive_handler_registration_changed_handler.h" #include "score/mw/com/impl/skeleton_event_binding.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" @@ -32,6 +33,11 @@ class GenericSkeletonEventBinding : public SkeletonEventBindingBase virtual Result> Allocate() noexcept = 0; virtual std::pair GetSizeInfo() const noexcept = 0; + + virtual Result SetReceiveHandlerRegistrationChangedHandler( + ReceiveHandlerRegistrationChangedCallback callback) noexcept = 0; + + virtual Result UnsetReceiveHandlerRegistrationChangedHandler() noexcept = 0; }; } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/generic_skeleton_event_test.cpp b/score/mw/com/impl/generic_skeleton_event_test.cpp index df08ed9b9..ce57cc10d 100644 --- a/score/mw/com/impl/generic_skeleton_event_test.cpp +++ b/score/mw/com/impl/generic_skeleton_event_test.cpp @@ -92,6 +92,56 @@ class GenericSkeletonEventTest : public ::testing::Test GenericSkeletonEventBindingFactory::mock_ = nullptr; } + /// \brief Creates a GenericSkeleton with a single event and a event binding mock. Used as return value for + /// CreateSkeletonWithEvent() setup method. + struct SkeletonWithEvent + { + GenericSkeleton skeleton; + GenericSkeletonEvent* event; + mock_binding::GenericSkeletonEvent* mock_event_binding; + }; + + /// \brief Helper method to create the common setup code for the following tests. + SkeletonWithEvent CreateSkeletonWithEvent( + const std::string& event_name = "test_event", + DataTypeMetaInfo size_info = {16, 8}, + std::unique_ptr> mock_event_binding = nullptr) + { + if (!mock_event_binding) + { + mock_event_binding = std::make_unique>(); + } + auto* mock_event_binding_ptr = mock_event_binding.get(); + + EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(ByMove(std::move(mock_event_binding)))); + + GenericSkeletonServiceElementInfo create_params; + std::vector events; + events.push_back({event_name, size_info}); + create_params.events = events; + + auto skeleton_result = GenericSkeleton::Create( + dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); + EXPECT_TRUE(skeleton_result.has_value()); + + auto& skeleton = skeleton_result.value(); + auto it = skeleton.GetEvents().find(event_name); + EXPECT_NE(it, skeleton.GetEvents().cend()); + auto* event = const_cast(&it->second); + + return {std::move(skeleton_result.value()), event, mock_event_binding_ptr}; + } + + /// \brief Helper method: Offers the skeleton service, setting up the required mock expectations for tests + /// that expect an offered service. + void OfferSkeletonService(GenericSkeleton& skeleton, mock_binding::GenericSkeletonEvent* mock_event_binding_ptr) + { + EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Result{})); + ASSERT_TRUE(skeleton.OfferService().has_value()); + } + // Mocks NiceMock generic_event_binding_factory_mock_; RuntimeMockGuard runtime_mock_guard_{}; @@ -111,28 +161,7 @@ TEST_F(GenericSkeletonEventTest, AllocateBeforeOfferReturnsError) RecordProperty("TestType", "Requirements-based test"); // Given a skeleton created with one event "test_event" - const DataTypeMetaInfo size_info{16, 8}; - const std::string event_name = "test_event"; - - GenericSkeletonServiceElementInfo create_params; - std::vector events; - events.push_back({event_name, size_info}); - create_params.events = events; - - EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) - .WillOnce(Return(ByMove(std::make_unique>()))); - - auto skeleton_result = GenericSkeleton::Create( - dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); - ASSERT_TRUE(skeleton_result.has_value()); - - // And given the event instance - auto& skeleton = skeleton_result.value(); - const auto& events_map = skeleton.GetEvents(); - auto it = events_map.find(event_name); - ASSERT_NE(it, events_map.cend()); - - auto* event = const_cast(&it->second); + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); // When calling Allocate() before OfferService() auto alloc_result = event->Allocate(); @@ -148,23 +177,7 @@ TEST_F(GenericSkeletonEventTest, SendBeforeOfferReturnsError) RecordProperty("TestType", "Requirements-based test"); // Given a skeleton created with one event "test_event" - const std::string event_name = "test_event"; - - GenericSkeletonServiceElementInfo create_params; - std::vector events; - events.push_back({event_name, {16, 8}}); - create_params.events = events; - - EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) - .WillOnce(Return(ByMove(std::make_unique>()))); - - auto skeleton_result = GenericSkeleton::Create( - dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); - ASSERT_TRUE(skeleton_result.has_value()); - - auto& skeleton = skeleton_result.value(); - - auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); // And a valid sample to send mock_binding::SampleAllocateePtr dummy_sample{nullptr, [](void*) {}}; @@ -182,30 +195,9 @@ TEST_F(GenericSkeletonEventTest, AllocateAndSendDispatchesToBindingAfterOffer) RecordProperty("Description", "Checks that Allocate and Send dispatch to the binding when the service is offered."); RecordProperty("TestType", "Requirements-based test"); - // Given a skeleton configured with an event binding mock - const std::string event_name = "test_event"; - auto mock_event_binding = std::make_unique>(); - auto* mock_event_binding_ptr = mock_event_binding.get(); - - EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) - .WillOnce(Return(ByMove(std::move(mock_event_binding)))); - - GenericSkeletonServiceElementInfo create_params; - std::vector events; - events.push_back({event_name, {16, 8}}); - create_params.events = events; - - auto skeleton_result = GenericSkeleton::Create( - dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); - ASSERT_TRUE(skeleton_result.has_value()); - auto& skeleton = skeleton_result.value(); - - auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); - - // And Given the service is Offered - EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Result{})); - ASSERT_TRUE(skeleton.OfferService().has_value()); + // Given a skeleton with a mock event binding, and the service is offered + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); + OfferSkeletonService(skeleton, mock_event_binding_ptr); // When calling Allocate() mock_binding::SampleAllocateePtr dummy_alloc{nullptr, [](void*) {}}; @@ -230,29 +222,9 @@ TEST_F(GenericSkeletonEventTest, AllocateReturnsErrorWhenBindingFails) "Checks that Allocate returns kSampleAllocationFailure if the binding allocation fails."); RecordProperty("TestType", "Requirements-based test"); - // Given a skeleton configured with an event binding mock - const std::string event_name = "test_event"; - auto mock_event_binding = std::make_unique>(); - auto* mock_event_binding_ptr = mock_event_binding.get(); - - EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) - .WillOnce(Return(ByMove(std::move(mock_event_binding)))); - - GenericSkeletonServiceElementInfo create_params; - std::vector events; - events.push_back({event_name, {16, 8}}); - create_params.events = events; - - auto skeleton_result = GenericSkeleton::Create( - dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); - ASSERT_TRUE(skeleton_result.has_value()); - auto& skeleton = skeleton_result.value(); - auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); - - // And Given the service is Offered - EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Result{})); - ASSERT_TRUE(skeleton.OfferService().has_value()); + // Given a skeleton with a mock event binding, and the service is offered + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); + OfferSkeletonService(skeleton, mock_event_binding_ptr); // Expect the binding to fail allocation EXPECT_CALL(*mock_event_binding_ptr, Allocate()) @@ -271,29 +243,9 @@ TEST_F(GenericSkeletonEventTest, SendReturnsErrorWhenBindingFails) RecordProperty("Description", "Checks that Send returns kBindingFailure if the binding send fails."); RecordProperty("TestType", "Requirements-based test"); - // Given a skeleton configured with an event binding mock - const std::string event_name = "test_event"; - auto mock_event_binding = std::make_unique>(); - auto* mock_event_binding_ptr = mock_event_binding.get(); - - EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) - .WillOnce(Return(ByMove(std::move(mock_event_binding)))); - - GenericSkeletonServiceElementInfo create_params; - std::vector events; - events.push_back({event_name, {16, 8}}); - create_params.events = events; - - auto skeleton_result = GenericSkeleton::Create( - dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); - ASSERT_TRUE(skeleton_result.has_value()); - auto& skeleton = skeleton_result.value(); - auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); - - // And Given the service is Offered - EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Result{})); - ASSERT_TRUE(skeleton.OfferService().has_value()); + // Given a skeleton with a mock event binding, and the service is offered + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); + OfferSkeletonService(skeleton, mock_event_binding_ptr); // Expect the binding to fail sending EXPECT_CALL(*mock_event_binding_ptr, Send(_)).WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure))); @@ -312,24 +264,8 @@ TEST_F(GenericSkeletonEventTest, GetSizeInfoDispatchesToBinding) RecordProperty("Description", "Checks that GetSizeInfo returns the correct DataTypeMetaInfo from the binding."); RecordProperty("TestType", "Requirements-based test"); - // Given a skeleton configured with an event binding mock - const std::string event_name = "test_event"; - auto mock_event_binding = std::make_unique>(); - auto* mock_event_binding_ptr = mock_event_binding.get(); - - EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _)) - .WillOnce(Return(ByMove(std::move(mock_event_binding)))); - - GenericSkeletonServiceElementInfo create_params; - std::vector events; - events.push_back({event_name, {16, 8}}); // Original creation info - create_params.events = events; - - auto skeleton_result = GenericSkeleton::Create( - dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(), create_params); - ASSERT_TRUE(skeleton_result.has_value()); - auto& skeleton = skeleton_result.value(); - auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second); + // Given a skeleton with a mock event binding + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); // Expect the binding to return specific size info std::pair expected_size_info{32, 16}; @@ -343,5 +279,22 @@ TEST_F(GenericSkeletonEventTest, GetSizeInfoDispatchesToBinding) EXPECT_EQ(result_info.alignment, expected_size_info.second); } +TEST_F(GenericSkeletonEventTest, SetReceiveHandlerNotificationCallbackDispatchesToBinding) +{ + RecordProperty("Description", + "Checks that SetReceiveHandlerNotificationCallback sets the callback in the binding."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a skeleton with a mock event binding + auto [skeleton, event, mock_event_binding_ptr] = CreateSkeletonWithEvent(); + + // Expect the binding to receive the callback + ReceiveHandlerRegistrationChangedCallback expected_callback = [](bool) {}; + + EXPECT_CALL(*mock_event_binding_ptr, SetReceiveHandlerRegistrationChangedHandler(_)); + + event->SetReceiveHandlerRegistrationChangedHandler(std::move(expected_callback)); +} + } // namespace } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/receive_handler_registration_changed_handler.cpp b/score/mw/com/impl/receive_handler_registration_changed_handler.cpp new file mode 100644 index 000000000..e49fb9372 --- /dev/null +++ b/score/mw/com/impl/receive_handler_registration_changed_handler.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * 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/impl/receive_handler_registration_changed_handler.h" diff --git a/score/mw/com/impl/receive_handler_registration_changed_handler.h b/score/mw/com/impl/receive_handler_registration_changed_handler.h new file mode 100644 index 000000000..291848386 --- /dev/null +++ b/score/mw/com/impl/receive_handler_registration_changed_handler.h @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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_IMPL_REGISTRATION_CHANGE_HANDLER_H +#define SCORE_MW_COM_IMPL_REGISTRATION_CHANGE_HANDLER_H + +#include + +namespace score::mw::com::impl +{ + +/// \brief Callback that will be called if the first ReceiveHandler of a GenericEvent will get registered or the last +/// ReceiveHandler will be removed +using ReceiveHandlerRegistrationChangedCallback = score::cpp::callback; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_REGISTRATION_CHANGE_HANDLER_H diff --git a/score/mw/com/types.h b/score/mw/com/types.h index c29f16ab9..6051ee861 100644 --- a/score/mw/com/types.h +++ b/score/mw/com/types.h @@ -27,6 +27,7 @@ #include "score/mw/com/impl/handle_type.h" #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/instance_specifier.h" +#include "score/mw/com/impl/receive_handler_registration_changed_handler.h" #include "score/mw/com/impl/skeleton_base.h" #include "score/mw/com/impl/subscription_state.h" #include "score/mw/com/impl/traits.h" @@ -142,6 +143,11 @@ using GenericSkeletonServiceElementInfo = impl::GenericSkeletonServiceElementInf /// \brief A type erased skeleton event that can be used to send data without knowing the SampleType. using GenericSkeletonEvent = impl::GenericSkeletonEvent; +/// \api +/// \brief A callback that can be registered on a GenericSkeletonEvent to be notified about changes in the +/// availability of receive-handlers for this event. +using ReceiveHandlerRegistrationChangedCallback = impl::ReceiveHandlerRegistrationChangedCallback; + } // namespace score::mw::com #endif // SCORE_MW_COM_TYPES_H