|
24 | 24 | #include <gmock/gmock.h> |
25 | 25 | #include <gtest/gtest.h> |
26 | 26 | #include <cstdint> |
| 27 | +#include <list> |
27 | 28 | #include <memory> |
| 29 | +#include <optional> |
| 30 | +#include <string> |
28 | 31 | #include <string_view> |
29 | 32 | #include <type_traits> |
30 | 33 | #include <utility> |
@@ -1558,5 +1561,157 @@ TEST(SkeletonFieldSetHandlerTest, SecondRegisterSetHandlerReplacesHandler) |
1558 | 1561 | EXPECT_TRUE(second_callback_called); |
1559 | 1562 | } |
1560 | 1563 |
|
| 1564 | +// ────────────────────────────────────────────────────────────────────────────── |
| 1565 | +// SkeletonField slot-count normalization when WithNotifier is enabled / disabled. |
| 1566 | +// |
| 1567 | +// Behaviour matrix: |
| 1568 | +// WithNotifier | slot count in config | outcome |
| 1569 | +// ------------- | -------------------- | -------------------------------------------------- |
| 1570 | +// enabled | configured | use the provided slot count. |
| 1571 | +// enabled | missing | factory terminates. |
| 1572 | +// disabled | configured | ignore the user set config and log warning, force to 2. |
| 1573 | +// disabled | missing | silently set to 2 |
| 1574 | + |
| 1575 | +class MyNotifierSkeleton : public SkeletonBase |
| 1576 | +{ |
| 1577 | + public: |
| 1578 | + using SkeletonBase::SkeletonBase; |
| 1579 | + SkeletonField<TestSampleType, WithGetter, WithNotifier> field_{*this, kFieldName}; |
| 1580 | +}; |
| 1581 | + |
| 1582 | +class MyGetterOnlySkeleton : public SkeletonBase |
| 1583 | +{ |
| 1584 | + public: |
| 1585 | + using SkeletonBase::SkeletonBase; |
| 1586 | + SkeletonField<TestSampleType, WithGetter> field_{*this, kFieldName}; |
| 1587 | +}; |
| 1588 | + |
| 1589 | +namespace |
| 1590 | +{ |
| 1591 | + |
| 1592 | +InstanceIdentifier MakeInstanceIdentifierWithFieldSlotCount(std::optional<std::uint16_t> slot_count) |
| 1593 | +{ |
| 1594 | + LolaFieldInstanceDeployment field_deployment{slot_count, |
| 1595 | + std::optional<LolaFieldInstanceDeployment::SubscriberCountType>{1U}, |
| 1596 | + std::optional<std::uint8_t>{}, |
| 1597 | + false, |
| 1598 | + LolaFieldInstanceDeployment::TracingSlotSizeType{0U}}; |
| 1599 | + LolaServiceInstanceDeployment lola_deployment{LolaServiceInstanceId{kInstanceId}}; |
| 1600 | + lola_deployment.fields_.emplace(std::string{kFieldName}, field_deployment); |
| 1601 | + // std::list is used (not std::vector) because the constructed InstanceIdentifier holds a pointer back into |
| 1602 | + // the ServiceInstanceDeployment we push here. List preserves references when more elements are added; vector |
| 1603 | + // does not (reallocation invalidates prior references). |
| 1604 | + static std::list<ServiceInstanceDeployment> kept_deployments; |
| 1605 | + kept_deployments.emplace_back( |
| 1606 | + kServiceIdentifier, std::move(lola_deployment), QualityType::kASIL_QM, kInstanceSpecifier); |
| 1607 | + return make_InstanceIdentifier(kept_deployments.back(), kTypeDeployment); |
| 1608 | +} |
| 1609 | + |
| 1610 | +std::optional<std::uint16_t> GetFieldSlotCount(const InstanceIdentifier& identifier) |
| 1611 | +{ |
| 1612 | + const InstanceIdentifierView identifier_view{identifier}; |
| 1613 | + const auto& service_instance_deployment = identifier_view.GetServiceInstanceDeployment(); |
| 1614 | + const auto& lola_deployment = |
| 1615 | + GetServiceInstanceDeploymentBinding<LolaServiceInstanceDeployment>(service_instance_deployment); |
| 1616 | + const auto field_iter = lola_deployment.fields_.find(std::string{kFieldName}); |
| 1617 | + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(field_iter != lola_deployment.fields_.cend()); |
| 1618 | + return field_iter->second.GetNumberOfSampleSlots(); |
| 1619 | +} |
| 1620 | + |
| 1621 | +} // namespace |
| 1622 | + |
| 1623 | +// The sidechannel: SkeletonField writes the override on the parent SkeletonBase before invoking the binding |
| 1624 | +// factory and clears it after. These tests use a lambda on the mocked CreateEventBinding to capture the override |
| 1625 | +// at the moment the factory sees it, then assert (a) the captured value matches the expected behaviour for the |
| 1626 | +// (notifier-tag, configured-slot-count) cell, and (b) the override is cleared again once construction returns. |
| 1627 | +TEST(SkeletonFieldSlotCountTest, NotifierEnabledWithConfiguredSlotCountLeavesOverrideUnset) |
| 1628 | +{ |
| 1629 | + RuntimeMockGuard runtime_mock_guard{}; |
| 1630 | + ON_CALL(runtime_mock_guard.runtime_mock_, GetTracingFilterConfig()).WillByDefault(Return(nullptr)); |
| 1631 | + SkeletonFieldBindingFactoryMockGuard<TestSampleType> skeleton_field_binding_factory_mock_guard{}; |
| 1632 | + |
| 1633 | + std::optional<std::uint16_t> override_at_factory_call{}; |
| 1634 | + EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, CreateEventBinding(_, _, kFieldName)) |
| 1635 | + .WillOnce(Invoke([&](const InstanceIdentifier&, SkeletonBase& parent, const std::string_view) { |
| 1636 | + override_at_factory_call = SkeletonBaseView{parent}.GetSlotCountOverride(); |
| 1637 | + return std::make_unique<mock_binding::SkeletonEvent<TestSampleType>>(); |
| 1638 | + })); |
| 1639 | + |
| 1640 | + const auto identifier = MakeInstanceIdentifierWithFieldSlotCount(std::uint16_t{5U}); |
| 1641 | + |
| 1642 | + MyNotifierSkeleton skeleton{std::make_unique<mock_binding::Skeleton>(), identifier}; |
| 1643 | + |
| 1644 | + EXPECT_FALSE(override_at_factory_call.has_value()); |
| 1645 | + EXPECT_FALSE(SkeletonBaseView{skeleton}.GetSlotCountOverride().has_value()); |
| 1646 | + EXPECT_EQ(GetFieldSlotCount(identifier), std::uint16_t{5U}); |
| 1647 | +} |
| 1648 | + |
| 1649 | +TEST(SkeletonFieldSlotCountTest, NotifierEnabledWithMissingSlotCountLeavesOverrideUnset) |
| 1650 | +{ |
| 1651 | + RuntimeMockGuard runtime_mock_guard{}; |
| 1652 | + ON_CALL(runtime_mock_guard.runtime_mock_, GetTracingFilterConfig()).WillByDefault(Return(nullptr)); |
| 1653 | + SkeletonFieldBindingFactoryMockGuard<TestSampleType> skeleton_field_binding_factory_mock_guard{}; |
| 1654 | + |
| 1655 | + std::optional<std::uint16_t> override_at_factory_call{}; |
| 1656 | + EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, CreateEventBinding(_, _, kFieldName)) |
| 1657 | + .WillOnce(Invoke([&](const InstanceIdentifier&, SkeletonBase& parent, const std::string_view) { |
| 1658 | + override_at_factory_call = SkeletonBaseView{parent}.GetSlotCountOverride(); |
| 1659 | + return std::make_unique<mock_binding::SkeletonEvent<TestSampleType>>(); |
| 1660 | + })); |
| 1661 | + |
| 1662 | + const auto identifier = MakeInstanceIdentifierWithFieldSlotCount(std::nullopt); |
| 1663 | + |
| 1664 | + MyNotifierSkeleton skeleton{std::make_unique<mock_binding::Skeleton>(), identifier}; |
| 1665 | + |
| 1666 | + EXPECT_FALSE(override_at_factory_call.has_value()); |
| 1667 | + EXPECT_FALSE(SkeletonBaseView{skeleton}.GetSlotCountOverride().has_value()); |
| 1668 | + EXPECT_FALSE(GetFieldSlotCount(identifier).has_value()); |
| 1669 | +} |
| 1670 | + |
| 1671 | +TEST(SkeletonFieldSlotCountTest, NotifierDisabledWithConfiguredSlotCountSetsOverrideToDefault) |
| 1672 | +{ |
| 1673 | + RuntimeMockGuard runtime_mock_guard{}; |
| 1674 | + ON_CALL(runtime_mock_guard.runtime_mock_, GetTracingFilterConfig()).WillByDefault(Return(nullptr)); |
| 1675 | + SkeletonFieldBindingFactoryMockGuard<TestSampleType> skeleton_field_binding_factory_mock_guard{}; |
| 1676 | + |
| 1677 | + std::optional<std::uint16_t> override_at_factory_call{}; |
| 1678 | + EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, CreateEventBinding(_, _, kFieldName)) |
| 1679 | + .WillOnce(Invoke([&](const InstanceIdentifier&, SkeletonBase& parent, const std::string_view) { |
| 1680 | + override_at_factory_call = SkeletonBaseView{parent}.GetSlotCountOverride(); |
| 1681 | + return std::make_unique<mock_binding::SkeletonEvent<TestSampleType>>(); |
| 1682 | + })); |
| 1683 | + |
| 1684 | + const auto identifier = MakeInstanceIdentifierWithFieldSlotCount(std::uint16_t{5U}); |
| 1685 | + |
| 1686 | + MyGetterOnlySkeleton skeleton{std::make_unique<mock_binding::Skeleton>(), identifier}; |
| 1687 | + |
| 1688 | + EXPECT_EQ(override_at_factory_call, std::uint16_t{2U}); |
| 1689 | + EXPECT_FALSE(SkeletonBaseView{skeleton}.GetSlotCountOverride().has_value()); |
| 1690 | + // Deployment itself is not mutated by the sidechannel — only the per-skeleton override slot is touched. |
| 1691 | + EXPECT_EQ(GetFieldSlotCount(identifier), std::uint16_t{5U}); |
| 1692 | +} |
| 1693 | + |
| 1694 | +TEST(SkeletonFieldSlotCountTest, NotifierDisabledWithMissingSlotCountSetsOverrideToDefault) |
| 1695 | +{ |
| 1696 | + RuntimeMockGuard runtime_mock_guard{}; |
| 1697 | + ON_CALL(runtime_mock_guard.runtime_mock_, GetTracingFilterConfig()).WillByDefault(Return(nullptr)); |
| 1698 | + SkeletonFieldBindingFactoryMockGuard<TestSampleType> skeleton_field_binding_factory_mock_guard{}; |
| 1699 | + |
| 1700 | + std::optional<std::uint16_t> override_at_factory_call{}; |
| 1701 | + EXPECT_CALL(skeleton_field_binding_factory_mock_guard.factory_mock_, CreateEventBinding(_, _, kFieldName)) |
| 1702 | + .WillOnce(Invoke([&](const InstanceIdentifier&, SkeletonBase& parent, const std::string_view) { |
| 1703 | + override_at_factory_call = SkeletonBaseView{parent}.GetSlotCountOverride(); |
| 1704 | + return std::make_unique<mock_binding::SkeletonEvent<TestSampleType>>(); |
| 1705 | + })); |
| 1706 | + |
| 1707 | + const auto identifier = MakeInstanceIdentifierWithFieldSlotCount(std::nullopt); |
| 1708 | + |
| 1709 | + MyGetterOnlySkeleton skeleton{std::make_unique<mock_binding::Skeleton>(), identifier}; |
| 1710 | + |
| 1711 | + EXPECT_EQ(override_at_factory_call, std::uint16_t{2U}); |
| 1712 | + EXPECT_FALSE(SkeletonBaseView{skeleton}.GetSlotCountOverride().has_value()); |
| 1713 | + EXPECT_FALSE(GetFieldSlotCount(identifier).has_value()); |
| 1714 | +} |
| 1715 | + |
1561 | 1716 | } // namespace |
1562 | 1717 | } // namespace score::mw::com::impl |
0 commit comments