diff --git a/score/mw/com/design/events_fields/README.md b/score/mw/com/design/events_fields/README.md index 20743af67..9b6b5b288 100644 --- a/score/mw/com/design/events_fields/README.md +++ b/score/mw/com/design/events_fields/README.md @@ -76,6 +76,23 @@ The same asymmetry shows up in how each side builds the field's event dispatch: - On the proxy side, the event dispatch (`proxy_event_dispatch_`) is only created when `WithNotifier` is set. Without it the member stays `nullptr` and the notifier methods are removed at compile time. +The notifier also decides how many sample slots the provider allocates. `numberOfSampleSlots` exists to give +subscribers something to read: the provider sizes the slot pool so all consumers can hold their samples while it +keeps publishing (see the formula in the [configuration readme](../../impl/configuration/README.md)). Without +`WithNotifier` no consumer can ever subscribe (proxy and skeleton are compiled from the same tag pack), so there is +nothing to size. The skeleton only needs two slots for its own `Update()`: one holds the current value, one is +written by the next update. The LoLa binding therefore ignores the configured value for such fields: + +| `WithNotifier` | `numberOfSampleSlots` | used slot count | +| -------------- | --------------------- | -------------------------- | +| enabled | configured | as configured | +| enabled | missing | none, startup terminates | +| disabled | missing | 2 | +| disabled | configured | 2, a warning is logged | + +Configured `numberOfIpcTracingSlots` come on top of the used slot count in all cases, because tracing holds a +reference to each traced sample until the trace call has completed. + The only combination we actually enforce is a `static_assert` on both `impl::ProxyField` and `impl::SkeletonField`: a field must have at least one of `WithGetter` or `WithNotifier`. Without one of them the consumer has no way to observe the value, which makes the field useless. We deliberately do not require a setter, since a read-only field is perfectly normal and the provider always sets the diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_properties.h b/score/mw/com/impl/bindings/lola/skeleton_event_properties.h index 6ce8669bd..d5f94e647 100644 --- a/score/mw/com/impl/bindings/lola/skeleton_event_properties.h +++ b/score/mw/com/impl/bindings/lola/skeleton_event_properties.h @@ -14,10 +14,15 @@ #define SCORE_MW_COM_IMPL_BINDINGS_LOLA_SKELETON_EVENT_PROPERTIES_H #include +#include namespace score::mw::com::impl::lola { +/// \brief Slot count a field without a notifier uses for its backing event: one slot for the current value, one so +/// Update() can write concurrently. +constexpr std::uint16_t kSlotCountForFieldWithoutNotifier{2U}; + struct SkeletonEventProperties { std::size_t number_of_slots; diff --git a/score/mw/com/impl/configuration/README.md b/score/mw/com/impl/configuration/README.md index 780853428..084bceda3 100644 --- a/score/mw/com/impl/configuration/README.md +++ b/score/mw/com/impl/configuration/README.md @@ -387,6 +387,10 @@ The properties of a field or an event object on the instance level are: `numberOfSampleSlots`. I.e. The sum of all `maxSamples` values of all subscribing consumers must not exceed `numberOfSampleSlots`. Otherwise, the subscribe call will be rejected. However, this check is not ASIL level overarching. See explanation in the `maxSubscribers` section below. + **Note**: For a field without `WithNotifier` this property is ignored, since no consumer can subscribe to it. + The provider always uses 2 slots there: one for the current value and one so `Update()` can write concurrently. + If a value is configured anyway, a warning is logged. See the + [events and fields design](../../design/events_fields/README.md) for details. - `maxSubscribers`: (mandatory on provider side) - how many consumers are allowed to subscribe to this event or field. This number is the combined number of `QM` and `ASIL-B` consumers. **Note**: The maximum number of subscribers can't currently be supervised ASIL level **overarching**. I.e. in case of diff --git a/score/mw/com/impl/field_tags.h b/score/mw/com/impl/field_tags.h index ccb0d5a12..34b9001f7 100644 --- a/score/mw/com/impl/field_tags.h +++ b/score/mw/com/impl/field_tags.h @@ -13,6 +13,7 @@ #ifndef SCORE_MW_COM_IMPL_FIELD_TAGS_H #define SCORE_MW_COM_IMPL_FIELD_TAGS_H +#include #include namespace score::mw::com::impl @@ -32,6 +33,14 @@ struct WithNotifier { }; +/// \brief Runtime form of the WithNotifier tag, for places the tag pack cannot reach (e.g. the binding +/// factories). +enum class FieldNotifier : std::uint8_t +{ + kEnabled = 0U, + kDisabled +}; + template struct contains_type : std::disjunction...> { diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index 571b48cff..b543b7335 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -255,6 +255,7 @@ cc_library( "//score/mw/com/impl/plumbing:__subpackages__", ], deps = [ + "//score/mw/com/impl:field_tags", "//score/mw/com/impl:instance_identifier", "//score/mw/com/impl:skeleton_base", "@score_baselibs//score/language/futurecpp", @@ -408,6 +409,7 @@ cc_library( deps = [ ":i_skeleton_field_binding_factory", ":skeleton_service_element_binding_factory_impl", + "//score/mw/com/impl:field_tags", "@score_baselibs//score/language/futurecpp", ], ) @@ -509,6 +511,7 @@ cc_library( deps = [ "skeleton_field_binding_factory_impl", ":i_skeleton_field_binding_factory", + "//score/mw/com/impl:field_tags", "@score_baselibs//score/language/futurecpp", ], ) @@ -614,6 +617,7 @@ cc_library( ], deps = [ ":i_skeleton_field_binding_factory", + "//score/mw/com/impl:field_tags", "@googletest//:gtest", ], ) @@ -845,6 +849,7 @@ cc_unit_test( "//score/mw/com/impl/bindings/mock_binding", "//score/mw/com/impl/configuration/test:configuration_store", "//score/mw/com/impl/test:dummy_instance_identifier_builder", + "@score_baselibs//score/mw/log:recorder_mock", ], ) diff --git a/score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h b/score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h index a9ea3407d..7f3260ff9 100644 --- a/score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h +++ b/score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h @@ -13,6 +13,7 @@ #ifndef SCORE_MW_COM_IMPL_PLUMBING_I_SKELETON_FIELD_BINDING_FACTORY_H #define SCORE_MW_COM_IMPL_PLUMBING_I_SKELETON_FIELD_BINDING_FACTORY_H +#include "score/mw/com/impl/field_tags.h" #include "score/mw/com/impl/handle_type.h" #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/skeleton_base.h" @@ -44,10 +45,12 @@ class ISkeletonFieldBindingFactory /// \param identifier The instance identifier containing the binding information. /// \param parent A reference to the Skeleton which owns this event. /// \param field_name The binding unspecific name of the field inside the skeleton denoted by instance identifier. + /// \param field_notifier An enum indicating whether the field was declared with WithNotifier or not. /// \return An instance of SkeletonEventBinding or nullptr in case of an error. virtual auto CreateEventBinding(const InstanceIdentifier& identifier, SkeletonBinding& parent_binding, - const std::string_view field_name) noexcept + const std::string_view field_name, + const FieldNotifier field_notifier) noexcept -> std::unique_ptr> = 0; }; diff --git a/score/mw/com/impl/plumbing/skeleton_event_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_event_binding_factory_impl.h index b6847ba13..45be6f6aa 100644 --- a/score/mw/com/impl/plumbing/skeleton_event_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_event_binding_factory_impl.h @@ -54,9 +54,8 @@ auto SkeletonEventBindingFactoryImpl::Create(const InstanceIdentifie const std::string_view event_name) noexcept -> std::unique_ptr> { - return CreateSkeletonEventOrField, - lola::SkeletonEvent, - ServiceElementType::EVENT>(identifier, parent_binding, event_name); + return CreateSkeletonEvent, lola::SkeletonEvent>( + identifier, parent_binding, event_name); } } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/plumbing/skeleton_field_binding_factory.h b/score/mw/com/impl/plumbing/skeleton_field_binding_factory.h index ce3768d6f..706e0d0e7 100644 --- a/score/mw/com/impl/plumbing/skeleton_field_binding_factory.h +++ b/score/mw/com/impl/plumbing/skeleton_field_binding_factory.h @@ -16,6 +16,7 @@ #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/skeleton_event.h" #include "score/mw/com/impl/configuration/service_type_deployment.h" +#include "score/mw/com/impl/field_tags.h" #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h" #include "score/mw/com/impl/plumbing/skeleton_field_binding_factory_impl.h" @@ -39,9 +40,10 @@ class SkeletonFieldBindingFactory final /// \brief See documentation in ISkeletonFieldBindingFactory. static std::unique_ptr> CreateEventBinding(const InstanceIdentifier& identifier, SkeletonBinding& parent_binding, - const std::string_view field_name) + const std::string_view field_name, + const FieldNotifier field_notifier) { - return instance().CreateEventBinding(identifier, parent_binding, field_name); + return instance().CreateEventBinding(identifier, parent_binding, field_name, field_notifier); } /// \brief Inject a mock ISkeletonFieldBindingFactory. If a mock is injected, then all calls on diff --git a/score/mw/com/impl/plumbing/skeleton_field_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_field_binding_factory_impl.h index 409f01494..16148b7a1 100644 --- a/score/mw/com/impl/plumbing/skeleton_field_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_field_binding_factory_impl.h @@ -15,13 +15,17 @@ #include "score/mw/com/impl/bindings/lola/element_fq_id.h" #include "score/mw/com/impl/bindings/lola/skeleton_event.h" +#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h" +#include "score/mw/com/impl/field_tags.h" #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h" #include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" #include "score/mw/com/impl/skeleton_base.h" #include "score/mw/com/impl/skeleton_event_binding.h" +#include #include +#include #include namespace score::mw::com::impl @@ -36,7 +40,8 @@ class SkeletonFieldBindingFactoryImpl : public ISkeletonFieldBindingFactory> CreateEventBinding( const InstanceIdentifier& identifier, SkeletonBinding& parent_binding, - const std::string_view field_name) noexcept override; + const std::string_view field_name, + const FieldNotifier field_notifier) noexcept override; }; template @@ -50,12 +55,16 @@ template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] auto SkeletonFieldBindingFactoryImpl::CreateEventBinding(const InstanceIdentifier& identifier, SkeletonBinding& parent_binding, - const std::string_view field_name) noexcept + const std::string_view field_name, + const FieldNotifier field_notifier) noexcept -> std::unique_ptr> { - return CreateSkeletonEventOrField, - lola::SkeletonEvent, - ServiceElementType::FIELD>(identifier, parent_binding, field_name); + const std::optional slot_count_override = + field_notifier == FieldNotifier::kDisabled + ? std::optional{lola::kSlotCountForFieldWithoutNotifier} + : std::nullopt; + return CreateSkeletonField, lola::SkeletonEvent>( + identifier, parent_binding, field_name, slot_count_override); } } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/plumbing/skeleton_field_binding_factory_mock.h b/score/mw/com/impl/plumbing/skeleton_field_binding_factory_mock.h index 60146b52e..67dcf20f4 100644 --- a/score/mw/com/impl/plumbing/skeleton_field_binding_factory_mock.h +++ b/score/mw/com/impl/plumbing/skeleton_field_binding_factory_mock.h @@ -13,6 +13,7 @@ #ifndef SCORE_MW_COM_IMPL_PLUMBING_SKELETON_FIELD_BINDING_FACTORY_MOCK_H #define SCORE_MW_COM_IMPL_PLUMBING_SKELETON_FIELD_BINDING_FACTORY_MOCK_H +#include "score/mw/com/impl/field_tags.h" #include "score/mw/com/impl/plumbing/i_skeleton_field_binding_factory.h" #include @@ -26,7 +27,7 @@ class SkeletonFieldBindingFactoryMock : public ISkeletonFieldBindingFactory>, CreateEventBinding, - (const InstanceIdentifier&, SkeletonBinding&, const std::string_view), + (const InstanceIdentifier&, SkeletonBinding&, const std::string_view, const FieldNotifier), (noexcept, override)); }; diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 8ff0a0358..07ba67e79 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -30,8 +30,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -43,10 +45,28 @@ namespace score::mw::com::impl namespace detail { -inline lola::SkeletonEventProperties GetSkeletonEventProperties( - const LolaEventInstanceDeployment& lola_event_instance_deployment) +// slot_count_override is defaulted because only fields override the slot count. Events and generic events use the +// configured count, so they call this without it and need not know the parameter exists. +inline lola::SkeletonEventProperties CreateSkeletonEventProperties( + const LolaEventInstanceDeployment& lola_event_instance_deployment, + const std::optional slot_count_override = std::nullopt) { - if (!lola_event_instance_deployment.GetNumberOfSampleSlots().has_value()) + std::optional effective_slot_count{}; + if (slot_count_override.has_value()) + { + static_assert(lola::kSlotCountForFieldWithoutNotifier + + std::numeric_limits::max() <= + std::numeric_limits::max(), + "Slot count for a field without notifier must leave room for the tracing slots."); + + effective_slot_count = static_cast( + slot_count_override.value() + lola_event_instance_deployment.GetNumberOfTracingSlots()); + } + else + { + effective_slot_count = lola_event_instance_deployment.GetNumberOfSampleSlots(); + } + if (!effective_slot_count.has_value()) { score::mw::log::LogFatal("lola") << "Could not create SkeletonEventProperties from ServiceElementInstanceDeployment. Number of sample slots " @@ -61,15 +81,17 @@ inline lola::SkeletonEventProperties GetSkeletonEventProperties( "not specified in the configuration. Terminating."; std::terminate(); } - return lola::SkeletonEventProperties{lola_event_instance_deployment.GetNumberOfSampleSlots().value(), + return lola::SkeletonEventProperties{effective_slot_count.value(), lola_event_instance_deployment.max_subscribers_.value(), lola_event_instance_deployment.enforce_max_samples_}; } -inline lola::SkeletonEventProperties GetSkeletonEventProperties( - const LolaFieldInstanceDeployment& lola_field_instance_deployment) +inline lola::SkeletonEventProperties CreateSkeletonEventProperties( + const LolaFieldInstanceDeployment& lola_field_instance_deployment, + const std::optional slot_count_override = std::nullopt) { - return GetSkeletonEventProperties(lola_field_instance_deployment.lola_event_instance_deployment_); + return CreateSkeletonEventProperties(lola_field_instance_deployment.lola_event_instance_deployment_, + slot_count_override); } } // namespace detail @@ -83,9 +105,11 @@ template slot_count_override = std::nullopt) noexcept -> std::unique_ptr { static_assert((element_type == ServiceElementType::EVENT) || (element_type == ServiceElementType::FIELD)); @@ -94,7 +118,7 @@ auto CreateSkeletonEventOrField(const InstanceIdentifier& identifier, using ReturnType = std::unique_ptr; auto visitor = score::cpp::overload( - [identifier_view, &parent_binding, &service_element_name]( + [identifier_view, &parent_binding, &service_element_name, slot_count_override]( const LolaServiceTypeDeployment& lola_service_type_deployment) -> ReturnType { auto* const lola_parent = dynamic_cast(&parent_binding); if (lola_parent == nullptr) @@ -111,8 +135,24 @@ auto CreateSkeletonEventOrField(const InstanceIdentifier& identifier, const std::string service_element_name_str{service_element_name}; const auto& lola_service_element_instance_deployment = GetServiceElementInstanceDeployment( lola_service_instance_deployment, service_element_name_str); + + if constexpr (element_type == ServiceElementType::FIELD) + { + const bool slot_count_overridden = slot_count_override.has_value(); + + const bool sample_slot_count_configured = + lola_service_element_instance_deployment.lola_event_instance_deployment_ + .GetNumberOfSampleSlotsExcludingTracingSlot() + .has_value(); + if (slot_count_overridden && sample_slot_count_configured) + { + score::mw::log::LogWarn("lola") + << "Field '" << service_element_name_str + << "' has WithNotifier disabled; configured numberOfSampleSlots is ignored."; + } + } const lola::SkeletonEventProperties skeleton_event_properties = - detail::GetSkeletonEventProperties(lola_service_element_instance_deployment); + detail::CreateSkeletonEventProperties(lola_service_element_instance_deployment, slot_count_override); const auto lola_service_element_id = GetServiceElementId(lola_service_type_deployment, service_element_name_str); @@ -131,6 +171,33 @@ auto CreateSkeletonEventOrField(const InstanceIdentifier& identifier, return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); } +/// \brief Creates the binding for a skeleton event. +template +auto CreateSkeletonEvent(const InstanceIdentifier& identifier, + SkeletonBinding& parent_binding, + const std::string_view event_name) noexcept -> std::unique_ptr +{ + return CreateSkeletonServiceElement(identifier, parent_binding, event_name); +} + +/// \brief Creates the binding for a skeleton field. A field without a notifier passes a slot count override so the +/// binding uses a fixed count instead of the configured one. +template +auto CreateSkeletonField( + const InstanceIdentifier& identifier, + SkeletonBinding& parent_binding, + const std::string_view field_name, + const std::optional slot_count_override) noexcept + -> std::unique_ptr +{ + return CreateSkeletonServiceElement( + identifier, parent_binding, field_name, slot_count_override); +} + /// @brief Overload for typed skeletons (which do not have a DataTypeMetaInfo). template // coverity[autosar_cpp14_a15_5_3_violation : FALSE] @@ -163,7 +230,7 @@ auto CreateGenericSkeletonEventOrField(const InstanceIdentifier& identifier, const auto& lola_service_element_instance_deployment = GetServiceElementInstanceDeployment( lola_service_instance_deployment, std::string{service_element_name}); const lola::SkeletonEventProperties skeleton_event_properties = - detail::GetSkeletonEventProperties(lola_service_element_instance_deployment); + detail::CreateSkeletonEventProperties(lola_service_element_instance_deployment); const auto lola_service_element_id = GetServiceElementId(lola_service_type_deployment, std::string{service_element_name}); diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_test.cpp b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_test.cpp index c9c111e2d..681da9116 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_test.cpp +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_test.cpp @@ -23,10 +23,15 @@ #include "score/mw/com/impl/skeleton_binding.h" #include "score/mw/com/impl/test/dummy_instance_identifier_builder.h" +#include "score/mw/log/logging.h" +#include "score/mw/log/recorder_mock.h" + #include #include #include +#include #include +#include namespace score::mw::com::impl { @@ -98,7 +103,7 @@ class SkeletonServiceElementBindingFactoryParamaterisedFixture instance_identifier, skeleton_binding, kDummyEventName); case ServiceElementType::FIELD: return SkeletonFieldBindingFactory::CreateEventBinding( - instance_identifier, skeleton_binding, kDummyFieldName); + instance_identifier, skeleton_binding, kDummyFieldName, FieldNotifier::kEnabled); case ServiceElementType::METHOD: case ServiceElementType::INVALID: default: @@ -280,5 +285,130 @@ TEST_P(SkeletonServiceElementBindingFactoryParamaterisedDeathTest, ".*"); } +// Slot count for a field depends only on whether it has a notifier (WithNotifier): +// with notifier + configured slots -> configured value is used, no warning +// with notifier + missing slots -> terminates (see death test above) +// without notifier + configured slots -> fixed count is used, configured value ignored with a warning +// without notifier + missing slots -> fixed count is used, no warning +// The observable behaviour (binding created, warning emitted) is checked below through the factory. The exact slot +// numbers, which the built binding does not expose, are pinned at the CreateSkeletonEventProperties helper further +// down. + +class SkeletonFieldNotifierSlotCountFixture : public lola::SkeletonMockedMemoryFixture +{ + protected: + void SetUp() override + { + lola::SkeletonMockedMemoryFixture::SetUp(); + score::mw::log::SetLogRecorder(&recorder_mock_); + } + + void TearDown() override + { + score::mw::log::SetLogRecorder(nullptr); + lola::SkeletonMockedMemoryFixture::TearDown(); + } + + InstanceIdentifier MakeFieldInstanceIdentifier(const std::optional configured_field_slots) + { + config_store_.emplace( + kInstanceSpecifier, + make_ServiceIdentifierType("/a/service/somewhere/out/there", 13U, 37U), + QualityType::kASIL_QM, + kLolaServiceTypeDeployment, + LolaServiceInstanceDeployment{ + LolaServiceInstanceId{kInstanceId}, + {{kDummyEventName, LolaEventInstanceDeployment{{1U}, {3U}, 1U, true, 0U}}}, + {{kDummyFieldName, + LolaFieldInstanceDeployment{ + LolaEventInstanceDeployment{configured_field_slots, {3U}, 1U, true, 0U}, false, false}}}}); + return config_store_->GetInstanceIdentifier(); + } + + std::optional config_store_{}; + score::mw::log::RecorderMock recorder_mock_{}; +}; + +TEST_F(SkeletonFieldNotifierSlotCountFixture, FieldWithoutNotifierIsCreatedWithoutAConfiguredSlotCount) +{ + // Given a lola skeleton whose field does not configure numberOfSampleSlots + const auto instance_identifier = MakeFieldInstanceIdentifier(std::nullopt); + InitialiseSkeleton(instance_identifier); + + // and no warning about an ignored slot count is expected + EXPECT_CALL(recorder_mock_, StartRecord(std::string_view{"lola"}, score::mw::log::LogLevel::kWarn)).Times(0); + + // When creating the binding for a field whose notifier is disabled + const auto unit = SkeletonFieldBindingFactory::CreateEventBinding( + instance_identifier, *skeleton_, kDummyFieldName, FieldNotifier::kDisabled); + + // Then a valid binding is created (with a notifier a missing slot count would terminate, see + // ConstructingWithoutNumberOfSamplesSlotsInServiceInstanceDeploymentTerminatestes test above) + EXPECT_NE(unit, nullptr); +} + +TEST_F(SkeletonFieldNotifierSlotCountFixture, FieldWithoutNotifierWarnsWhenASlotCountIsConfigured) +{ + // Given a lola skeleton whose field configures a numberOfSampleSlots that a notifier-less field cannot use + const auto instance_identifier = MakeFieldInstanceIdentifier(std::uint16_t{5U}); + InitialiseSkeleton(instance_identifier); + + // Given a warning about the ignored slot count is expected + EXPECT_CALL(recorder_mock_, StartRecord(std::string_view{"lola"}, score::mw::log::LogLevel::kWarn)).Times(1); + + // When creating the binding for a field whose notifier is disabled + const auto unit = SkeletonFieldBindingFactory::CreateEventBinding( + instance_identifier, *skeleton_, kDummyFieldName, FieldNotifier::kDisabled); + + // Then a valid binding is still created + EXPECT_NE(unit, nullptr); +} + +TEST_F(SkeletonFieldNotifierSlotCountFixture, FieldWithNotifierDoesNotWarnAboutTheConfiguredSlotCount) +{ + // Given a lola skeleton whose field configures a numberOfSampleSlots + const auto instance_identifier = MakeFieldInstanceIdentifier(std::uint16_t{5U}); + InitialiseSkeleton(instance_identifier); + + // Given no warning is expected because an enabled notifier uses the configured count + EXPECT_CALL(recorder_mock_, StartRecord(std::string_view{"lola"}, score::mw::log::LogLevel::kWarn)).Times(0); + + // When creating the binding for a field whose notifier is enabled + const auto unit = SkeletonFieldBindingFactory::CreateEventBinding( + instance_identifier, *skeleton_, kDummyFieldName, FieldNotifier::kEnabled); + + // Then a valid binding is created + EXPECT_NE(unit, nullptr); +} + +// There is no Public API to check the slot count of a binding, so the exact slot count is pinned at the +// CreateSkeletonEventProperties helper. + +TEST(CreateSkeletonEventPropertiesTest, UsesConfiguredSlotCountWhenNoOverrideIsGiven) +{ + // Given a field deployment configuring 5 sample slots and no tracing slots + const LolaFieldInstanceDeployment field_deployment{ + LolaEventInstanceDeployment{{5U}, {3U}, 1U, true, 0U}, false, false}; + + // When creating the SkeletonEventProperties without a slot count override + const auto properties = detail::CreateSkeletonEventProperties(field_deployment, std::nullopt); + + // Then the configured slot count is used + EXPECT_EQ(properties.number_of_slots, 5U); +} + +TEST(CreateSkeletonEventPropertiesTest, OverrideReplacesConfiguredCountAndTracingSlotsAreAddedOnTop) +{ + // Given a field deployment configuring 9 sample slots and 3 tracing slots + const LolaFieldInstanceDeployment field_deployment{ + LolaEventInstanceDeployment{{9U}, {3U}, 1U, true, 3U}, false, false}; + + // When creating the SkeletonEventProperties with a slot count override of 2 + const auto properties = detail::CreateSkeletonEventProperties(field_deployment, std::uint16_t{2U}); + + // Then the override replaces the configured 9 and the tracing slots are added on top (2 + 3) + EXPECT_EQ(properties.number_of_slots, 5U); +} + } // namespace } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/skeleton_base_test.cpp b/score/mw/com/impl/skeleton_base_test.cpp index 7c09701d3..015feb28f 100644 --- a/score/mw/com/impl/skeleton_base_test.cpp +++ b/score/mw/com/impl/skeleton_base_test.cpp @@ -117,7 +117,7 @@ class SkeletonBaseFixture : public ::testing::Test Create(instance_identifier, _, kDummyEventName2)) .WillOnce(Return(ByMove(std::move(skeleton_event_mock_ptr_2)))); EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(instance_identifier, _, kDummyFieldName)) + CreateEventBinding(instance_identifier, _, kDummyFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_mock_ptr)))); EXPECT_CALL(*event_binding_mock_1_, GetBindingType()).WillOnce(Return(BindingType::kLoLa)); @@ -620,7 +620,7 @@ TEST_F(SkeletonBaseOfferFixture, NoStopOfferOnErrorIdentifier) Create(instance_identifier, _, kDummyEventName2)) .Times(0); EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(GetInstanceIdentifierWithoutBinding(), _, kDummyFieldName)) + CreateEventBinding(GetInstanceIdentifierWithoutBinding(), _, kDummyFieldName, _)) .Times(0); // Given a constructed Skeleton with a invalid identifier diff --git a/score/mw/com/impl/skeleton_field.h b/score/mw/com/impl/skeleton_field.h index 41a397c20..25a91bdad 100644 --- a/score/mw/com/impl/skeleton_field.h +++ b/score/mw/com/impl/skeleton_field.h @@ -219,17 +219,20 @@ class SkeletonFieldImpl : public SkeletonFieldBase static constexpr bool kHasGetter = contains_type::value; static constexpr bool kHasSetter = contains_type::value; + static constexpr bool kHasNotifier = contains_type::value; static std::unique_ptr> MakeSkeletonEvent(SkeletonBase& parent, const std::string_view field_name) { - // No kHasNotifier: the SkeletonEvent is always built because it provides Update/Allocate. const SkeletonBaseView skeleton_base_view{parent}; return std::make_unique>( parent, field_name, SkeletonFieldBindingFactory::CreateEventBinding( - skeleton_base_view.GetAssociatedInstanceIdentifier(), skeleton_base_view.GetBinding(), field_name), + skeleton_base_view.GetAssociatedInstanceIdentifier(), + skeleton_base_view.GetBinding(), + field_name, + kHasNotifier ? FieldNotifier::kEnabled : FieldNotifier::kDisabled), typename SkeletonEvent::FieldOnlyConstructorEnabler{}); } diff --git a/score/mw/com/impl/skeleton_field_test.cpp b/score/mw/com/impl/skeleton_field_test.cpp index af6fc25c8..39cafe02d 100644 --- a/score/mw/com/impl/skeleton_field_test.cpp +++ b/score/mw/com/impl/skeleton_field_test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -88,7 +89,7 @@ class SkeletonFieldTestFixture : public ::testing::Test void SetUp() override { ON_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(kInstanceIdWithLolaBinding, _, kFieldName)) + CreateEventBinding(kInstanceIdWithLolaBinding, _, kFieldName, _)) .WillByDefault(InvokeWithoutArgs([this]() { return std::make_unique>( skeleton_field_binding_mock_); @@ -688,7 +689,7 @@ TEST(SkeletonFieldInitialValueTest, MoveAssigningFieldBeforePrepareOfferWillKeep auto skeleton_field_binding_mock_ptr = std::make_unique>(); auto& skeleton_field_binding_mock = *skeleton_field_binding_mock_ptr; EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, - CreateEventBinding(kInstanceIdWithLolaBinding, _, kFieldName)) + CreateEventBinding(kInstanceIdWithLolaBinding, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_binding_mock_ptr)))); EXPECT_CALL(skeleton_field_binding_mock, GetBindingType()).WillOnce(Return(BindingType::kLoLa)); @@ -719,7 +720,8 @@ TEST(SkeletonFieldInitialValueTest, MoveAssigningFieldBeforePrepareOfferWillKeep // and Expecting that a second SkeletonField binding is created auto skeleton_field_binding_mock_ptr_2 = std::make_unique>(); auto& skeleton_field_binding_mock_2 = *skeleton_field_binding_mock_ptr_2; - EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, CreateEventBinding(identifier2, _, kFieldName)) + EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, + CreateEventBinding(identifier2, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_binding_mock_ptr_2)))); EXPECT_CALL(skeleton_field_binding_mock_2, GetBindingType()).WillOnce(Return(BindingType::kLoLa)); @@ -1212,5 +1214,64 @@ TEST_F(SkeletonFieldMoveConstructionFixture, captured_set_handler_.value()(in_span, out_span); } +class MyNotifierSkeleton : public SkeletonBase +{ + public: + using SkeletonBase::SkeletonBase; + SkeletonField field_{*this, kFieldName}; +}; + +class MyGetterOnlySkeleton : public SkeletonBase +{ + public: + using SkeletonBase::SkeletonBase; + SkeletonField field_{*this, kFieldName}; +}; + +class SkeletonFieldNotifierTest : public ::testing::Test +{ + protected: + void SetUp() override + { + ON_CALL(skeleton_method_binding_factory_mock_guard_.factory_mock_, Create(_, _, _, MethodType::kGet)) + .WillByDefault(InvokeWithoutArgs([]() { + return std::make_unique(); + })); + + ON_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, CreateEventBinding(_, _, kFieldName, _)) + .WillByDefault(Invoke([this](const InstanceIdentifier&, + SkeletonBinding&, + const std::string_view, + const FieldNotifier field_notifier) { + notifier_at_factory_call_ = field_notifier; + return std::make_unique>(); + })); + } + + RuntimeMockGuard runtime_mock_guard_{}; + SkeletonFieldBindingFactoryMockGuard skeleton_field_binding_factory_mock_guard_{}; + SkeletonMethodBindingFactoryMockGuard skeleton_method_binding_factory_mock_guard_{}; + + std::optional notifier_at_factory_call_{}; +}; + +TEST_F(SkeletonFieldNotifierTest, FieldWithNotifierReportsNotifierEnabledToTheFactory) +{ + // When constructing a skeleton whose field has WithNotifier in its tag pack + MyNotifierSkeleton skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; + + // Then the field binding factory is told that the notifier is enabled + EXPECT_EQ(notifier_at_factory_call_, FieldNotifier::kEnabled); +} + +TEST_F(SkeletonFieldNotifierTest, FieldWithoutNotifierReportsNotifierDisabledToTheFactory) +{ + // When constructing a skeleton whose field has no WithNotifier in its tag pack + MyGetterOnlySkeleton skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; + + // Then the field binding factory is told that the notifier is disabled + EXPECT_EQ(notifier_at_factory_call_, FieldNotifier::kDisabled); +} + } // namespace } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/tracing/test/skeleton_field_tracing_test.cpp b/score/mw/com/impl/tracing/test/skeleton_field_tracing_test.cpp index 5c4e14a21..7f8068c2f 100644 --- a/score/mw/com/impl/tracing/test/skeleton_field_tracing_test.cpp +++ b/score/mw/com/impl/tracing/test/skeleton_field_tracing_test.cpp @@ -85,7 +85,7 @@ TEST(SkeletonFieldTracingTest, TracePointsAreDisabledIfConfigNotReturnedByRuntim // Expecting that a SkeletonField binding is created auto skeleton_field_binding_mock_ptr = std::make_unique>(); EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, - CreateEventBinding(kInstanceIdentifier, _, kFieldName)) + CreateEventBinding(kInstanceIdentifier, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_binding_mock_ptr)))); EXPECT_CALL(runtime_mock_guard.runtime_mock_, GetTracingFilterConfig()).WillOnce(Return(nullptr)); @@ -125,7 +125,7 @@ TEST_P(SkeletonFieldTracingParamaterisedFixture, TracePointsAreCorrectlySet) auto skeleton_field_binding_mock_ptr = std::make_unique>(); auto& skeleton_field_binding_mock = *skeleton_field_binding_mock_ptr; EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, - CreateEventBinding(kInstanceIdentifier, _, kFieldName)) + CreateEventBinding(kInstanceIdentifier, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_binding_mock_ptr)))); EXPECT_CALL(runtime_mock_guard.runtime_mock_, GetTracingFilterConfig()).WillOnce(Return(&tracing_mock)); EXPECT_CALL(runtime_mock_guard.runtime_mock_, GetTracingRuntime()).WillOnce(Return(&tracing_runtime_mock)); @@ -204,7 +204,7 @@ class SkeletonFieldTracingFixture : public ::testing::Test { auto skeleton_event_binding_mock_ptr = std::make_unique>(); mock_skeleton_field_binding_ = skeleton_event_binding_mock_ptr.get(); - EXPECT_CALL(factory_mock_guard.factory_mock_, CreateEventBinding(kInstanceIdentifier, _, kFieldName)) + EXPECT_CALL(factory_mock_guard.factory_mock_, CreateEventBinding(kInstanceIdentifier, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_event_binding_mock_ptr)))); } diff --git a/score/mw/com/impl/traits_test.cpp b/score/mw/com/impl/traits_test.cpp index ed551f949..25512a9f1 100644 --- a/score/mw/com/impl/traits_test.cpp +++ b/score/mw/com/impl/traits_test.cpp @@ -689,7 +689,7 @@ class SkeletonCreationFixture : public ::testing::Test // By default the Create call on the SkeletonFieldBindingFactory returns valid bindings. ON_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(identifier_with_valid_binding_, _, kFieldName)) + CreateEventBinding(identifier_with_valid_binding_, _, kFieldName, _)) .WillByDefault(Return(ByMove(std::move(skeleton_field_binding_mock_ptr)))); // By default the Create call on the SkeletonMethodBindingFactory returns valid bindings. @@ -761,7 +761,7 @@ TEST_F(GeneratedSkeletonCreationInstanceSpecifierTestFixture, Create(identifier_with_valid_binding_, _, kEventName)) .WillOnce(Return(ByMove(std::move(skeleton_event_binding_mock_ptr)))); EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(identifier_with_valid_binding_, _, kFieldName)) + CreateEventBinding(identifier_with_valid_binding_, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_binding_mock_ptr)))); EXPECT_CALL(skeleton_method_binding_factory_mock_guard_.factory_mock_, Create(identifier_with_valid_binding_, _, kFieldName, MethodType::kSet)) @@ -835,7 +835,7 @@ TEST_F(GeneratedSkeletonCreationInstanceSpecifierTestFixture, ReturnErrorWhenCre // Expecting that the Create call on the SkeletonFieldBindingFactory returns an invalid binding for the field. EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(identifier_with_valid_binding_, _, kFieldName)) + CreateEventBinding(identifier_with_valid_binding_, _, kFieldName, _)) .WillOnce(Return(ByMove(nullptr))); // When constructing a skeleton with an InstanceSpecifier @@ -914,7 +914,7 @@ TEST_F(GeneratedSkeletonCreationInstanceIdentifierTestFixture, ConstructingFromE Create(identifier_with_valid_binding_, _, kEventName)) .WillOnce(Return(ByMove(std::move(skeleton_event_binding_mock_ptr)))); EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(identifier_with_valid_binding_, _, kFieldName)) + CreateEventBinding(identifier_with_valid_binding_, _, kFieldName, _)) .WillOnce(Return(ByMove(std::move(skeleton_field_binding_mock_ptr)))); // When constructing a skeleton with an InstanceIdentifier @@ -979,7 +979,7 @@ TEST_F(GeneratedSkeletonCreationInstanceIdentifierTestFixture, ConstructingFromI // Expecting that the Create call on the SkeletonFieldBindingFactory returns an invalid binding for the field. EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, - CreateEventBinding(identifier_with_valid_binding_, _, kFieldName)) + CreateEventBinding(identifier_with_valid_binding_, _, kFieldName, _)) .WillOnce(Return(ByMove(nullptr))); // When constructing a skeleton with an InstanceIdentifier @@ -1150,7 +1150,7 @@ class GeneratedSkeletonStopOfferServiceRaiiFixture : public SkeletonCreationFixt EXPECT_CALL(skeleton_event_binding_factory_mock_guard_.factory_mock_, Create(_, _, _)) .WillOnce(Return(ByMove( std::make_unique>(skeleton_event_binding_mock_)))); - EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, CreateEventBinding(_, _, _)) + EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, CreateEventBinding(_, _, _, _)) .WillOnce(Return(ByMove( std::make_unique>(skeleton_field_binding_mock_)))); EXPECT_CALL(skeleton_method_binding_factory_mock_guard_.factory_mock_, Create(_, _, _, MethodType::kSet)) @@ -1168,7 +1168,7 @@ class GeneratedSkeletonStopOfferServiceRaiiFixture : public SkeletonCreationFixt EXPECT_CALL(skeleton_event_binding_factory_mock_guard_.factory_mock_, Create(_, _, _)) .WillOnce(Return(ByMove( std::make_unique>(skeleton_event_binding_mock_2_)))); - EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, CreateEventBinding(_, _, _)) + EXPECT_CALL(skeleton_field_binding_factory_mock_guard_.factory_mock_, CreateEventBinding(_, _, _, _)) .WillOnce(Return(ByMove( std::make_unique>(skeleton_field_binding_mock_2_)))); EXPECT_CALL(skeleton_method_binding_factory_mock_guard_.factory_mock_, Create(_, _, _, MethodType::kSet))