Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2ff40c3
lola: Change order of args in constructors
KrishaDeshkool Apr 24, 2026
55c4e77
mw/com: Remove excess ProxyField constructors
KrishaDeshkool Apr 24, 2026
85c958e
mw/com: Use tag types instead of bools for field enable flags
KrishaDeshkool Apr 24, 2026
86812a4
mw/com: add WithNotifier field tag and enforce observable conditions
KrishaDeshkool Apr 28, 2026
a9bc798
mw/com: SFINAE-gate proxy field notifier surface on WithNotifier
KrishaDeshkool Apr 28, 2026
6cfd03c
mw/com: add SFINAE detection tests for proxy field notifier surface
KrishaDeshkool Apr 28, 2026
284b5b0
mw/com: make event_binding defaulted to nullptr
KrishaDeshkool Apr 28, 2026
3025a5d
mw/com: expand proxy field ctors to one per tag combination
KrishaDeshkool Apr 29, 2026
be44b5e
mw/com: disambiguate proxy field test ctor with WithTestTag
KrishaDeshkool Apr 29, 2026
bf4da15
mw::com: make expectations more explicit
KrishaDeshkool May 5, 2026
1669bc6
mw/com: Add unit tests for MethodType and UniqueMethodIdentifier
KrishaDeshkool Apr 24, 2026
bc4165b
mw:com: remove redundant instance_id from LoLaProxyElementBuildingBlocks
KrishaDeshkool Apr 27, 2026
165ff2e
mw/com: Add tests for LookupLolaProxyElement
KrishaDeshkool Apr 24, 2026
69dd34c
lola: Extend ProxyMethodBindingFactory field Get/Set tests with Attorney
KrishaDeshkool Apr 24, 2026
3e9d942
mw/com: add ProxyField Get/Set and SkeletonField RegisterSetHandler t…
KrishaDeshkool Apr 24, 2026
f88b1f9
lola: Add field method handling tests for skeleton and proxy.
KrishaDeshkool Apr 24, 2026
67789ce
mw/com: add positive ProxyField WithNotifier surface tests
KrishaDeshkool May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion score/mw/com/impl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ cc_library(
"//score/mw/com:__subpackages__",
],
deps = [
":field_tags",
":method_type",
":skeleton_event",
":skeleton_field_base",
Expand All @@ -284,6 +285,7 @@ cc_library(
"//score/mw/com:__subpackages__",
],
deps = [
":field_tags",
":flag_owner",
":proxy_base",
":proxy_event",
Expand Down Expand Up @@ -323,6 +325,7 @@ cc_library(
"//score/mw/com:__subpackages__",
],
deps = [
":field_tags",
":method_type",
":proxy_event",
":proxy_event_binding",
Expand Down Expand Up @@ -720,7 +723,19 @@ cc_library(
cc_library(
name = "method_type",
srcs = ["method_type.cpp"],
hdrs = ["method_type.h"],
hdrs = [
"method_identifier.h",
"method_type.h",
],
features = COMPILER_WARNING_FEATURES,
tags = ["FFI"],
visibility = ["//score/mw/com:__subpackages__"],
)

cc_library(
name = "field_tags",
srcs = ["field_tags.cpp"],
hdrs = ["field_tags.h"],
features = COMPILER_WARNING_FEATURES,
tags = ["FFI"],
visibility = ["//score/mw/com:__subpackages__"],
Expand Down Expand Up @@ -1048,6 +1063,24 @@ cc_gtest_unit_test(
],
)

cc_gtest_unit_test(
name = "method_type_test",
srcs = ["method_type_test.cpp"],
features = COMPILER_WARNING_FEATURES,
deps = [
":method_type",
],
)

cc_gtest_unit_test(
name = "method_identifier_test",
srcs = ["method_identifier_test.cpp"],
features = COMPILER_WARNING_FEATURES,
deps = [
":method_type",
],
)

cc_gtest_unit_test(
name = "sample_reference_tracker_test",
srcs = ["sample_reference_tracker_test.cpp"],
Expand Down Expand Up @@ -1116,9 +1149,12 @@ cc_gtest_unit_test(
deps = [
":impl",
":runtime_mock",
"//score/mw/com/impl/bindings/mock_binding",
"//score/mw/com/impl/configuration/test:configuration_store",
"//score/mw/com/impl/plumbing:proxy_field_binding_factory_mock",
"//score/mw/com/impl/test:binding_factory_resources",
"//score/mw/com/impl/test:proxy_resources",
"//score/mw/com/impl/test:runtime_mock_guard",
],
)

Expand Down Expand Up @@ -1339,6 +1375,8 @@ cc_unit_test_suites_for_host_and_qnx(
":skeleton_base_test",
":unit_test",
":traits_test",
":method_type_test",
":method_identifier_test",
":unit_test_runtime_single_exec",
],
test_suites_from_sub_packages = [
Expand Down
3 changes: 3 additions & 0 deletions score/mw/com/impl/bindings/lola/proxy_method.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace score::mw::com::impl::lola
{

class Proxy;
class ProxyMethodAttorney;

class ProxyMethod : public ProxyMethodBinding
{
Expand Down Expand Up @@ -72,6 +73,8 @@ class ProxyMethod : public ProxyMethodBinding
bool IsSubscribed() const;

private:
friend class ProxyMethodAttorney;

QualityType asil_level_;
IRuntime& lola_runtime_;
TypeErasedCallQueue::TypeErasedElementInfo type_erased_element_info_;
Expand Down
111 changes: 111 additions & 0 deletions score/mw/com/impl/bindings/lola/proxy_method_handling_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ const LolaServiceTypeDeployment kLolaServiceTypeDeploymentWithMethods{
{},
{{kDummyMethodName0, kDummyMethodId0}, {kDummyMethodName1, kDummyMethodId1}, {kDummyMethodName2, kDummyMethodId2}}};

const ConfigurationStore kConfigurationStore{InstanceSpecifier::Create(std::string{"my_instance_spec"}).value(),
make_ServiceIdentifierType("foo"),
QualityType::kASIL_B,
kLolaServiceTypeDeploymentWithMethods,
kLolaServiceInstanceDeploymentWithMethods};

// Deployment configuration that includes fields alongside methods.
const std::string kDummyFieldName{"my_dummy_field"};
constexpr LolaServiceElementId kDummyFieldId{20U};

const LolaServiceInstanceDeployment kLolaServiceInstanceDeploymentWithFields{
kLolaInstanceId,
{},
{{kDummyFieldName, LolaFieldInstanceDeployment{{1U}, {3U}, 1U, true, 0}}},
{{kDummyMethodName0, LolaMethodInstanceDeployment{kDummyQueueSize0}}}};
const LolaServiceTypeDeployment kLolaServiceTypeDeploymentWithFields{kLolaServiceId,
{},
{{kDummyFieldName, kDummyFieldId}},
{{kDummyMethodName0, kDummyMethodId0}}};

const ConfigurationStore kConfigurationStoreWithFields{
InstanceSpecifier::Create(std::string{"my_field_instance_spec"}).value(),
make_ServiceIdentifierType("foo"),
QualityType::kASIL_B,
kLolaServiceTypeDeploymentWithFields,
kLolaServiceInstanceDeploymentWithFields};

const std::optional<DataTypeSizeInfo> kEmptyInArgsTypeErasedDataInfo{};
const std::optional<DataTypeSizeInfo> kEmptyReturnTypeTypeErasedDataInfo{};
const DataTypeSizeInfo kValidInArgsTypeErasedDataInfo{16U, 16U};
Expand Down Expand Up @@ -213,6 +240,19 @@ class ProxyMethodHandlingFixture : public ProxyMockedMemoryFixture
return *this;
}

ProxyMethodHandlingFixture& WithRegisteredProxyMethodsWithType(
std::vector<std::tuple<LolaServiceElementId, MethodType, TypeErasedCallQueue::TypeErasedElementInfo>>
methods_to_register)
{
for (auto& [method_id, method_type, type_erased_element_info] : methods_to_register)
{
const ProxyMethodInstanceIdentifier proxy_method_instance_identifier{proxy_->GetProxyInstanceIdentifier(),
{method_id, method_type}};
proxy_method_storage_.emplace_back(*proxy_, proxy_method_instance_identifier, type_erased_element_info);
}
return *this;
}

void StopOfferService()
{
ASSERT_TRUE(find_service_handler_.has_value());
Expand Down Expand Up @@ -1051,5 +1091,76 @@ TEST_F(ProxyMethodHandlingFixture, EnablingMethodThatDoesNotContainQueueSizeInCo
SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED(score::cpp::ignore = proxy_->SetupMethods());
}

class ProxyFieldMethodHandlingFixture : public ProxyMethodHandlingFixture
{
public:
ProxyFieldMethodHandlingFixture& GivenAProxyWithFields()
{
InitialiseProxyWithConstructor(kConfigurationStoreWithFields.GetInstanceIdentifier());
SCORE_LANGUAGE_FUTURECPP_ASSERT(proxy_ != nullptr);
return *this;
}

// Returns the size SetupMethods asked SharedMemoryFactory::Create for, or 0 if Create wasn't called.
std::size_t CaptureShmSizeRequestedBySetupMethods()
{
std::size_t captured_size{0U};
EXPECT_CALL(shared_memory_factory_mock_guard_.mock_, Create(_, _, _, _, _))
.Times(AtMost(1))
.WillRepeatedly(DoAll(SaveArg<2>(&captured_size), Return(mock_method_memory_resource_)));
const auto result = proxy_->SetupMethods();
EXPECT_TRUE(result.has_value());
return captured_size;
}
};

TEST_F(ProxyFieldMethodHandlingFixture, SetupMethodsIncludesFieldGetMethodWhenRegistered)
{
// Given a proxy with a field deployment and a registered Get ProxyMethod
GivenAProxyWithFields().GivenAMockedSharedMemoryResource().WithRegisteredProxyMethodsWithType(
{{kDummyFieldId, MethodType::kGet, kEmptyTypeErasedInfo}});

// When SetupMethods runs
// Then it asks for shm with a non-zero size, proving the kGet method got folded into the layout
EXPECT_GT(CaptureShmSizeRequestedBySetupMethods(), 0U);
}

TEST_F(ProxyFieldMethodHandlingFixture, SetupMethodsIncludesFieldSetMethodWhenRegistered)
{
// Given a proxy with a field deployment and a registered Set ProxyMethod
GivenAProxyWithFields().GivenAMockedSharedMemoryResource().WithRegisteredProxyMethodsWithType(
{{kDummyFieldId, MethodType::kSet, kEmptyTypeErasedInfo}});

// When SetupMethods runs
// Then same as the Get case - the kSet branch contributes to the layout
EXPECT_GT(CaptureShmSizeRequestedBySetupMethods(), 0U);
}

TEST_F(ProxyFieldMethodHandlingFixture, SetupMethodsIncludesBothFieldGetAndSetMethodsWhenBothRegistered)
{
// Given a proxy with a field deployment and BOTH Get and Set registered
GivenAProxyWithFields().GivenAMockedSharedMemoryResource().WithRegisteredProxyMethodsWithType(
{{kDummyFieldId, MethodType::kGet, kEmptyTypeErasedInfo},
{kDummyFieldId, MethodType::kSet, kEmptyTypeErasedInfo}});

// When SetupMethods runs
// Then the two-entry layout asks for strictly more than a one-entry layout would (base + one slot).
// That catches a regression where only one of Get/Set is exercised.
using MethodDataElement = decltype(MethodData::method_call_queues_)::value_type;
EXPECT_GT(CaptureShmSizeRequestedBySetupMethods(), sizeof(MethodData) + sizeof(MethodDataElement));
}

TEST_F(ProxyFieldMethodHandlingFixture, SetupMethodsDoesNotCreateShmWhenNoFieldMethodsRegistered)
{
// Given a proxy with a field deployment but no registered Get/Set ProxyMethods
GivenAProxyWithFields().GivenAMockedSharedMemoryResource();

// When SetupMethods runs
// Then nothing to register -> no shm allocation
EXPECT_CALL(shared_memory_factory_mock_guard_.mock_, Create(_, _, _, _, _)).Times(0);
const auto result = proxy_->SetupMethods();
EXPECT_TRUE(result.has_value());
}

} // namespace
} // namespace score::mw::com::impl::lola
139 changes: 139 additions & 0 deletions score/mw/com/impl/bindings/lola/skeleton_method_handling_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1082,5 +1082,144 @@ TEST_F(SkeletonOnServiceMethodsSubscribedFixture,
EXPECT_TRUE(scoped_handler_result.has_value());
}

TEST_F(SkeletonOnServiceMethodsSubscribedFixture, SubscribeMethodsSkipsFieldGetMethodNotOnSkeleton)
{
// TODO: Rework this once the get support is added.

// Given a skeleton with only regular methods offered and fake method data that includes a field
// Get method the skeleton doesn't actually serve
GivenASkeletonWithTwoMethods().WhichCapturesRegisteredMethodSubscribedHandlers().WhichIsOffered();
FakeMethodData fake_method_data_with_field_get{
{{foo_unique_method_id_, kFooTypeErasedElementInfo},
{UniqueMethodIdentifier{test::kFooFieldId, MethodType::kGet}, kDumbTypeErasedElementInfo}}};
ON_CALL(*mock_method_memory_resource_qm_, getUsableBaseAddress())
.WillByDefault(Return(static_cast<void*>(&fake_method_data_with_field_get.method_data_)));
const ProxyMethodInstanceIdentifier field_get_identifier_qm{
proxy_instance_identifier_qm_, UniqueMethodIdentifier{test::kFooFieldId, MethodType::kGet}};
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, foo_proxy_method_identifier_qm_, _, _));
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, field_get_identifier_qm, _, _)).Times(0);

// When the proxy subscribes
ASSERT_TRUE(captured_method_subscribed_handler_qm_.has_value());
const auto result = std::invoke(captured_method_subscribed_handler_qm_.value(),
proxy_instance_identifier_qm_,
test::kAllowedQmMethodConsumer,
kDummyPid);

// Then subscription succeeds - the regular method is registered and the field Get is silently skipped
EXPECT_TRUE(result.has_value());
}

TEST_F(SkeletonOnServiceMethodsSubscribedFixture, SubscribeMethodsSkipsFieldSetMethodNotOnSkeleton)
{
// Given a skeleton with only regular methods offered and fake method data that includes a field
// Set method the skeleton doesn't actually serve
GivenASkeletonWithTwoMethods().WhichCapturesRegisteredMethodSubscribedHandlers().WhichIsOffered();
FakeMethodData fake_method_data_with_field_set{
{{foo_unique_method_id_, kFooTypeErasedElementInfo},
{UniqueMethodIdentifier{test::kFooFieldId, MethodType::kSet}, kDumbTypeErasedElementInfo}}};
ON_CALL(*mock_method_memory_resource_qm_, getUsableBaseAddress())
.WillByDefault(Return(static_cast<void*>(&fake_method_data_with_field_set.method_data_)));
const ProxyMethodInstanceIdentifier field_set_identifier_qm{
proxy_instance_identifier_qm_, UniqueMethodIdentifier{test::kFooFieldId, MethodType::kSet}};
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, foo_proxy_method_identifier_qm_, _, _));
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, field_set_identifier_qm, _, _)).Times(0);

