From c78ab9080ea00cde027557252887dd630b2e9e2e Mon Sep 17 00:00:00 2001 From: Ahmed Mousa Date: Mon, 6 Jul 2026 13:39:58 +0200 Subject: [PATCH 1/4] mw/com: cover decision false branch for missing instance id Issue: SWP-270960 --- .../lola_service_instance_identifier_test.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/score/mw/com/impl/bindings/lola/service_discovery/lola_service_instance_identifier_test.cpp b/score/mw/com/impl/bindings/lola/service_discovery/lola_service_instance_identifier_test.cpp index e89a8b18a..158252b8b 100644 --- a/score/mw/com/impl/bindings/lola/service_discovery/lola_service_instance_identifier_test.cpp +++ b/score/mw/com/impl/bindings/lola/service_discovery/lola_service_instance_identifier_test.cpp @@ -69,6 +69,23 @@ TEST(LolaServiceInstanceIdentifierTest, ConstructWithEnrichedInstanceIdentifier) EXPECT_EQ(identifier.GetInstanceId().value(), kLolaInstanceId); } +TEST(LolaServiceInstanceIdentifierTest, ConstructWithEnrichedInstanceIdentifierWithoutInstanceId) +{ + // Given an enriched instance identifier without an instance id (deployment has no instance id) + const ServiceInstanceDeployment kLolaServiceInstanceDeploymentNoId{ + kService, LolaServiceInstanceDeployment{}, QualityType::kASIL_QM, kInstanceSpecifier}; + const InstanceIdentifier kLolaInstanceIdentifierNoId = + make_InstanceIdentifier(kLolaServiceInstanceDeploymentNoId, kLolaServiceTypeDeployment); + EnrichedInstanceIdentifier enriched_instance_identifier{kLolaInstanceIdentifierNoId}; + + // When creating an identifier + LolaServiceInstanceIdentifier identifier{enriched_instance_identifier}; + + // Then the service id is set but the instance id is absent + EXPECT_EQ(identifier.GetServiceId(), kLolaServiceId); + ASSERT_FALSE(identifier.GetInstanceId().has_value()); +} + TEST(LolaServiceInstanceIdentifierTest, ComparesEqual) { // Given two identical identifiers From 5ca2ef53b6d5be26baac30411a2900083f463b2c Mon Sep 17 00:00:00 2001 From: Ahmed Mousa Date: Mon, 6 Jul 2026 13:44:46 +0200 Subject: [PATCH 2/4] mw/com: suppress unreachable LCOV branch warning Issue: SWP-270960 --- .../com/impl/tracing/configuration/tracing_filter_config.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp b/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp index 68effffdb..7fa98ea73 100644 --- a/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp +++ b/score/mw/com/impl/tracing/configuration/tracing_filter_config.cpp @@ -256,7 +256,11 @@ std::size_t FindNumberOfTracingSlots( return e.GetNumberOfTracingSlots(); }); } + // LCOV_EXCL_BR_START (Defensive programming: FindNumberOfTracingSlots is only called internally with + // ServiceElementType::EVENT or ServiceElementType::FIELD. Entering the false branch of this check is + // therefore unreachable.) else if (service_element_type == ServiceElementType::FIELD) + // LCOV_EXCL_BR_STOP { slots_per_tracing_point = get_number_of_tracing_slots(lola_service_instance_deployment->fields_, From 0f80d4c8d6ec9437327d8b5ff1e35a79f021c672 Mon Sep 17 00:00:00 2001 From: Ahmed Mousa Date: Mon, 6 Jul 2026 13:47:34 +0200 Subject: [PATCH 3/4] mw/com: suppress DCOV branch warnings for range-for loops Issue: SWP-270960 --- .../tracing_filter_config_parser.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser.cpp b/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser.cpp index 1dcc62393..fa2765606 100644 --- a/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser.cpp +++ b/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser.cpp @@ -159,7 +159,11 @@ bool IsOptionalBoolPropertyEnabled(const score::json::Object& json, const std::s std::set GetInstancesOfServiceType(const Configuration& configuration, std::string_view service_type) { std::set result{}; + // LCOV_EXCL_BR_START (Tool incorrectly marks the range-for loop as "Decision couldn't be analyzed" despite all + // lines within the loop being covered. We also have a test for the case where GetServiceInstances() is empty. + // Suppression can be removed when the tooling bug is fixed.) for (const auto& service_instance_element : configuration.GetServiceInstances()) + // LCOV_EXCL_BR_STOP { if (service_instance_element.second.service_.ToString() == service_type) { @@ -186,7 +190,11 @@ std::set GetElementNamesOfServiceType(const std::string_view s [&result, element_type](const LolaServiceTypeDeployment& lola_service_deployment) { if (element_type == ServiceElementType::EVENT) { + // LCOV_EXCL_BR_START (Tool incorrectly marks the range-for loop as "Decision couldn't be analyzed" + // despite all lines within the loop being covered. We also have a test for the case where + // lola_service_deployment.events_ is empty. Suppression can be removed when the tooling bug is fixed.) for (const auto& event : lola_service_deployment.events_) + // LCOV_EXCL_BR_STOP { score::cpp::ignore = result.insert(event.first); } @@ -197,8 +205,11 @@ std::set GetElementNamesOfServiceType(const std::string_view s else if (element_type == ServiceElementType::FIELD) // LCOV_EXCL_BR_STOP { - + // LCOV_EXCL_BR_START (Tool incorrectly marks the range-for loop as "Decision couldn't be analyzed" + // despite all lines within the loop being covered. We also have a test for the case where + // lola_service_deployment.fields_ is empty. Suppression can be removed when the tooling bug is fixed.) for (const auto& field : lola_service_deployment.fields_) + // LCOV_EXCL_BR_STOP { score::cpp::ignore = result.insert(field.first); } @@ -222,7 +233,12 @@ std::set GetElementNamesOfServiceType(const std::string_view s // LCOV_EXCL_STOP ); + // LCOV_EXCL_BR_START (Tool incorrectly marks the range-for loop as "Decision couldn't be analyzed". The false + // case (empty GetServiceTypes()) is structurally unreachable: GetElementNamesOfServiceType is only called from + // ParseEvents/ParseFields which are reached only after the service type was found in GetServiceTypes(). + // Suppression can be removed when the tooling bug is fixed.) for (const auto& service_type_deployment : configuration.GetServiceTypes()) + // LCOV_EXCL_BR_STOP { const ServiceIdentifierTypeView current_service_type_view{service_type_deployment.first}; if (current_service_type_view.getInternalTypeName() == service_type) From a63ebb28cb6d203d6733820e2d1b440f5259c7cf Mon Sep 17 00:00:00 2001 From: Ahmed Mousa Date: Mon, 6 Jul 2026 13:48:49 +0200 Subject: [PATCH 4/4] mw/com: add tests for empty-collection paths Issue: SWP-270960 --- .../tracing_filter_config_parser_test.cpp | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser_test.cpp b/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser_test.cpp index 52c032758..c67e04e93 100644 --- a/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser_test.cpp +++ b/score/mw/com/impl/tracing/configuration/tracing_filter_config_parser_test.cpp @@ -1310,5 +1310,133 @@ TEST_F(TraceConfigParserFixture, EmptyTracingFilterConfigCanBeParsed) // expect, that there is no error EXPECT_TRUE(result.has_value()); } + +TEST_F(TraceConfigParserFixture, NoTracePointsEnabledWhenServiceTypeHasNoInstances) +{ + // Given a mw_com_config with a service type that has no service instances + auto config_no_instances_json = R"( +{ + "serviceTypes": [{ + "serviceTypeName": "/bmw/ncar/services/TirePressureService", + "version": {"major": 12, "minor": 34}, + "bindings": [{"binding": "SHM", "serviceId": 1234, + "events": [{"eventName": "CurrentPressureFrontLeft", "eventId": 20}]}] + }], + "serviceInstances": [] +} +)"_json; + auto config = score::mw::com::impl::configuration::Parse(std::move(config_no_instances_json)); + + // And a filter config referencing that service type with an event trace point + auto filter_config_json = R"( +{ + "services": [{ + "shortname_path": "/bmw/ncar/services/TirePressureService", + "events": [{"shortname": "CurrentPressureFrontLeft", "trace_send": true}] + }] +} +)"_json; + + // When parsing the filter config + auto result = Parse(std::move(filter_config_json), config); + + // Then parsing succeeds without error (no instances means no trace points are registered) + EXPECT_TRUE(result.has_value()); +} + +TEST_F(TraceConfigParserFixture, EventsInFilterConfigIgnoredWhenServiceTypeHasNoEvents) +{ + // Given a mw_com_config with a service type that has only fields and no events, + // and a service instance for it + auto config_no_events_json = R"( +{ + "serviceTypes": [{ + "serviceTypeName": "/bmw/ncar/services/TirePressureService", + "version": {"major": 12, "minor": 34}, + "bindings": [{"binding": "SHM", "serviceId": 1234, "events": [], + "fields": [{"fieldName": "CurrentTemperatureFrontLeft", "fieldId": 30}]}] + }], + "serviceInstances": [{ + "instanceSpecifier": "abc/abc/TirePressurePort", + "serviceTypeName": "/bmw/ncar/services/TirePressureService", + "version": {"major": 12, "minor": 34}, + "instances": [{ + "instanceId": 1234, + "asil-level": "QM", + "binding": "SHM", + "fields": [{"fieldName": "CurrentTemperatureFrontLeft", + "numberOfSampleSlots": 50, "maxSubscribers": 5, + "numberOfIpcTracingSlots": 1}] + }] + }] +} +)"_json; + auto config = score::mw::com::impl::configuration::Parse(std::move(config_no_events_json)); + + // And a filter config that requests event trace points for a service that has no events + auto filter_config_json = R"( +{ + "services": [{ + "shortname_path": "/bmw/ncar/services/TirePressureService", + "events": [{"shortname": "NonExistentEvent", "trace_send": true}] + }] +} +)"_json; + + // When parsing the filter config + auto result = Parse(std::move(filter_config_json), config); + + // Then parsing succeeds - the event is silently ignored because the service type has no events + EXPECT_TRUE(result.has_value()); +} + +TEST_F(TraceConfigParserFixture, FieldsInFilterConfigIgnoredWhenServiceTypeHasNoFields) +{ + // Given a mw_com_config with a service type that has only events and no fields, + // and a service instance for it + auto config_no_fields_json = R"( +{ + "serviceTypes": [{ + "serviceTypeName": "/bmw/ncar/services/TirePressureService", + "version": {"major": 12, "minor": 34}, + "bindings": [{"binding": "SHM", "serviceId": 1234, + "events": [{"eventName": "CurrentPressureFrontLeft", "eventId": 20}]}] + }], + "serviceInstances": [{ + "instanceSpecifier": "abc/abc/TirePressurePort", + "serviceTypeName": "/bmw/ncar/services/TirePressureService", + "version": {"major": 12, "minor": 34}, + "instances": [{ + "instanceId": 1234, + "asil-level": "QM", + "binding": "SHM", + "events": [{"eventName": "CurrentPressureFrontLeft", + "numberOfSampleSlots": 50, "maxSubscribers": 5, + "numberOfIpcTracingSlots": 1}] + }] + }] +} +)"_json; + auto config = score::mw::com::impl::configuration::Parse(std::move(config_no_fields_json)); + + // And a filter config that requests field trace points for a service that has no fields + auto filter_config_json = R"( +{ + "services": [{ + "shortname_path": "/bmw/ncar/services/TirePressureService", + "fields": [{ + "shortname": "NonExistentField", + "notifier": {"trace_subscribe_send": true} + }] + }] +} +)"_json; + + // When parsing the filter config + auto result = Parse(std::move(filter_config_json), config); + + // Then parsing succeeds - the field is silently ignored because the service type has no fields + EXPECT_TRUE(result.has_value()); +} } // namespace } // namespace score::mw::com::impl::tracing