From c99c737d7c2acec311fea297e9ce3c9bd05ff775 Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Fri, 10 Apr 2026 16:10:07 +0200 Subject: [PATCH 01/15] mw/com: Extract boilerplate into fixture --- score/mw/com/impl/methods/BUILD | 1 + .../com/impl/methods/skeleton_method_test.cpp | 129 +++++++----------- 2 files changed, 51 insertions(+), 79 deletions(-) diff --git a/score/mw/com/impl/methods/BUILD b/score/mw/com/impl/methods/BUILD index 6a36ba878..0c9ba0b8e 100644 --- a/score/mw/com/impl/methods/BUILD +++ b/score/mw/com/impl/methods/BUILD @@ -187,6 +187,7 @@ cc_unit_test( ":skeleton_method", "//score/mw/com/impl/bindings/mock_binding", "//score/mw/com/impl/plumbing:skeleton_method_binding_factory_mock", + "//score/mw/com/impl/test:binding_factory_resources", "@score_baselibs//score/language/futurecpp", ], ) diff --git a/score/mw/com/impl/methods/skeleton_method_test.cpp b/score/mw/com/impl/methods/skeleton_method_test.cpp index 5173ab4f8..9d0905f8b 100644 --- a/score/mw/com/impl/methods/skeleton_method_test.cpp +++ b/score/mw/com/impl/methods/skeleton_method_test.cpp @@ -17,9 +17,8 @@ #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/methods/skeleton_method.h" #include "score/mw/com/impl/methods/skeleton_method_base.h" -#include "score/mw/com/impl/plumbing/skeleton_method_binding_factory.h" -#include "score/mw/com/impl/plumbing/skeleton_method_binding_factory_mock.h" #include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/test/binding_factory_resources.h" #include "score/result/result.h" #include @@ -40,12 +39,12 @@ using ::testing::Return; const auto kInstanceSpecifier = InstanceSpecifier::Create(std::string{"abc/abc/TirePressurePort"}).value(); const auto kServiceIdentifier = make_ServiceIdentifierType("foo", 13, 37); -std::uint16_t kInstanceId{23U}; +constexpr std::uint16_t kInstanceId{23U}; const ServiceInstanceDeployment kDeploymentInfo{kServiceIdentifier, LolaServiceInstanceDeployment{LolaServiceInstanceId{kInstanceId}}, QualityType::kASIL_QM, kInstanceSpecifier}; -std::uint16_t kServiceId{34U}; +constexpr std::uint16_t kServiceId{34U}; const ServiceTypeDeployment kTypeDeployment{LolaServiceTypeDeployment{kServiceId}}; const auto kInstanceIdWithLolaBinding = make_InstanceIdentifier(kDeploymentInfo, kTypeDeployment); @@ -57,22 +56,6 @@ class EmptySkeleton final : public SkeletonBase using TestMethodType = bool(int, bool); -class SkeletonMethodTestFixture : public ::testing::Test -{ - public: - void CreateSkeletonMethod() - { - EmptySkeleton empty_skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; - auto mock_method_binding_ptr = std::make_unique(mock_method_binding_); - - method_ = std::make_unique>( - empty_skeleton, "dummy_method", std::move(mock_method_binding_ptr)); - } - - std::unique_ptr> method_{nullptr}; - mock_binding::SkeletonMethod mock_method_binding_{}; -}; - TEST(SkeletonMethodTests, NotCopyable) { static_assert(!std::is_copy_constructible>::value, "Is wrongly copyable"); @@ -99,17 +82,38 @@ TEST(SkeletonMethodTest, ClassTypeDependsOnMethodType) "Class type does not depend on event data type"); } -template +template class SkeletonMethodTypedTest : public ::testing::Test { public: - using Type = T; + using Type = MethodType; void SetUp() override { - ON_CALL(skeleton_method_binding_mock_, RegisterHandler(_)).WillByDefault(Return(Result{})); + ON_CALL(mock_method_binding_, RegisterHandler(_)).WillByDefault(Return(Result{})); + } + + SkeletonMethodTypedTest& GivenASkeletonMethod() + { + auto mock_method_binding_ptr = std::make_unique(mock_method_binding_); + + method_ = std::make_unique>( + empty_skeleton_, method_name_, std::move(mock_method_binding_ptr)); + return *this; + } + + SkeletonMethodTypedTest& WithAMockedMethodBindingfactory() + { + skeleton_method_binding_factory_mock_guard_ = std::make_unique(); + return *this; } - mock_binding::SkeletonMethod skeleton_method_binding_mock_; + + EmptySkeleton empty_skeleton_{std::make_unique(), kInstanceIdWithLolaBinding}; + std::string method_name_{"dummy_method"}; + std::unique_ptr> method_{nullptr}; + mock_binding::SkeletonMethod mock_method_binding_{}; + + std::unique_ptr skeleton_method_binding_factory_mock_guard_{nullptr}; }; struct MyDataStruct @@ -128,55 +132,35 @@ using RegisteredFunctionTypes = ::testing::Types; TYPED_TEST_SUITE(SkeletonMethodTypedTest, RegisteredFunctionTypes, ); +using SkeletonMethodTestFixture = SkeletonMethodTypedTest; + TYPED_TEST(SkeletonMethodTypedTest, AnyCombinationOfReturnAndInputArgTypesCanBeRegistered) { - // Given A skeleton Method with a mock method binding - EmptySkeleton empty_skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; - auto mock_method_binding_ptr = std::make_unique(); - auto& mock_method_binding = *mock_method_binding_ptr; - - // And a method type that can be one of the four qualitatively different Types - // void() SomeType() - // void(SomeTypes) SomeType(SomeTypes) - using FixtureMethodType = typename TestFixture::Type; - SkeletonMethod method{empty_skeleton, "dummy_method", std::move(mock_method_binding_ptr)}; + this->GivenASkeletonMethod(); // Expecting that the register call is dispatched to the binding without errors - EXPECT_CALL(mock_method_binding, RegisterHandler(_)); + EXPECT_CALL(this->mock_method_binding_, RegisterHandler(_)); - // When a Register call is issued at the binding independent level + // // When a Register call is issued at the binding independent level + using FixtureMethodType = typename TestFixture::Type; score::cpp::callback test_callback{}; - - std::ignore = method.RegisterHandler(std::move(test_callback)); + std::ignore = this->method_->RegisterHandler(std::move(test_callback)); } TYPED_TEST(SkeletonMethodTypedTest, TwoParameterConstructorCorrectlyCallsBindingFactoryAndSkeletonMethodIsCreated) { + this->WithAMockedMethodBindingfactory(); - auto skeleton_method_binding_factory_mock = SkeletonMethodBindingFactoryMock(); - SkeletonMethodBindingFactory::InjectMockBinding(&skeleton_method_binding_factory_mock); - - auto skeleton_method_binding = - std::make_unique(this->skeleton_method_binding_mock_); - - // Given A skeleton Method with a mock method binding - - EmptySkeleton empty_skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; - - // And a method type that can be one of the four qualitatively different Types void() SomeType() - // void(SomeTypes) SomeType(SomeTypes) - - // expecting that a binding factory cannot crete a binding - EXPECT_CALL(skeleton_method_binding_factory_mock, - Create(_ /*handle*/, _ /*parent binding*/, _ /*method_name*/, _ /*method_type*/)) - .WillOnce(testing::Return(testing::ByMove(std::move(skeleton_method_binding)))); + // Expecting that a binding factory can create a binding + EXPECT_CALL(this->skeleton_method_binding_factory_mock_guard_->factory_mock_, + Create(_ /*handle*/, _ /*parent binding*/, this->method_name_, _)) + .WillOnce(testing::Return( + testing::ByMove(std::make_unique(this->mock_method_binding_)))); // When the 2-parameter constructor of the SkeletonMethod class is called using FixtureMethodType = typename TestFixture::Type; - SkeletonMethod method{empty_skeleton, "dummy_method"}; - - EXPECT_CALL(this->skeleton_method_binding_mock_, RegisterHandler(_)); + SkeletonMethod method{this->empty_skeleton_, this->method_name_}; // Then a Binding can be created which is capable of registering a callback score::cpp::callback test_callback{}; @@ -187,38 +171,24 @@ TYPED_TEST( SkeletonMethodTypedTest, TwoParameterConstructorCorrectlyCallsBindingFactoryButSkeletonMethodIsNotCreatedWhenTheBindingFactoryDoesNotReturnBinding) { + this->WithAMockedMethodBindingfactory(); - auto skeleton_method_binding_factory_mock = SkeletonMethodBindingFactoryMock(); - SkeletonMethodBindingFactory::InjectMockBinding(&skeleton_method_binding_factory_mock); - - auto skeleton_method_binding = - std::make_unique(this->skeleton_method_binding_mock_); - - // Given A skeleton Method with a mock method binding - - EmptySkeleton empty_skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; - - // And a method type that can be one of the four qualitatively different Types - // void() SomeType() - // void(SomeTypes) SomeType(SomeTypes) - - // expecting that a binding factory cannot crete a binding - EXPECT_CALL(skeleton_method_binding_factory_mock, - Create(_ /*handle*/, _ /*parent binding*/, _ /*method_name*/, _ /*method_type*/)) + // Expecting that a binding factory cannot create a binding + EXPECT_CALL(this->skeleton_method_binding_factory_mock_guard_->factory_mock_, + Create(_ /*handle*/, _ /*parent binding*/, this->method_name_, _ /*method_type*/)) .WillOnce(testing::Return(testing::ByMove(nullptr))); // When the 2-parameter constructor of the SkeletonMethod class is called using FixtureMethodType = typename TestFixture::Type; - SkeletonMethod method{empty_skeleton, "dummy_method"}; + SkeletonMethod method{this->empty_skeleton_, this->method_name_}; // Then the binding cannot be created and calling AreBindingsValid returns false - EXPECT_FALSE(SkeletonBaseView{empty_skeleton}.AreBindingsValid()); + EXPECT_FALSE(SkeletonBaseView{this->empty_skeleton_}.AreBindingsValid()); } TEST_F(SkeletonMethodTestFixture, ACallbackWithAPointerAsStateCanBeRegistered) { - // Given A skeleton Method with a mock method binding - CreateSkeletonMethod(); + GivenASkeletonMethod(); // And a callback with a unique_ptr as a state auto test_struct_p = std::make_unique(); @@ -383,6 +353,7 @@ TEST_F(SkeletonMethodVoidVoidFixture, DataTransferBetweenTypedAndTypeErasedCallb // When the type erased call is executed by the binding typeerased_callback_.value()({}, {}); } + } // namespace } // namespace score::mw::com::impl From 1de79be9ac752a4b85193ed3f4e6bb97e1e80365 Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Wed, 29 Apr 2026 12:01:21 +0200 Subject: [PATCH 02/15] mw/com: Add helpers for checking method handler signatures --- score/mw/com/impl/methods/BUILD | 20 ++ .../impl/methods/method_handler_checker.cpp | 13 + .../com/impl/methods/method_handler_checker.h | 125 +++++++++ .../methods/method_handler_checker_test.cpp | 242 ++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 score/mw/com/impl/methods/method_handler_checker.cpp create mode 100644 score/mw/com/impl/methods/method_handler_checker.h create mode 100644 score/mw/com/impl/methods/method_handler_checker_test.cpp diff --git a/score/mw/com/impl/methods/BUILD b/score/mw/com/impl/methods/BUILD index 0c9ba0b8e..fd6d23f83 100644 --- a/score/mw/com/impl/methods/BUILD +++ b/score/mw/com/impl/methods/BUILD @@ -28,6 +28,17 @@ cc_library( ], ) +cc_library( + name = "method_handler_checker", + srcs = ["method_handler_checker.cpp"], + hdrs = ["method_handler_checker.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], +) + cc_library( name = "proxy_method_base", srcs = ["proxy_method_base.cpp"], @@ -111,6 +122,15 @@ cc_unit_test( ], ) +cc_unit_test( + name = "method_handler_checker_test", + srcs = ["method_handler_checker_test.cpp"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":method_handler_checker", + ], +) + cc_unit_test( name = "proxy_method_test", srcs = ["proxy_method_test.cpp"], diff --git a/score/mw/com/impl/methods/method_handler_checker.cpp b/score/mw/com/impl/methods/method_handler_checker.cpp new file mode 100644 index 000000000..35391fb98 --- /dev/null +++ b/score/mw/com/impl/methods/method_handler_checker.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 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/methods/method_handler_checker.h" diff --git a/score/mw/com/impl/methods/method_handler_checker.h b/score/mw/com/impl/methods/method_handler_checker.h new file mode 100644 index 000000000..15f6e2ec0 --- /dev/null +++ b/score/mw/com/impl/methods/method_handler_checker.h @@ -0,0 +1,125 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_METHODS_METHOD_HANDLER_CHECKER_H +#define SCORE_MW_COM_IMPL_METHODS_METHOD_HANDLER_CHECKER_H + +#include +#include + +namespace score::mw::com::impl +{ +namespace detail +{ + +template +struct get_method_in_arg_and_return_types : public get_method_in_arg_and_return_types +{ +}; + +template +struct get_method_in_arg_and_return_types +{ + using return_type = RetArg; + using in_args = std::tuple; +}; + +template +struct get_method_in_arg_types : public get_method_in_arg_types +{ +}; + +template +struct get_method_in_arg_types +{ + using type = std::tuple; +}; + +template +struct get_method_return_type : public get_method_return_type +{ +}; + +template +struct get_method_return_type +{ + using type = RetArg; +}; + +template +struct get_callable_return_type : public get_callable_return_type +{ +}; + +template +struct get_callable_return_type +{ + using type = Ret; +}; + +} // namespace detail + +template +constexpr void AssertCallableMatchesMethodSignature() +{ + using CallableReturnType = typename detail::get_callable_return_type::type; + static_assert(std::is_same_v, + "Registered method callable must have void return type! The actual method return type is passed as " + "first argument to the callable as non-const reference!"); + + constexpr bool has_in_args = (sizeof...(ArgTypes) != 0); + constexpr bool has_return_type = !std::is_same_v; + if constexpr (has_in_args && has_return_type) + { + using MethodInArgTuple = typename detail::get_method_in_arg_and_return_types::in_args; + using MethodReturnType = typename detail::get_method_in_arg_and_return_types::return_type; + + using ExpectedInArgTypeTuple = std::tuple; + using ExpectedReturnType = ReturnType&; + + static_assert( + std::is_same_v, + "Registered method callable must have the method return type as first argument as non-const reference!"); + static_assert(std::is_same_v, + "Registered method callable must have the same in argument types as the method signature " + "following the return type, but with const reference semantics!"); + } + else if constexpr (!has_in_args && has_return_type) + { + using MethodCallableReturnType = typename detail::get_method_return_type::type; + using ExpectedReturnType = ReturnType&; + + static_assert( + std::is_same_v, + "Registered method callable must have the method return type as only argument as non-const reference!"); + } + else if constexpr (has_in_args && !has_return_type) + { + using MethodCallableInArgTuple = typename detail::get_method_in_arg_types::type; + using ExpectedInArgTypeTuple = std::tuple; + + static_assert(std::is_same_v, + "Registered method callable must have only the in argument types from the method signature, but " + "with const reference semantics!"); + } + else + { + using MethodCallableInArgTuple = typename detail::get_method_in_arg_types::type; + static_assert(std::is_same_v>, + "Registered method callable must not have any arguments since the method signature does not " + "specify any in arguments or return type!"); + } +} + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_METHODS_METHOD_HANDLER_CHECKER_H diff --git a/score/mw/com/impl/methods/method_handler_checker_test.cpp b/score/mw/com/impl/methods/method_handler_checker_test.cpp new file mode 100644 index 000000000..63ff53877 --- /dev/null +++ b/score/mw/com/impl/methods/method_handler_checker_test.cpp @@ -0,0 +1,242 @@ +/******************************************************************************** + * Copyright (c) 2025 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/methods/method_handler_checker.h" + +#include + +#include + +namespace score::mw::com::impl +{ +namespace +{ + +TEST(MethodHandlerCheckerReturnAndInArgsTest, CallingAssertCallableWithValidCallableDoesNotTerminate) +{ + // Given a method with a return type and InArgs. + using MethodReturnType = int; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts the return and InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +// TODO: Tests for invalid callables that should cause static assertion failures. These tests are disabled since we +// currently don't have infrastructure for compile time testing. To be enabled in SWP-46885. +#if 0 +TEST(MethodHandlerCheckerReturnAndInArgsTest, CallingAssertCallableWithCallableWithMissingReturnTerminates) +{ + // Given a method with a return type and InArgs. + using MethodReturnType = int; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts only the InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} + +TEST(MethodHandlerCheckerReturnAndInArgsTest, CallingAssertCallableWithCallableWithMissingInArgTerminates) +{ + // Given a method with a return type and InArgs. + using MethodReturnType = int; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts only the return and only one of the InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} + +TEST(MethodHandlerCheckerReturnAndInArgsTest, CallingAssertCallableWithCallableWithConstReturnTypeRefTerminates) +{ + // Given a method with a return type and InArgs. + using MethodReturnType = int; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts the correct arguments but the return type is a const reference instead of + // non-const reference. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} + +TEST(MethodHandlerCheckerReturnAndInArgsTest, CallingAssertCallableWithCallableWithNonConstInArgRefTerminates) +{ + // Given a method with a return type and InArgs. + using MethodReturnType = int; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts the correct arguments but one of the InArgs is a non-const reference instead of + // a const reference. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} +#endif + +TEST(MethodHandlerCheckerReturnOnlyTest, CallingAssertCallableWithValidCallableDoesNotTerminate) +{ + // Given a method with a return type and no InArgs. + using MethodReturnType = int; + + // and given a callable that accepts the return and no InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +// TODO: Tests for invalid callables that should cause static assertion failures. These tests are disabled since we +// currently don't have infrastructure for compile time testing. To be enabled in SWP-46885. +#if 0 +TEST(MethodHandlerCheckerReturnOnlyTest, CallingAssertCallableWithCallableWithMissingReturnTerminates) +{ + // Given a method with a return type and no InArgs. + using MethodReturnType = int; + + // and given a callable that accepts no return and no InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} + +TEST(MethodHandlerCheckerReturnOnlyTest, CallingAssertCallableWithCallableWithConstReturnTypeRefTerminates) +{ + // Given a method with a return type and no InArgs. + using MethodReturnType = int; + + // and given a callable that accepts the return type as const reference instead of non-const reference. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} +#endif + +TEST(MethodHandlerCheckerInArgsOnlyTest, CallingAssertCallableWithValidCallableDoesNotTerminate) +{ + // Given a method with no return type and InArgs. + using MethodReturnType = void; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts no return and InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +// TODO: Tests for invalid callables that should cause static assertion failures. These tests are disabled since we +// currently don't have infrastructure for compile time testing. To be enabled in SWP-46885. +#if 0 +TEST(MethodHandlerCheckerInArgsOnlyTest, CallingAssertCallableWithCallableWithMissingInArgTerminates) +{ + // Given a method with no return type and InArgs. + using MethodReturnType = void; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts only one InArg. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} + +TEST(MethodHandlerCheckerInArgsOnlyTest, CallingAssertCallableWithCallableWithNonConstInArgRefTerminates) +{ + // Given a method with no return type and InArgs. + using MethodReturnType = void; + using FirstInArgType = int; + using SecondInArgType = double; + + // and given a callable that accepts one of the InArgs as non-const reference instead of const reference. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} +#endif + +TEST(MethodHandlerCheckerNoReturnAndNoInArgsTest, CallingAssertCallableWithValidCallableDoesNotTerminate) +{ + // Given a method with no return type and no InArgs. + using MethodReturnType = void; + + // and given a callable that accepts no return and no InArgs. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +// TODO: Tests for invalid callables that should cause static assertion failures. These tests are disabled since we +// currently don't have infrastructure for compile time testing. To be enabled in SWP-46885. +#if 0 +TEST(MethodHandlerCheckerNoReturnAndNoInArgsTest, CallingAssertCallableWithCallableWithInArgTerminates) +{ + // Given a method with no return type and no InArgs. + using MethodReturnType = void; + + // and given a callable that accepts an InArg. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} + +TEST(MethodHandlerCheckerNoReturnAndNoInArgsTest, CallingAssertCallableWithCallableWithReturnTypeTerminates) +{ + // Given a method with no return type and no InArgs. + using MethodReturnType = void; + + // and given a callable that returns a non-void value. + using ValidCallable = std::function; + + // When calling AssertCallableMatchesMethodSignature + // Then the function should not compile + AssertCallableMatchesMethodSignature(); +} +#endif + +} // namespace + +} // namespace score::mw::com::impl From 3e44d294e776c4955942665d62acb7ed2a452d74 Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Wed, 29 Apr 2026 18:48:55 +0200 Subject: [PATCH 03/15] mw/com: Make input argument to field Set const This is the copy variant of set which takes a field value and returns the new value via the MethodReturnTypePtr. The provided value will not be modified so it's misleading that it's non-const. --- score/mw/com/impl/proxy_field.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/score/mw/com/impl/proxy_field.h b/score/mw/com/impl/proxy_field.h index 5e6598bdb..7c032d239 100644 --- a/score/mw/com/impl/proxy_field.h +++ b/score/mw/com/impl/proxy_field.h @@ -259,7 +259,7 @@ class ProxyField final : public ProxyFieldBase } template >> - score::Result> Set(SampleDataType& new_field_value) noexcept + score::Result> Set(const SampleDataType& new_field_value) noexcept { return proxy_method_set_dispatch_->operator()(new_field_value); } From 8a79e2e8914b08e15e3a94e15c5b47c3c438074a Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Wed, 29 Apr 2026 18:55:53 +0200 Subject: [PATCH 04/15] mw/com: Fix method signature to avoid copies of return type --- score/mw/com/impl/methods/BUILD | 1 + score/mw/com/impl/methods/skeleton_method.h | 38 ++++--- .../com/impl/methods/skeleton_method_test.cpp | 106 +++++++++++------- score/mw/com/impl/skeleton_field.h | 22 ++-- score/mw/com/impl/skeleton_field_test.cpp | 35 +++--- .../methods_test_resources/method_provider.h | 17 +-- .../non_trivial_constructors/provider.cpp | 14 ++- 7 files changed, 140 insertions(+), 93 deletions(-) diff --git a/score/mw/com/impl/methods/BUILD b/score/mw/com/impl/methods/BUILD index fd6d23f83..efe71d32a 100644 --- a/score/mw/com/impl/methods/BUILD +++ b/score/mw/com/impl/methods/BUILD @@ -174,6 +174,7 @@ cc_library( "//score/mw/com/impl:__subpackages__", ], deps = [ + ":method_handler_checker", ":skeleton_method_binding", "//score/mw/com/impl:instance_identifier", "//score/mw/com/impl:skeleton_base", diff --git a/score/mw/com/impl/methods/skeleton_method.h b/score/mw/com/impl/methods/skeleton_method.h index 8164f2761..5a6eb9ef2 100644 --- a/score/mw/com/impl/methods/skeleton_method.h +++ b/score/mw/com/impl/methods/skeleton_method.h @@ -12,7 +12,9 @@ ********************************************************************************/ #ifndef SCORE_MW_COM_IMPL_METHODS_SKELETON_METHOD_H #define SCORE_MW_COM_IMPL_METHODS_SKELETON_METHOD_H + #include "score/mw/com/impl/method_type.h" +#include "score/mw/com/impl/methods/method_handler_checker.h" #include "score/mw/com/impl/methods/skeleton_method_base.h" #include "score/mw/com/impl/methods/skeleton_method_binding.h" #include "score/mw/com/impl/plumbing/skeleton_method_binding_factory.h" @@ -21,7 +23,6 @@ #include "score/result/result.h" #include -#include #include #include #include @@ -160,19 +161,11 @@ Result SkeletonMethod::RegisterHandler(Callable&& { static_assert(std::is_rvalue_reference_v, "Callbeck provided to register has to be an rvalue reference"); - - // A move would only be problematic if the forwarding refference Callback&& evealuates to an LValue and and is moved - // from (which the caller of the function might not expect). We static assert that the callback is an RValue - // refference and can always be movef from. - // NOLINTNEXTLINE(bugprone-move-forwarding-reference) The callback is asserted to be an RValue reference - auto callable_invoker = [callable = std::move(callback)](auto&&... ptrs) -> decltype(auto) { - return std::invoke(callable, (*ptrs)...); - }; + AssertCallableMatchesMethodSignature(); SkeletonMethodBinding::TypeErasedHandler type_erased_callable = - [callable_invoker = std::move(callable_invoker)]( - std::optional> type_erased_in_args, - std::optional> type_erased_return) { + [callback = std::forward(callback)](std::optional> type_erased_in_args, + std::optional> type_erased_return) { using InArgPtrTuple = std::tuple; InArgPtrTuple typed_in_arg_ptrs{}; @@ -189,15 +182,29 @@ Result SkeletonMethod::RegisterHandler(Callable&& constexpr bool is_return_type_not_void = !std::is_same_v; if constexpr (is_return_type_not_void) { + const auto typed_return_ptr_tuple = Deserialize(type_erased_return.value()); + auto* const typed_return_ptr = std::get<0>(typed_return_ptr_tuple); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( type_erased_return.has_value(), "ReturnType is non void. Thus, type_erased_result needs to have a value!"); - ReturnType res = std::apply(callable_invoker, std::forward(typed_in_arg_ptrs)); - SerializeArgs(type_erased_return.value(), res); + + // Call the callable with the typed_return_ptr and the typed_in_arg_ptrs which are unpacked from the + // tuple into individual arguments. + std::apply( + [&callback, typed_return_ptr](ArgTypes*... typed_in_arg_ptrs) { + std::invoke(callback, *typed_return_ptr, *typed_in_arg_ptrs...); + }, + typed_in_arg_ptrs); } else { - std::apply(callable_invoker, std::forward(typed_in_arg_ptrs)); + // Call the callable with the typed_in_arg_ptrs which are unpacked from the tuple into individual + // arguments. + std::apply( + [&callback](ArgTypes*... typed_in_arg_ptrs) { + std::invoke(callback, *typed_in_arg_ptrs...); + }, + typed_in_arg_ptrs); } }; @@ -205,4 +212,5 @@ Result SkeletonMethod::RegisterHandler(Callable&& } } // namespace score::mw::com::impl + #endif // SCORE_MW_COM_IMPL_METHODS_SKELETON_METHOD_H diff --git a/score/mw/com/impl/methods/skeleton_method_test.cpp b/score/mw/com/impl/methods/skeleton_method_test.cpp index 9d0905f8b..9cd00b542 100644 --- a/score/mw/com/impl/methods/skeleton_method_test.cpp +++ b/score/mw/com/impl/methods/skeleton_method_test.cpp @@ -55,6 +55,7 @@ class EmptySkeleton final : public SkeletonBase }; using TestMethodType = bool(int, bool); +using TestMethodHandlerType = void(bool&, const int&, const bool&); TEST(SkeletonMethodTests, NotCopyable) { @@ -82,11 +83,12 @@ TEST(SkeletonMethodTest, ClassTypeDependsOnMethodType) "Class type does not depend on event data type"); } -template +template class SkeletonMethodTypedTest : public ::testing::Test { public: - using Type = MethodType; + using MethodType = typename Types::MethodType; + using HandlerType = typename Types::HandlerType; void SetUp() override { @@ -123,16 +125,28 @@ struct MyDataStruct double d; float f[4]; }; -using RegisteredFunctionTypes = ::testing::Types; + +template +struct MethodTypeAndHandlerType +{ + using MethodType = MethodTypeIn; + using HandlerType = HandlerTypeIn; +}; +using RegisteredFunctionTypes = + ::testing::Types, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType, + MethodTypeAndHandlerType>; TYPED_TEST_SUITE(SkeletonMethodTypedTest, RegisteredFunctionTypes, ); -using SkeletonMethodTestFixture = SkeletonMethodTypedTest; +using SkeletonMethodTestFixture = + SkeletonMethodTypedTest>; TYPED_TEST(SkeletonMethodTypedTest, AnyCombinationOfReturnAndInputArgTypesCanBeRegistered) { @@ -143,8 +157,8 @@ TYPED_TEST(SkeletonMethodTypedTest, AnyCombinationOfReturnAndInputArgTypesCanBeR EXPECT_CALL(this->mock_method_binding_, RegisterHandler(_)); // // When a Register call is issued at the binding independent level - using FixtureMethodType = typename TestFixture::Type; - score::cpp::callback test_callback{}; + using HandlerType = typename TestFixture::HandlerType; + score::cpp::callback test_callback{}; std::ignore = this->method_->RegisterHandler(std::move(test_callback)); } @@ -159,11 +173,12 @@ TYPED_TEST(SkeletonMethodTypedTest, TwoParameterConstructorCorrectlyCallsBinding testing::ByMove(std::make_unique(this->mock_method_binding_)))); // When the 2-parameter constructor of the SkeletonMethod class is called - using FixtureMethodType = typename TestFixture::Type; + using FixtureMethodType = typename TestFixture::MethodType; SkeletonMethod method{this->empty_skeleton_, this->method_name_}; // Then a Binding can be created which is capable of registering a callback - score::cpp::callback test_callback{}; + using HandlerType = typename TestFixture::HandlerType; + score::cpp::callback test_callback{}; EXPECT_TRUE(method.RegisterHandler(std::move(test_callback))); } @@ -179,7 +194,7 @@ TYPED_TEST( .WillOnce(testing::Return(testing::ByMove(nullptr))); // When the 2-parameter constructor of the SkeletonMethod class is called - using FixtureMethodType = typename TestFixture::Type; + using FixtureMethodType = typename TestFixture::MethodType; SkeletonMethod method{this->empty_skeleton_, this->method_name_}; // Then the binding cannot be created and calling AreBindingsValid returns false @@ -192,10 +207,10 @@ TEST_F(SkeletonMethodTestFixture, ACallbackWithAPointerAsStateCanBeRegistered) // And a callback with a unique_ptr as a state auto test_struct_p = std::make_unique(); - score::cpp::callback test_callback_with_state = [state = std::move(test_struct_p)]( - int, bool b) noexcept { - return state->b || b; - }; + score::cpp::callback test_callback_with_state = + [state = std::move(test_struct_p)](bool& return_value, const int&, const bool& b) noexcept { + return_value = state->b || b; + }; // Expecting that the register call is dispatched to the binding without an error EXPECT_CALL(mock_method_binding_, RegisterHandler(_)); @@ -207,34 +222,38 @@ TEST_F(SkeletonMethodTestFixture, ACallbackWithAPointerAsStateCanBeRegistered) using Thing = long; using InType1 = double; using InType2 = int; -using VoidVoid = void(); -using ThingVoid = Thing(); -using VoidStuff = void(InType1, InType2); -using ThingStuff = Thing(InType1, InType2); - -template +using VoidVoid = MethodTypeAndHandlerType; +using ThingVoid = MethodTypeAndHandlerType; +using VoidStuff = + MethodTypeAndHandlerType; +using ThingStuff = MethodTypeAndHandlerType; + +template class SkeletonMethodGenericTestFixture : public ::testing::Test { - public: void CreateSkeletonMethodWithMockedTypeErasedCallback() { EmptySkeleton empty_skeleton{std::make_unique(), kInstanceIdWithLolaBinding}; auto mock_method_binding_ptr = std::make_unique(mock_method_binding_); - method_ = std::make_unique>( + method_ = std::make_unique>( empty_skeleton, "dummy_method", std::move(mock_method_binding_ptr)); } static constexpr std::size_t in_args_buffer_size = sizeof(InType1) + sizeof(InType2); - void SerializeBuffers(InType1 in_arg_1, InType2 in_arg_2) + void SerializeBuffers(InType1 in_arg_1, InType2 in_arg_2, InType2 in_arg_3) { constexpr std::size_t in_type_1_size = sizeof(InType1); + constexpr std::size_t in_type_2_size = sizeof(InType2); std::byte* write_head = in_args_buffer_.begin(); new (write_head) InType1(in_arg_1); write_head += in_type_1_size; new (write_head) InType2(in_arg_2); + write_head += in_type_2_size; + new (write_head) InType2(in_arg_3); } Thing GetTypedResultFromOutArgBuffer() { @@ -243,8 +262,8 @@ class SkeletonMethodGenericTestFixture : public ::testing::Test std::array out_arg_buffer_{}; std::array in_args_buffer_{}; - std::unique_ptr> method_{nullptr}; - ::testing::MockFunction typed_callback_mock_{}; + std::unique_ptr> method_{nullptr}; + ::testing::MockFunction typed_callback_mock_{}; std::optional typeerased_callback_{}; mock_binding::SkeletonMethod mock_method_binding_{}; }; @@ -265,11 +284,15 @@ TEST_F(SkeletonMethodThingStuffFixture, DataTransferBetweenTypedAndTypeErasedCal Thing ret_val{505}; InType1 in_arg_1{6.12}; InType2 in_arg_2{17}; + InType2 in_arg_3{18}; - // Expecting that a typed callable will be called with correctly deserialized inargs and will return a value - EXPECT_CALL(typed_callback_mock_, Call(in_arg_1, in_arg_2)).WillOnce(Return(ret_val)); + // Expecting that a typed callable will be called with correctly deserialized inargs and return value + EXPECT_CALL(typed_callback_mock_, Call(_, in_arg_1, in_arg_2, in_arg_3)) + .WillOnce(Invoke([ret_val](auto& return_arg, auto, auto, auto) { + return_arg = ret_val; + })); - SerializeBuffers(in_arg_1, in_arg_2); + SerializeBuffers(in_arg_1, in_arg_2, in_arg_3); EXPECT_TRUE(method_->RegisterHandler(typed_callback_mock_.AsStdFunction())); // When the type erased call is executed by the binding typeerased_callback_.value()(in_args_buffer_, out_arg_buffer_); @@ -294,8 +317,10 @@ TEST_F(SkeletonMethodThingVoidFixture, DataTransferBetweenTypedAndTypeErasedCall Thing ret_val{50255}; - // Expecting that a typed callable will be called without inargs and will return a value - EXPECT_CALL(typed_callback_mock_, Call()).WillOnce(Return(ret_val)); + // Expecting that a typed callable will be called without inargs and return a value + EXPECT_CALL(typed_callback_mock_, Call(_)).WillOnce(Invoke([ret_val](auto& return_arg) { + return_arg = ret_val; + })); EXPECT_TRUE(method_->RegisterHandler(typed_callback_mock_.AsStdFunction())); @@ -322,11 +347,12 @@ TEST_F(SkeletonMethodVoidStuffFixture, DataTransferBetweenTypedAndTypeErasedCall InType1 in_arg_1{0.6}; InType2 in_arg_2{0x1700}; + InType2 in_arg_3{0x1701}; - // Expecting that a typed callable will be called with correctly deserialized inargs and will not return a value - EXPECT_CALL(typed_callback_mock_, Call(in_arg_1, in_arg_2)).WillOnce(Return()); + // Expecting that a typed callable will be called with correctly deserialized inargs and no return value + EXPECT_CALL(typed_callback_mock_, Call(in_arg_1, in_arg_2, in_arg_3)); - SerializeBuffers(in_arg_1, in_arg_2); + SerializeBuffers(in_arg_1, in_arg_2, in_arg_3); EXPECT_TRUE(method_->RegisterHandler(typed_callback_mock_.AsStdFunction())); // When the type erased call is executed by the binding @@ -346,8 +372,8 @@ TEST_F(SkeletonMethodVoidVoidFixture, DataTransferBetweenTypedAndTypeErasedCallb return {}; })); - // Expecting that a typed callable will be called without inargs and will not return a value - EXPECT_CALL(typed_callback_mock_, Call()).WillOnce(Return()); + // Expecting that a typed callable will be called without inargs or return value + EXPECT_CALL(typed_callback_mock_, Call()); EXPECT_TRUE(method_->RegisterHandler(typed_callback_mock_.AsStdFunction())); // When the type erased call is executed by the binding diff --git a/score/mw/com/impl/skeleton_field.h b/score/mw/com/impl/skeleton_field.h index f9cd07006..f71df207c 100644 --- a/score/mw/com/impl/skeleton_field.h +++ b/score/mw/com/impl/skeleton_field.h @@ -14,6 +14,7 @@ #define SCORE_MW_COM_IMPL_SKELETON_FIELD_H #include "score/mw/com/impl/method_type.h" +#include "score/mw/com/impl/methods/method_handler_checker.h" #include "score/mw/com/impl/methods/skeleton_method.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" #include "score/mw/com/impl/plumbing/skeleton_field_binding_factory.h" @@ -123,24 +124,27 @@ class SkeletonField : public SkeletonFieldBase template = 0, typename CallableType> Result RegisterSetHandler(CallableType&& set_handler) { - static_assert(std::is_invocable_v, + static_assert(std::is_invocable_r_v, "RegisterSetHandler: handler must be callable as void(FieldType& value). " "The argument initially holds the proxy-requested value and may be modified in-place."); - auto wrapped_callback = - [this, set_handler = std::forward(set_handler)](FieldType& new_value) -> FieldType { + auto state = + std::make_unique>(this, std::forward(set_handler)); + + auto wrapped_callback = [state = std::move(state)](FieldType& final_value, const FieldType& desired_value) { + // Copy desired_value (which is a method InArg) into final_value (which is the method return value). + // final_value can then be modified in place by set_handler. + final_value = desired_value; + // Allow user to validate/modify the value in-place - set_handler(new_value); + state->second(final_value); - // Store the (possibly modified) value as the latest field value - auto update_result = this->Update(new_value); + // Copy the (possibly modified) value into the latest field value + auto update_result = state->first->Update(final_value); if (!update_result.has_value()) { score::mw::log::LogError("lola") << "Set handler: failed to update field value."; } - - // Return the accepted value to the proxy - return new_value; }; is_set_handler_registered_ = true; diff --git a/score/mw/com/impl/skeleton_field_test.cpp b/score/mw/com/impl/skeleton_field_test.cpp index 1255f5e12..131a1bc59 100644 --- a/score/mw/com/impl/skeleton_field_test.cpp +++ b/score/mw/com/impl/skeleton_field_test.cpp @@ -944,25 +944,30 @@ TEST_F(SkeletonFieldSetHandlerTest, PrepareOfferSucceedsWithoutHandlerWhenEnable EXPECT_TRUE(result.has_value()); } -TEST(SkeletonFieldSetHandlerTypeTraitsTest, RegisterSetHandlerAcceptsAnyCallable) +TEST_F(SkeletonFieldSetHandlerTest, RegisterSetHandlerAcceptsStdFunction) { - RecordProperty("Description", - "RegisterSetHandler() shall accept any callable (lambda, std::function, " - "score::cpp::callback) with signature void(FieldType&). " - "The public interface is not tied to a specific callable type."); - RecordProperty("TestType", "Requirements-based test"); - RecordProperty("Priority", "1"); - RecordProperty("DerivationTechnique", "Analysis of requirements"); + // Given a skeleton containing a field with a setter enabled + MySetterSkeleton unit{std::make_unique(), kInstanceIdWithLolaBinding}; + + // When registering a set handler using std::function + std::function std_function_handler = [](TestSampleType& /*value*/) noexcept {}; + const auto result = unit.my_setter_field_.RegisterSetHandler(std_function_handler).has_value(); - using HandlerSignature = void(TestSampleType&); + // Then the registration succeeds + EXPECT_TRUE(result); +} - // lambda - static_assert(std::is_invocable_v, TestSampleType&>, - "std::function with expected signature must be invocable"); +TEST_F(SkeletonFieldSetHandlerTest, RegisterSetHandlerAcceptsCppCallback) +{ + // Given a skeleton containing a field with a setter enabled + MySetterSkeleton unit{std::make_unique(), kInstanceIdWithLolaBinding}; + + // When registering a set handler using score::cpp::callback + score::cpp::callback cpp_callback_handler = [](TestSampleType& /*value*/) noexcept {}; + const auto result = unit.my_setter_field_.RegisterSetHandler(std::move(cpp_callback_handler)).has_value(); - // score::cpp::callback - static_assert(std::is_invocable_v, TestSampleType&>, - "score::cpp::callback with expected signature must be invocable"); + // Then the registration succeeds + EXPECT_TRUE(result); } // Handler wrapping: user callback is invoked and the field value is updated diff --git a/score/mw/com/test/methods/methods_test_resources/method_provider.h b/score/mw/com/test/methods/methods_test_resources/method_provider.h index ff36eed65..134779503 100644 --- a/score/mw/com/test/methods/methods_test_resources/method_provider.h +++ b/score/mw/com/test/methods/methods_test_resources/method_provider.h @@ -109,10 +109,11 @@ bool MethodProvider::RegisterMethodHandlerWithInArgsAndReturn() { SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(skeleton_ != nullptr); - auto handler_with_in_args_and_return = [](std::int32_t a, std::int32_t b) -> std::int32_t { - std::cout << "Provider: with_in_args_and_return called with " << a << " + " << b << std::endl; - return a + b; - }; + auto handler_with_in_args_and_return = + [](std::int32_t& return_value, const std::int32_t& a, const std::int32_t& b) { + std::cout << "Provider: with_in_args_and_return called with " << a << " + " << b << std::endl; + return_value = a + b; + }; const auto register_result = skeleton_->with_in_args_and_return.RegisterHandler(std::move(handler_with_in_args_and_return)); if (!register_result) @@ -129,8 +130,8 @@ template bool MethodProvider::RegisterMethodHandlerWithInArgsOnly(const std::int32_t expected_input_argument_a, const std::int32_t expected_input_argument_b) { - auto handler_with_in_args_only = [expected_input_argument_a, expected_input_argument_b](std::int32_t a, - std::int32_t b) { + auto handler_with_in_args_only = [expected_input_argument_a, expected_input_argument_b](const std::int32_t& a, + const std::int32_t& b) { std::cout << "Provider: with_in_args_only called with " << a << " + " << b << std::endl; SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(a == expected_input_argument_a, "Unexpected first InArg received!"); SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(b == expected_input_argument_b, "Unexpected second InArg received!"); @@ -149,9 +150,9 @@ bool MethodProvider::RegisterMethodHandlerWithInArgsOnly(const std::in template bool MethodProvider::RegisterMethodHandlerWithReturnOnly(const std::int32_t expected_return_value) { - auto handler_with_return_only = [expected_return_value]() -> std::int32_t { + auto handler_with_return_only = [expected_return_value](std::int32_t& return_value) { std::cout << "Provider: with_return_only called. Returning " << expected_return_value << std::endl; - return expected_return_value; + return_value = expected_return_value; }; const auto register_result = skeleton_->with_return_only.RegisterHandler(std::move(handler_with_return_only)); if (!register_result) diff --git a/score/mw/com/test/methods/non_trivial_constructors/provider.cpp b/score/mw/com/test/methods/non_trivial_constructors/provider.cpp index 5ee46c003..094794383 100644 --- a/score/mw/com/test/methods/non_trivial_constructors/provider.cpp +++ b/score/mw/com/test/methods/non_trivial_constructors/provider.cpp @@ -31,10 +31,11 @@ const std::string kInstanceSpecifier{"test/methods/non_trivial_constructors/Meth bool RegisterMethodHandlerWithInArgsAndReturn(NonTrivialConstructorSkeleton& skeleton) { - auto handler_with_in_args_and_return = [](NonTriviallyConstructibleType a, - NonTriviallyConstructibleType b) -> NonTriviallyConstructibleType { + auto handler_with_in_args_and_return = [](NonTriviallyConstructibleType& return_value, + const NonTriviallyConstructibleType& a, + const NonTriviallyConstructibleType& b) { std::cout << "Provider: with_in_args_and_return called with " << a << " + " << b << std::endl; - return a + b; + return_value = a + b; }; const auto register_result = skeleton.with_in_args_and_return.RegisterHandler(std::move(handler_with_in_args_and_return)); @@ -50,7 +51,8 @@ bool RegisterMethodHandlerWithInArgsAndReturn(NonTrivialConstructorSkeleton& ske bool RegisterMethodHandlerWithInArgsOnly(NonTrivialConstructorSkeleton& skeleton) { - auto handler_with_in_args_only = [](NonTriviallyConstructibleType a, NonTriviallyConstructibleType b) { + auto handler_with_in_args_only = [](const NonTriviallyConstructibleType& a, + const NonTriviallyConstructibleType& b) { std::cout << "Provider: with_in_args_only called with " << a << " + " << b << std::endl; SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(a == NonTriviallyConstructibleType{}, "Unexpected first InArg received!"); @@ -70,9 +72,9 @@ bool RegisterMethodHandlerWithInArgsOnly(NonTrivialConstructorSkeleton& skeleton bool RegisterMethodHandlerWithReturnOnly(NonTrivialConstructorSkeleton& skeleton) { - auto handler_with_return_only = []() -> NonTriviallyConstructibleType { + auto handler_with_return_only = [](NonTriviallyConstructibleType& return_value) { std::cout << "Provider: with_return_only called. Returning " << NonTriviallyConstructibleType{} << std::endl; - return NonTriviallyConstructibleType{}; + return_value = NonTriviallyConstructibleType{}; }; const auto register_result = skeleton.with_return_only.RegisterHandler(std::move(handler_with_return_only)); if (!register_result) From 5729f2f32161f2226fcfbb2b008c46fd3ace3ea6 Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Thu, 28 May 2026 11:37:53 +0200 Subject: [PATCH 05/15] mw/com: Use nice mocks to avoid spam in test logs --- score/mw/com/impl/traits_test.cpp | 41 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/score/mw/com/impl/traits_test.cpp b/score/mw/com/impl/traits_test.cpp index f99961ee5..c54481a4a 100644 --- a/score/mw/com/impl/traits_test.cpp +++ b/score/mw/com/impl/traits_test.cpp @@ -39,6 +39,7 @@ namespace using ::testing::_; using ::testing::ByMove; using ::testing::Invoke; +using ::testing::NiceMock; using ::testing::Return; using ::testing::ReturnRef; @@ -87,8 +88,8 @@ class RuntimeMockGuard Runtime::InjectMock(nullptr); } - RuntimeMock runtime_mock_; - ServiceDiscoveryMock service_discovery_mock_{}; + NiceMock runtime_mock_; + NiceMock service_discovery_mock_{}; }; class ProxyCreationFixture : public ::testing::Test @@ -150,12 +151,12 @@ class ProxyCreationFixture : public ::testing::Test ProxyEventBindingFactoryMockGuard proxy_event_binding_factory_mock_guard_{}; ProxyFieldBindingFactoryMockGuard proxy_field_binding_factory_mock_guard_{}; ProxyMethodBindingFactoryMockGuard proxy_method_binding_factory_mock_guard_{}; - mock_binding::Proxy proxy_binding_mock_{}; - mock_binding::ProxyEvent proxy_event_binding_mock_{}; - mock_binding::ProxyEvent proxy_field_binding_mock_{}; - mock_binding::ProxyMethod proxy_method_binding_mock_{}; - mock_binding::ProxyMethod proxy_field_set_binding_mock_{}; - mock_binding::ProxyMethod proxy_field_get_binding_mock_{}; + NiceMock proxy_binding_mock_{}; + NiceMock> proxy_event_binding_mock_{}; + NiceMock> proxy_field_binding_mock_{}; + NiceMock proxy_method_binding_mock_{}; + NiceMock proxy_field_set_binding_mock_{}; + NiceMock proxy_field_get_binding_mock_{}; }; TEST(GeneratedProxyTest, NotCopyable) @@ -526,12 +527,12 @@ class SkeletonCreationFixture : public ::testing::Test SkeletonEventBindingFactoryMockGuard skeleton_event_binding_factory_mock_guard_{}; SkeletonFieldBindingFactoryMockGuard skeleton_field_binding_factory_mock_guard_{}; SkeletonMethodBindingFactoryMockGuard skeleton_method_binding_factory_mock_guard_{}; - mock_binding::Skeleton skeleton_binding_mock_{}; - mock_binding::SkeletonEvent skeleton_event_binding_mock_{}; - mock_binding::SkeletonEvent skeleton_field_binding_mock_{}; - mock_binding::SkeletonMethod skeleton_method_binding_mock_{}; - mock_binding::SkeletonMethod skeleton_field_set_binding_mock_{}; - mock_binding::SkeletonMethod skeleton_field_get_binding_mock_{}; + NiceMock skeleton_binding_mock_{}; + NiceMock> skeleton_event_binding_mock_{}; + NiceMock> skeleton_field_binding_mock_{}; + NiceMock skeleton_method_binding_mock_{}; + NiceMock skeleton_field_set_binding_mock_{}; + NiceMock skeleton_field_get_binding_mock_{}; }; using GeneratedSkeletonCreationInstanceSpecifierTestFixture = SkeletonCreationFixture; @@ -1016,12 +1017,12 @@ class GeneratedSkeletonStopOfferServiceRaiiFixture : public SkeletonCreationFixt bool skeleton_event_stop_offer_called_2_{false}; bool skeleton_field_stop_offer_called_2_{false}; - mock_binding::Skeleton skeleton_binding_mock_2_{}; - mock_binding::SkeletonEvent skeleton_event_binding_mock_2_{}; - mock_binding::SkeletonEvent skeleton_field_binding_mock_2_{}; - mock_binding::SkeletonMethod skeleton_method_binding_mock_2_{}; - mock_binding::SkeletonMethod skeleton_field_set_binding_mock_2_{}; - mock_binding::SkeletonMethod skeleton_field_get_binding_mock_2_{}; + NiceMock skeleton_binding_mock_2_{}; + NiceMock> skeleton_event_binding_mock_2_{}; + NiceMock> skeleton_field_binding_mock_2_{}; + NiceMock skeleton_method_binding_mock_2_{}; + NiceMock skeleton_field_set_binding_mock_2_{}; + NiceMock skeleton_field_get_binding_mock_2_{}; std::optional skeleton_{}; std::optional skeleton_2_{}; From 8d38212db9bcbdac88463c97dc7b255176743784 Mon Sep 17 00:00:00 2001 From: muhseth Date: Tue, 31 Mar 2026 15:05:13 +0200 Subject: [PATCH 06/15] mw/com: Clean up sample timestamp usage - introduce InvalidTimestamp/FirstValidTimestamp constants - adapt timestamp tests --- .../bindings/lola/event_data_control_composite.cpp | 2 +- .../lola/event_data_control_composite_test.cpp | 12 ++++++------ score/mw/com/impl/bindings/lola/event_slot_status.h | 10 ++++++++-- .../com/impl/bindings/lola/skeleton_event_test.cpp | 4 ++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/score/mw/com/impl/bindings/lola/event_data_control_composite.cpp b/score/mw/com/impl/bindings/lola/event_data_control_composite.cpp index 5dce0f2b3..9219f70f6 100644 --- a/score/mw/com/impl/bindings/lola/event_data_control_composite.cpp +++ b/score/mw/com/impl/bindings/lola/event_data_control_composite.cpp @@ -236,7 +236,7 @@ template