// When the proxy subscribes
ASSERT_TRUE(captured_method_subscribed_handler_qm_.has_value());
const auto result = std::invoke(captured_method_subscribed_handler_qm_.value(),
proxy_instance_identifier_qm_,
test::kAllowedQmMethodConsumer,
kDummyPid);

// Then subscription succeeds - the regular method is registered and the field Set is silently skipped
EXPECT_TRUE(result.has_value());
}

TEST_F(SkeletonOnServiceMethodsSubscribedFixture,
SubscribeMethodsContinuesPastSkippedFieldMethodToRegularMethodsThatFollow)
{
// Given a skeleton with foo and dumb offered
GivenASkeletonWithTwoMethods().WhichCapturesRegisteredMethodSubscribedHandlers().WhichIsOffered();

// Fake method data places a field Get BEFORE both regular methods. If the skeleton broke out of
// the subscribe loop on the skipped field method, foo and dumb would never be registered.
FakeMethodData fake_method_data_with_field_first{
{{UniqueMethodIdentifier{test::kFooFieldId, MethodType::kGet}, kDumbTypeErasedElementInfo},
{foo_unique_method_id_, kFooTypeErasedElementInfo},
{dumb_unique_method_id_, kDumbTypeErasedElementInfo}}};

ON_CALL(*mock_method_memory_resource_qm_, getUsableBaseAddress())
.WillByDefault(Return(static_cast<void*>(&fake_method_data_with_field_first.method_data_)));

// Expect both regular methods to be registered; the field Get in between must be skipped via continue
const ProxyMethodInstanceIdentifier field_get_identifier_qm{
proxy_instance_identifier_qm_, UniqueMethodIdentifier{test::kFooFieldId, MethodType::kGet}};
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, foo_proxy_method_identifier_qm_, _, _));
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, dumb_proxy_method_identifier_qm_, _, _));
EXPECT_CALL(message_passing_mock_, RegisterMethodCallHandler(_, field_get_identifier_qm, _, _)).Times(0);

