From f8adb62d43c75768db691723ec28f4ef7c10870b Mon Sep 17 00:00:00 2001 From: rahulsutariya Date: Fri, 8 May 2026 12:54:30 +0200 Subject: [PATCH] Extend GenericSkeletonEvent to trigger EventUpdate notification --- score/mw/com/impl/bindings/lola/BUILD | 10 + .../bindings/lola/generic_skeleton_event.cpp | 5 + .../bindings/lola/generic_skeleton_event.h | 2 + .../lola/generic_skeleton_event_test.cpp | 506 ++++++++++++++++++ .../bindings/lola/skeleton_event_common.h | 10 + .../mock_binding/generic_skeleton_event.h | 2 + score/mw/com/impl/generic_skeleton_event.cpp | 13 + score/mw/com/impl/generic_skeleton_event.h | 3 + .../com/impl/generic_skeleton_event_binding.h | 3 + .../com/impl/generic_skeleton_event_test.cpp | 68 +++ 10 files changed, 622 insertions(+) create mode 100644 score/mw/com/impl/bindings/lola/generic_skeleton_event_test.cpp diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD index c485f5e56..993b270d5 100644 --- a/score/mw/com/impl/bindings/lola/BUILD +++ b/score/mw/com/impl/bindings/lola/BUILD @@ -943,6 +943,16 @@ cc_unit_test( ], ) +cc_unit_test( + name = "generic_skeleton_event_test", + srcs = ["generic_skeleton_event_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":generic_skeleton_event", + "//score/mw/com/impl/bindings/lola/test:skeleton_event_test_resources", + ], +) + cc_unit_test( name = "skeleton_method_test", srcs = ["skeleton_method_test.cpp"], diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp index 1118c1f78..24148d4f7 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp @@ -75,6 +75,11 @@ Result> GenericSkeletonEvent::All return impl::MakeSampleAllocateePtr(std::move(lola_ptr)); } +Result GenericSkeletonEvent::Notify() noexcept +{ + return skeleton_event_common_.NotifyConsumersIfHandlersRegistered(); +} + std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept { return {size_info_.size, size_info_.alignment}; 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..9e3854be1 100644 --- a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h @@ -44,6 +44,8 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding Result> Allocate() noexcept override; + Result Notify() noexcept override; + std::pair GetSizeInfo() const noexcept override; Result PrepareOffer() noexcept override; diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event_test.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event_test.cpp new file mode 100644 index 000000000..a724dedfc --- /dev/null +++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event_test.cpp @@ -0,0 +1,506 @@ +/******************************************************************************** + * 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/generic_skeleton_event.h" + +#include "score/mw/com/impl/bindings/lola/test/skeleton_event_test_resources.h" + +#include +#include + +#include +#include + +namespace score::mw::com::impl::lola +{ + +class GenericSkeletonEventFixture : public SkeletonEventFixture +{ + protected: + void SetUp() override + { + // Call base class SetUp which sets up mocks + SkeletonEventFixture::SetUp(); + + // Initialize the skeleton + InitialiseSkeleton(GetValidInstanceIdentifier()); + + // Prepare the skeleton with empty bindings + 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)); + } + + void CreateGenericSkeletonEvent(ElementFqId element_fq_id, + const std::string& event_name, + const std::size_t max_samples, + const std::uint8_t max_subscribers, + const bool enforce_max_samples) + { + const SkeletonEventProperties properties{max_samples, max_subscribers, enforce_max_samples}; + generic_skeleton_event_ = std::make_unique( + *skeleton_, event_name, properties, element_fq_id, size_info_, impl::tracing::SkeletonEventTracingData{}); + } + + std::unique_ptr generic_skeleton_event_; + const DataTypeMetaInfo size_info_{10U, 8U}; +}; + +// TODO: Fix requirement linkage as soon as requirements are matured in S-CORE. + +// Test: Construction +TEST_F(GenericSkeletonEventFixture, CanConstructAGenericSkeletonEvent) +{ + RecordProperty("Verifies", "SCR-14035184"); + RecordProperty("Description", "Checks that a GenericSkeletonEvent can be constructed."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // When constructing a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // Then a valid GenericSkeletonEvent is created + EXPECT_NE(generic_skeleton_event_, nullptr); +} + +// Test: GetBindingType +TEST_F(GenericSkeletonEventFixture, GetBindingType) +{ + RecordProperty("Verifies", "SCR-14035184"); + RecordProperty("Description", "Checks that GetBindingType returns kLoLa for GenericSkeletonEvent."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When requesting the binding type + // Then we get kLoLa + EXPECT_EQ(generic_skeleton_event_->GetBindingType(), BindingType::kLoLa); +} + +// Test: GetSizeInfo +TEST_F(GenericSkeletonEventFixture, GetSizeInfo) +{ + RecordProperty("Verifies", "SCR-14035184"); + RecordProperty("Description", "Checks that GetSizeInfo returns correct size and alignment."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When requesting size info + auto size_info = generic_skeleton_event_->GetSizeInfo(); + + // Then we get the correct size and alignment + EXPECT_EQ(size_info.first, size_info_.size); + EXPECT_EQ(size_info.second, size_info_.alignment); +} + +// Test: GetMaxSize +TEST_F(GenericSkeletonEventFixture, GetMaxSize) +{ + RecordProperty("Verifies", "SCR-14035184"); + RecordProperty("Description", "Checks that GetMaxSize returns correct maximum size."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When requesting max size + auto max_size = generic_skeleton_event_->GetMaxSize(); + + // Then we get the correct size + EXPECT_EQ(max_size, size_info_.size); +} + +// Test: PrepareOffer +TEST_F(GenericSkeletonEventFixture, PrepareOffer) +{ + RecordProperty("Verifies", "SCR-14035184"); + RecordProperty("Description", "Checks that PrepareOffer successfully initializes the event."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + auto result = generic_skeleton_event_->PrepareOffer(); + + // Then the operation succeeds + EXPECT_TRUE(result.has_value()); +} + +// Test: Allocate - before PrepareOffer +TEST_F(GenericSkeletonEventFixture, CannotAllocateBeforePrepareOffer) +{ + RecordProperty("Verifies", "SCR-21840368, SCR-17434933"); + RecordProperty("Description", "Checks that allocation fails before PrepareOffer."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent that has NOT been offered + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When trying to allocate without PrepareOffer + auto ptr = generic_skeleton_event_->Allocate(); + + // Then allocation fails + EXPECT_FALSE(ptr.has_value()); +} + +// Test: Allocate - after PrepareOffer +TEST_F(GenericSkeletonEventFixture, CanAllocateAfterPrepareOffer) +{ + RecordProperty("Verifies", "SCR-21840368, SCR-17434933"); + RecordProperty("Description", "Checks that allocation succeeds after PrepareOffer."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + std::ignore = generic_skeleton_event_->PrepareOffer(); + + // And allocating a sample + auto ptr = generic_skeleton_event_->Allocate(); + + // Then allocation succeeds + EXPECT_TRUE(ptr.has_value()); +} + +// Test: Multiple allocations +TEST_F(GenericSkeletonEventFixture, MultipleAllocationsWork) +{ + RecordProperty("Verifies", "SCR-21840368, SCR-17434933"); + RecordProperty("Description", "Checks that multiple allocations work correctly."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + std::ignore = generic_skeleton_event_->PrepareOffer(); + + // And allocating multiple samples + std::vector> pointers; + for (std::size_t i = 0; i < max_samples; ++i) + { + auto ptr = generic_skeleton_event_->Allocate(); + EXPECT_TRUE(ptr.has_value()); + pointers.push_back(std::move(ptr).value()); + } + + // Then all allocations succeeded + EXPECT_EQ(pointers.size(), max_samples); +} + +// Test: Allocation fails when slots are full +TEST_F(GenericSkeletonEventFixture, AllocationFailsWhenSlotsFull) +{ + RecordProperty("Verifies", "SCR-21840368, SCR-17434933, SCR-5899090"); + RecordProperty("Description", "Checks that allocation fails when all slots are used."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{3U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent with limited slots + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + std::ignore = generic_skeleton_event_->PrepareOffer(); + + // And allocating all available slots + std::vector> pointers; + for (std::size_t i = 0; i < max_samples; ++i) + { + auto ptr = generic_skeleton_event_->Allocate(); + EXPECT_TRUE(ptr.has_value()); + pointers.push_back(std::move(ptr).value()); + } + + // When trying to allocate beyond max_samples on ASIL-B + EXPECT_CALL(service_discovery_mock_, StopOfferService(testing::_, IServiceDiscovery::QualityTypeSelector::kAsilQm)) + .Times(1); + + auto overflow_ptr = generic_skeleton_event_->Allocate(); + + // Then allocation fails + EXPECT_FALSE(overflow_ptr.has_value()); +} + +// Test: Send after allocation +TEST_F(GenericSkeletonEventFixture, CanSendAfterAllocate) +{ + RecordProperty("Verifies", "SCR-21840368"); + RecordProperty("Description", "Checks that Send works with allocated samples."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + std::ignore = generic_skeleton_event_->PrepareOffer(); + + // And allocating a sample + auto ptr = generic_skeleton_event_->Allocate(); + ASSERT_TRUE(ptr.has_value()); + + auto sample = std::move(ptr).value(); + + // When sending the sample (moving the sample since Send takes by value) + auto result = generic_skeleton_event_->Send(std::move(sample)); + + // Then the send succeeds + EXPECT_TRUE(result.has_value()); +} + +// Test: Notify consumers +TEST_F(GenericSkeletonEventFixture, CanNotifyConsumers) +{ + RecordProperty("Verifies", "SCR-21840368"); + RecordProperty("Description", "Checks that Notify works correctly."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + std::ignore = generic_skeleton_event_->PrepareOffer(); + + // When calling Notify (without handlers registered, this should succeed) + auto result = generic_skeleton_event_->Notify(); + + // Then the notify succeeds + EXPECT_TRUE(result.has_value()); +} + +// Test: Notify triggers message passing when handlers are registered +TEST_F(GenericSkeletonEventFixture, NotifyCallsMessagePassingWithRegisteredHandlers) +{ + RecordProperty("Verifies", "SCR-21840368"); + RecordProperty("Description", + "Checks that Notify forwards event updates to message passing when receive handlers are present."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // Expect that EventNotificationExistenceChangedCallbacks are registered for both ASIL levels at message_passing + EXPECT_CALL( + message_passing_mock_, + RegisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, fake_element_fq_id_, testing::_)) + .WillOnce(testing::Invoke( + [](QualityType, ElementFqId, IMessagePassingService::HandlerStatusChangeCallback has_handlers_callback) { + has_handlers_callback(true); + })); + EXPECT_CALL( + message_passing_mock_, + RegisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, fake_element_fq_id_, testing::_)) + .WillOnce(testing::Invoke( + [](QualityType, ElementFqId, IMessagePassingService::HandlerStatusChangeCallback has_handlers_callback) { + has_handlers_callback(true); + })); + + // When offering the event + std::ignore = generic_skeleton_event_->PrepareOffer(); + // Expect that NotifyEvent gets called at message-passing for both ASIL-levels + EXPECT_CALL(message_passing_mock_, NotifyEvent(QualityType::kASIL_QM, fake_element_fq_id_)); + EXPECT_CALL(message_passing_mock_, NotifyEvent(QualityType::kASIL_B, fake_element_fq_id_)); + // When Notify() gets called on the GenericSkeletonEvent + auto result = generic_skeleton_event_->Notify(); + + // Then message passing is notified for both quality levels, and Notify() completes without errors. + EXPECT_TRUE(result.has_value()); +} + +// Test: PrepareStopOffer +TEST_F(GenericSkeletonEventFixture, PrepareStopOffer) +{ + RecordProperty("Verifies", "SCR-21840368"); + RecordProperty("Description", "Checks that PrepareStopOffer cleans up resources."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When calling PrepareOffer + std::ignore = generic_skeleton_event_->PrepareOffer(); + + // And allocating a sample + auto ptr = generic_skeleton_event_->Allocate(); + ASSERT_TRUE(ptr.has_value()); + + // When calling PrepareStopOffer + generic_skeleton_event_->PrepareStopOffer(); + + // Then no error occurs + EXPECT_TRUE(true); // PrepareStopOffer is void, just ensure it completes +} + +// Test: SetSkeletonEventTracingData +TEST_F(GenericSkeletonEventFixture, SetSkeletonEventTracingData) +{ + RecordProperty("Verifies", "SCR-21840368"); + RecordProperty("Description", "Checks that SetSkeletonEventTracingData works correctly."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When setting tracing data + impl::tracing::SkeletonEventTracingData tracing_data{}; + tracing_data.enable_send = true; + + generic_skeleton_event_->SetSkeletonEventTracingData(tracing_data); + + // Then no error occurs + EXPECT_TRUE(true); // SetSkeletonEventTracingData is void, just ensure it completes +} + +// Test: Full lifecycle +TEST_F(GenericSkeletonEventFixture, FullEventLifecycle) +{ + RecordProperty("Verifies", "SCR-21840368, SCR-17434933, SCR-5899090"); + RecordProperty("Description", "Checks complete event lifecycle: Offer -> Allocate -> Send -> StopOffer."); + RecordProperty("TestType", "Requirements-based test"); + RecordProperty("Priority", "1"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const bool enforce_max_samples{true}; + const std::size_t max_samples{5U}; + const std::uint8_t max_subscribers{3U}; + + // Given a GenericSkeletonEvent + CreateGenericSkeletonEvent( + fake_element_fq_id_, fake_event_name_, max_samples, max_subscribers, enforce_max_samples); + + // When offering the event + auto offer_result = generic_skeleton_event_->PrepareOffer(); + EXPECT_TRUE(offer_result.has_value()); + + // And allocating a sample + auto alloc_result = generic_skeleton_event_->Allocate(); + EXPECT_TRUE(alloc_result.has_value()); + + auto sample = std::move(alloc_result).value(); + + // And sending the sample + auto send_result = generic_skeleton_event_->Send(std::move(sample)); + EXPECT_TRUE(send_result.has_value()); + + // And notifying consumers + auto notify_result = generic_skeleton_event_->Notify(); + EXPECT_TRUE(notify_result.has_value()); + + // And stopping the offer + generic_skeleton_event_->PrepareStopOffer(); + + // Then the complete lifecycle succeeds +} + +} // namespace score::mw::com::impl::lola 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 27e9b28ca..042ad3cc0 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_common.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.h @@ -76,6 +76,10 @@ class SkeletonEventCommon Result AllocateSlot() noexcept; Result Send(impl::SampleAllocateePtr& sample) noexcept; + /// \brief Dispatches NotifyEvent() to QM and ASIL consumers if their respective + /// receive-handler registration flags are set. + Result NotifyConsumersIfHandlersRegistered() noexcept; + // Accessors for members used by PrepareOfferCommon/PrepareStopOfferCommon void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept { @@ -315,7 +319,13 @@ Result SkeletonEventCommon::Send(impl::SampleAllocateePtrEventReady(slot, current_timestamp_); + NotifyConsumersIfHandlersRegistered(); + return {}; +} +template +Result SkeletonEventCommon::NotifyConsumersIfHandlersRegistered() noexcept +{ // Only call NotifyEvent if there are any registered receive handlers for each quality level. // This avoids the expensive lock operation in the common case where no handlers are registered. // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent 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..e2f897564 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 @@ -31,6 +31,8 @@ class GenericSkeletonEvent : public GenericSkeletonEventBinding MOCK_METHOD(Result>, Allocate, (), (noexcept, override)); + MOCK_METHOD(Result, Notify, (), (noexcept, override)); + MOCK_METHOD((std::pair), GetSizeInfo, (), (const, noexcept, override)); MOCK_METHOD(Result, PrepareOffer, (), (noexcept, override)); MOCK_METHOD(void, PrepareStopOffer, (), (noexcept, override)); diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index eb5e049a7..89d9b0120 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -88,6 +88,19 @@ Result> GenericSkeletonEvent::Allocate() noexcept return result; } +Result GenericSkeletonEvent::Notify() noexcept +{ + if (!service_offered_flag_.IsSet()) + { + score::mw::log::LogError("lola") + << "GenericSkeletonEvent::Notify failed as Event has not yet been offered or has been stop offered"; + return MakeUnexpected(ComErrc::kNotOffered); + } + auto* const binding = static_cast(binding_.get()); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding != nullptr, "Binding is not initialized!"); + return binding->Notify(); +} + DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept { const auto* const binding = static_cast(binding_.get()); diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h index 72c08534b..8853f8b26 100644 --- a/score/mw/com/impl/generic_skeleton_event.h +++ b/score/mw/com/impl/generic_skeleton_event.h @@ -36,6 +36,9 @@ class GenericSkeletonEvent : public SkeletonEventBase Result> Allocate() noexcept; + /// \brief Explicitly trigger event-update-notifications without sending new data. + /// \note Caller must have already committed data to shared memory (gateway use). + Result Notify() noexcept; DataTypeMetaInfo GetSizeInfo() const noexcept; }; diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h index 1c22856e0..fe811d8f1 100644 --- a/score/mw/com/impl/generic_skeleton_event_binding.h +++ b/score/mw/com/impl/generic_skeleton_event_binding.h @@ -31,6 +31,9 @@ class GenericSkeletonEventBinding : public SkeletonEventBindingBase virtual Result> Allocate() noexcept = 0; + /// \brief Get Notification when new sample is available. + virtual Result Notify() noexcept = 0; + virtual std::pair GetSizeInfo() const noexcept = 0; }; diff --git a/score/mw/com/impl/generic_skeleton_event_test.cpp b/score/mw/com/impl/generic_skeleton_event_test.cpp index df08ed9b9..e82366546 100644 --- a/score/mw/com/impl/generic_skeleton_event_test.cpp +++ b/score/mw/com/impl/generic_skeleton_event_test.cpp @@ -343,5 +343,73 @@ TEST_F(GenericSkeletonEventTest, GetSizeInfoDispatchesToBinding) EXPECT_EQ(result_info.alignment, expected_size_info.second); } +TEST_F(GenericSkeletonEventTest, NotifyBeforeOfferReturnsError) +{ + RecordProperty("Description", "Checks that calling Notify() before OfferService() returns kNotOffered."); + 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); + + // When calling Notify() before OfferService() + auto notify_result = event->Notify(); + + // Then it fails with kNotOffered + ASSERT_FALSE(notify_result.has_value()); + EXPECT_EQ(notify_result.error(), ComErrc::kNotOffered); +} + +TEST_F(GenericSkeletonEventTest, NotifyDispatchesToBindingAfterOffer) +{ + RecordProperty("Description", "Checks that Notify() dispatches 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()); + + // When calling Notify() + EXPECT_CALL(*mock_event_binding_ptr, Notify()).WillOnce(Return(score::Result{})); + auto notify_result = event->Notify(); + + // Then it succeeds + ASSERT_TRUE(notify_result.has_value()); +} + } // namespace } // namespace score::mw::com::impl