// When the proxy subscribes
ASSERT_TRUE(captured_method_subscribed_handler_qm_.has_value());
const auto result = std::invoke(captured_method_subscribed_handler_qm_.value(),
proxy_instance_identifier_qm_,
test::kAllowedQmMethodConsumer,
kDummyPid);

// Then subscription succeeds overall
EXPECT_TRUE(result.has_value());
}

/// Tests for VerifyAllMethodsRegistered skipping kGet methods.

class SkeletonFieldMethodHandlingFixture : public SkeletonMethodHandlingFixture
{
public:
SkeletonFieldMethodHandlingFixture& WithAFieldGetMethodWithoutHandler()
{
const UniqueMethodIdentifier get_method_id{test::kFooFieldId, MethodType::kGet};
get_field_method_ = std::make_unique<SkeletonMethod>(*skeleton_, get_method_id);
return *this;
}

SkeletonFieldMethodHandlingFixture& WithAFieldSetMethodWithoutHandler()
{
const UniqueMethodIdentifier set_method_id{test::kFooFieldId, MethodType::kSet};
set_field_method_ = std::make_unique<SkeletonMethod>(*skeleton_, set_method_id);
return *this;
}

std::unique_ptr<SkeletonMethod> get_field_method_{nullptr};
std::unique_ptr<SkeletonMethod> set_field_method_{nullptr};
};

TEST_F(SkeletonFieldMethodHandlingFixture, VerifyAllMethodsRegisteredReturnsTrueWhenGetMethodHasNoHandler)
{
// TODO: Rework this once the get support is added.

// Given a skeleton with regular methods that have handlers registered, plus a field Get method without one
GivenASkeletonWithTwoMethods();
WithAFieldGetMethodWithoutHandler();

// When verifying all methods are registered
const auto result = skeleton_->VerifyAllMethodsRegistered();

// Then verification succeeds because kGet methods are skipped
EXPECT_TRUE(result);
}

TEST_F(SkeletonFieldMethodHandlingFixture, VerifyAllMethodsRegisteredReturnsFalseWhenSetMethodHasNoHandler)
{
// Given a skeleton with regular methods that have handlers registered, plus a field Set method.
GivenASkeletonWithTwoMethods();
WithAFieldSetMethodWithoutHandler();

// When verifying all methods are registered
const auto result = skeleton_->VerifyAllMethodsRegistered();

// Then verification fails because kSet methods are NOT skipped
EXPECT_FALSE(result);
}

} // namespace
} // namespace score::mw::com::impl::lola
13 changes: 13 additions & 0 deletions score/mw/com/impl/field_tags.cpp
Original file line number Diff line number Diff line change
@@ -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/field_tags.h"
Loading