diff --git a/score/mw/com/design/ipc_tracing/structural_event_field_extensions.puml b/score/mw/com/design/ipc_tracing/structural_event_field_extensions.puml index a1e164658..321121be3 100644 --- a/score/mw/com/design/ipc_tracing/structural_event_field_extensions.puml +++ b/score/mw/com/design/ipc_tracing/structural_event_field_extensions.puml @@ -27,7 +27,6 @@ class "score::mw::com::impl::SkeletonEventBase" { +SkeletonEventBase(std::unique_ptr binding) +PrepareOffer(): score::Result +PrepareStopOffer(): void - +UpdateSkeletonReference(SkeletonBase& base_skeleton): void -binding_: std::unique_ptr -tracing_data_: SkeletonEventTracing } diff --git a/score/mw/com/design/skeleton_proxy/skeleton_binding_model.puml b/score/mw/com/design/skeleton_proxy/skeleton_binding_model.puml index 6db774ecc..430bc06b1 100644 --- a/score/mw/com/design/skeleton_proxy/skeleton_binding_model.puml +++ b/score/mw/com/design/skeleton_proxy/skeleton_binding_model.puml @@ -40,8 +40,7 @@ abstract class "score::mw::com::impl::SkeletonBase" as SkeletonBase { +StopOfferService(): void -- Notes: - SkeletonBase is moveable but not copyable. On moving, the SkeletonBase should call - UpdateSkeletonReference on all events and fields within its events_ and fields_ maps. + SkeletonBase is moveable but not copyable. On moving, the SkeletonBase updates method references. } class SkeletonBinding { @@ -91,7 +90,6 @@ abstract class "score::mw::com::impl::SkeletonFieldBase" as SkeletonFieldBase { +SkeletonFieldBase(SkeletonBase&, std::string_view field_name, std::unique_ptr) +PrepareOffer(): Result +PrepareStopOffer(): void - +UpdateSkeletonReference(SkeletonBase& base_skeleton): void -IsInitialValueSaved(): bool = 0 -DoDeferredUpdate(): Result = 0 @@ -108,7 +106,6 @@ class "score::mw::com::impl::SkeletonEventBase" as SkeletonEventBase { +SkeletonEventBase(std::unique_ptr binding) +PrepareOffer(): score::Result +PrepareStopOffer(): void - +UpdateSkeletonReference(SkeletonBase& base_skeleton): void -- Notes: @@ -118,8 +115,7 @@ class "score::mw::com::impl::SkeletonEventBase" as SkeletonEventBase { This allows SkeletonEventBase to access the type independent methods of SkeletonEventBindingBase. Derived classes i.e. impl::SkeletonEvent should downcast binding_ to a SkeletonEventBinding in order to use type - dependent methods. On moving, the SkeletonEvent should call UpdateEvent on its - parent skeleton so that the parent stores its new address. + dependent methods. } class SkeletonServiceMethodBinding { diff --git a/score/mw/com/design/skeleton_proxy/skeleton_proxy_binding_model.puml b/score/mw/com/design/skeleton_proxy/skeleton_proxy_binding_model.puml index 91ebcc955..323e966eb 100644 --- a/score/mw/com/design/skeleton_proxy/skeleton_proxy_binding_model.puml +++ b/score/mw/com/design/skeleton_proxy/skeleton_proxy_binding_model.puml @@ -55,8 +55,7 @@ abstract class "score::mw::com::impl::SkeletonBase" as ScoreMwComImplSkeletonBas +StopOfferService(): void -- Notes: - SkeletonBase is moveable but not copyable. On moving, the SkeletonBase should call UpdateSkeletonReference on all - events and fields within its events_ and fields_ maps. + SkeletonBase is moveable but not copyable. On moving, the SkeletonBase updates method references. } abstract class "DummySkeleton\n{{generated}}" as DummySkeleton { @@ -75,7 +74,6 @@ class "score::mw::com::impl::SkeletonEventBase" as ScoreMwComImplSkeletonEventBa +SkeletonEventBase(std::unique_ptr binding) +PrepareOffer(): score::Result +PrepareStopOffer(): void - +UpdateSkeletonReference(SkeletonBase& base_skeleton): void -- Notes: SkeletonEventBase is moveable but not copyable. @@ -84,15 +82,13 @@ class "score::mw::com::impl::SkeletonEventBase" as ScoreMwComImplSkeletonEventBa This allows SkeletonEventBase to access the type independent methods of SkeletonEventBindingBase. Derived classes i.e. impl::SkeletonEvent should downcast binding_ to a SkeletonEventBinding in order to use type - dependent methods. On moving, the SkeletonEvent should call UpdateEvent on its - parent skeleton so that the parent stores its new address. + dependent methods. } class "score::mw::com::impl::SkeletonFieldBase" as ScoreMwComImplSkeletonFieldBase { +SkeletonFieldBase(SkeletonBase&, std::string_view field_name, std::unique_ptr) +PrepareOffer(): Result +PrepareStopOffer(): void - +UpdateSkeletonReference(SkeletonBase& base_skeleton): void -- #skeleton_event_dispatch_ : std::unique_ptr #skeleton_base_ : std::reference_wrapper diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 2e11a3422..3b1787e40 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -192,6 +192,7 @@ cc_library( deps = [ ":data_type_meta_info", ":generic_skeleton_event_binding", + ":moveable_reference", ":skeleton_base", ":skeleton_event_base", ":skeleton_event_binding", @@ -243,6 +244,7 @@ cc_library( ], deps = [ ":instance_identifier", + ":moveable_reference", ":runtime", ":skeleton_base", ":skeleton_event_base", @@ -352,6 +354,7 @@ cc_library( ], deps = [ ":flag_owner", + ":moveable_reference", ":skeleton_event_binding", "//score/mw/com/impl/tracing:skeleton_event_tracing", "//score/mw/com/impl/tracing:skeleton_event_tracing_data", @@ -371,6 +374,7 @@ cc_library( ], deps = [ ":error", + ":moveable_reference", ":skeleton_event_base", "//score/mw/com/impl/methods:skeleton_method_base", "@score_baselibs//score/language/futurecpp", @@ -393,6 +397,7 @@ cc_library( deps = [ ":flag_owner", ":instance_identifier", + ":moveable_reference", ":runtime", ":skeleton_binding", ":skeleton_event_base", @@ -1044,6 +1049,20 @@ cc_library( ], ) +cc_library( + name = "moveable_reference", + srcs = [ + "reference_to_moveable.cpp", + "reference_to_moveable_owner.cpp", + ], + hdrs = [ + "reference_to_moveable.h", + "reference_to_moveable_owner.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], +) + cc_unit_test( name = "unit_test_runtime_single_exec", srcs = ["runtime_single_exec_test.cpp"], @@ -1309,6 +1328,28 @@ cc_unit_test( ], ) +cc_unit_test( + name = "reference_to_moveable_test", + srcs = [ + "reference_to_moveable_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":moveable_reference", + ], +) + +cc_unit_test( + name = "reference_to_moveable_owner_test", + srcs = [ + "reference_to_moveable_owner_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":moveable_reference", + ], +) + cc_unit_test( name = "unit_test", srcs = [ diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp index 8ca84b28a..1de17c32c 100644 --- a/score/mw/com/impl/generic_skeleton_event.cpp +++ b/score/mw/com/impl/generic_skeleton_event.cpp @@ -26,7 +26,7 @@ GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base, std::unique_ptr binding) : SkeletonEventBase(skeleton_base, event_name, std::move(binding)) { - SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, *this); + SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, reference_to_moveable_owner_.Get()); if (binding_ != nullptr) { diff --git a/score/mw/com/impl/methods/BUILD b/score/mw/com/impl/methods/BUILD index 0c9ba0b8e..c6687676e 100644 --- a/score/mw/com/impl/methods/BUILD +++ b/score/mw/com/impl/methods/BUILD @@ -28,6 +28,46 @@ cc_library( ], ) +cc_library( + name = "method_traits_checker", + srcs = ["method_traits_checker.cpp"], + hdrs = ["method_traits_checker.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ":callable_traits", + "@score_baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "callable_traits", + hdrs = [ + "callable_traits.h", + "callable_traits_impl.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ], +) + +cc_unit_test( + name = "callable_traits_test", + srcs = ["callable_traits_test.cpp"], + deps = [ + ":callable_traits", + "//score/mw/com/impl/methods/test:callable_traits_resources", + "@googletest//:gtest", + ], +) + cc_library( name = "proxy_method_base", srcs = ["proxy_method_base.cpp"], @@ -111,6 +151,20 @@ cc_unit_test( ], ) +cc_unit_test( + name = "method_traits_checker_test", + srcs = ["method_traits_checker_test.cpp"], + features = COMPILER_WARNING_FEATURES + [ + # These tests catch exceptions instead of using gtest EXPECT_DEATH so we disable aborts_upon_exception. + "-aborts_upon_exception", + ], + deps = [ + ":method_traits_checker", + "@googletest//:gtest", + "@score_baselibs//score/language/futurecpp:futurecpp_test_support", + ], +) + cc_unit_test( name = "proxy_method_test", srcs = ["proxy_method_test.cpp"], @@ -140,6 +194,7 @@ cc_library( deps = [ ":skeleton_method_binding", "//score/mw/com/impl:method_type", + "//score/mw/com/impl:moveable_reference", "//score/mw/com/impl/util:type_erased_storage", ], ) @@ -154,6 +209,7 @@ cc_library( "//score/mw/com/impl:__subpackages__", ], deps = [ + ":method_traits_checker", ":skeleton_method_binding", "//score/mw/com/impl:instance_identifier", "//score/mw/com/impl:skeleton_base", @@ -186,6 +242,7 @@ cc_unit_test( deps = [ ":skeleton_method", "//score/mw/com/impl/bindings/mock_binding", + "//score/mw/com/impl/methods/test:callable_traits_resources", "//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/callable_traits.cpp b/score/mw/com/impl/methods/callable_traits.cpp new file mode 100644 index 000000000..b874eecf8 --- /dev/null +++ b/score/mw/com/impl/methods/callable_traits.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_traits.h" diff --git a/score/mw/com/impl/methods/callable_traits.h b/score/mw/com/impl/methods/callable_traits.h new file mode 100644 index 000000000..8dd8f6f63 --- /dev/null +++ b/score/mw/com/impl/methods/callable_traits.h @@ -0,0 +1,79 @@ +/******************************************************************************** + * 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_CALLABLE_TRAITS_H +#define SCORE_MW_COM_IMPL_METHODS_METHOD_CALLABLE_TRAITS_H + +#include +#include + +namespace score::mw::com::impl +{ + +/// \brief Implementation of std::remove_cvref which is only available in C++20. +template +using remove_cvref_t = std::remove_cv_t>; + +/// \brief Helper struct to check if a tuple type is empty. +template +struct is_tuple_empty : std::false_type +{ +}; + +template +struct is_tuple_empty == 0U>> : std::true_type +{ +}; + +template +using is_tuple_empty_t = typename is_tuple_empty::type; + +/// \brief Helper struct to extract the tail of a tuple (i.e. all elements except the first one) as a tuple type. +template +struct get_tuple_tail; + +template +struct get_tuple_tail> +{ + using type = std::tuple; +}; + +template +using get_tuple_tail_t = typename get_tuple_tail::type; + +/// \brief Helper struct to extract the in argument types of a callable as a tuple type in member alias `type`. +/// +/// e.g. for a callable of type `int(const double&, int, const std::string), `get_callable_args_types::type` will be `std::tuple`. +/// Full implementation is in callable_traits_impl.h. +template +struct get_callable_args_types : public get_callable_args_types::operator())> +{ +}; + +/// \brief Helper struct to extract the return type of a callable as a type in member alias `type`. +/// +/// e.g. for a callable of type `int(const double&, int, const std::string), `get_callable_return_type::type` will be `int`. +/// Full implementation is in callable_traits_impl.h. +template +struct get_callable_return_type : public get_callable_return_type::operator())> +{ +}; + +} // namespace score::mw::com::impl + +// To avoid keeping the large number of struct specializations for different callable types in the header file, we put +// them in a separate implementation header file. +#include "score/mw/com/impl/methods/callable_traits_impl.h" + +#endif // SCORE_MW_COM_IMPL_METHODS_METHOD_CALLABLE_TRAITS_H diff --git a/score/mw/com/impl/methods/callable_traits_impl.h b/score/mw/com/impl/methods/callable_traits_impl.h new file mode 100644 index 000000000..d95f7f143 --- /dev/null +++ b/score/mw/com/impl/methods/callable_traits_impl.h @@ -0,0 +1,72 @@ +/******************************************************************************** + * 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_CALLABLE_TRAITS_IMPL_H +#define SCORE_MW_COM_IMPL_METHODS_METHOD_CALLABLE_TRAITS_IMPL_H + +#include +#include + +namespace score::mw::com::impl +{ + +template +struct get_callable_args_types +{ + using type = std::tuple; +}; + +template +struct get_callable_args_types +{ + using type = std::tuple; +}; + +template +struct get_callable_args_types +{ + using type = std::tuple; +}; + +template +struct get_callable_args_types +{ + using type = std::tuple; +}; + +template +struct get_callable_return_type +{ + using type = Ret; +}; + +template +struct get_callable_return_type +{ + using type = Ret; +}; + +template +struct get_callable_return_type +{ + using type = Ret; +}; + +template +struct get_callable_return_type +{ + using type = Ret; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_METHODS_METHOD_CALLABLE_TRAITS_IMPL_H diff --git a/score/mw/com/impl/methods/callable_traits_test.cpp b/score/mw/com/impl/methods/callable_traits_test.cpp new file mode 100644 index 000000000..9a555fc03 --- /dev/null +++ b/score/mw/com/impl/methods/callable_traits_test.cpp @@ -0,0 +1,243 @@ +/******************************************************************************** + * 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/callable_traits.h" +#include "score/mw/com/impl/methods/test/callable_traits_resources.h" + +#include + +#include + +#include +#include +#include + +namespace score::mw::com::impl +{ + +// Test data structures +struct MyDataStruct +{ + int i; + std::string s; + bool b; +}; + +template +struct ReturnTypeTestData +{ + using CallableType = Callable; + using ExpectedReturnType = ExpectedReturn; +}; + +template +class CallableTraitsReturnTypeFixture : public ::testing::Test +{ + public: + using CallableType = typename ReturnTypeTestData::CallableType; + using ExpectedReturnType = typename ReturnTypeTestData::ExpectedReturnType; +}; + +using MyReturnTypeTestData = ::testing::Types< + ReturnTypeTestData, int>, + ReturnTypeTestData, MyDataStruct>, + ReturnTypeTestData, void>, + ReturnTypeTestData, void>, + + ReturnTypeTestData, int>, + ReturnTypeTestData, MyDataStruct>, + ReturnTypeTestData, void>, + ReturnTypeTestData, void>, + + ReturnTypeTestData, int>, + ReturnTypeTestData, MyDataStruct>, + ReturnTypeTestData, void>, + ReturnTypeTestData, void>, + + ReturnTypeTestData, int>, + ReturnTypeTestData, MyDataStruct>, + ReturnTypeTestData, void>, + ReturnTypeTestData, void>, + + ReturnTypeTestData, int>, + ReturnTypeTestData, MyDataStruct>, + ReturnTypeTestData, void>, + ReturnTypeTestData, void>, + + ReturnTypeTestData, int>, + ReturnTypeTestData, MyDataStruct>, + ReturnTypeTestData, void>, + ReturnTypeTestData, void>, + + ReturnTypeTestData::Create()), int>, + ReturnTypeTestData::Create()), MyDataStruct>, + ReturnTypeTestData::Create()), void>, + ReturnTypeTestData::Create()), void>, + + ReturnTypeTestData::Create()), int>, + ReturnTypeTestData::Create()), MyDataStruct>, + ReturnTypeTestData::Create()), void>, + ReturnTypeTestData::Create()), void>, + + ReturnTypeTestData::Create()), int>, + ReturnTypeTestData::Create()), MyDataStruct>, + ReturnTypeTestData::Create()), void>, + ReturnTypeTestData::Create()), void>, + + ReturnTypeTestData::Create()), int>, + ReturnTypeTestData::Create()), MyDataStruct>, + ReturnTypeTestData::Create()), void>, + ReturnTypeTestData::Create()), void> + + >; +TYPED_TEST_SUITE(CallableTraitsReturnTypeFixture, MyReturnTypeTestData); + +TYPED_TEST(CallableTraitsReturnTypeFixture, GetCallableReturnTypeForCallable) +{ + // When extracting the return type from a callable type + using ActualReturnType = typename get_callable_return_type::type; + + // Then the extracted return type matches the expected return type + EXPECT_TRUE((std::is_same_v)); +} + +template +struct InArgTypesTestData +{ + using CallableType = Callable; + using ExpectedInArgTypes = ExpectedInArgs; +}; + +template +class CallableTraitsInArgTypesFixture : public ::testing::Test +{ + public: + using CallableType = typename InArgTypesTestData::CallableType; + using ExpectedInArgTypes = typename InArgTypesTestData::ExpectedInArgTypes; +}; + +// clang-format off +using MyInArgTypesTestData = ::testing::Types< + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + InArgTypesTestData, std::tuple>, + + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple>, + InArgTypesTestData::Create()), std::tuple> + >; +// clang-format on + +TYPED_TEST_SUITE(CallableTraitsInArgTypesFixture, MyInArgTypesTestData); + +TYPED_TEST(CallableTraitsInArgTypesFixture, GetCallableInArgTypesForCallable) +{ + // When extracting the InArg types from a callable type + using ActualInArgTypes = typename get_callable_args_types::type; + + // Then the extracted InArg types match the expected InArg types + EXPECT_TRUE((std::is_same_v)); +} + +TEST(CallableTraitsIsTupleEmptyTest, EmptyTupleReturnsTrue) +{ + // When checking if a tuple containg no types is empty + // Then the result is true + EXPECT_TRUE(is_tuple_empty>::value); +} + +TEST(CallableTraitsIsTupleEmptyTest, TupleWithOneArgReturnsFalse) +{ + // When checking if a tuple containg one type is empty + // Then the result is false + EXPECT_FALSE(is_tuple_empty>::value); +} + +TEST(CallableTraitsIsTupleEmptyTest, TupleWithMultipleArgsReturnsFalse) +{ + // When checking if a tuple containg multiple types is empty + // Then the result is false + EXPECT_FALSE((is_tuple_empty>::value)); +} + +TEST(CallableTraitsGetTupleTailTest, GetTupleTailOfSingleElementTupleReturnsEmptyTuple) +{ + // When extracting the tail of a tuple containing one type + using Tail = typename get_tuple_tail>::type; + + // Then the extracted tail is an empty tuple + EXPECT_TRUE((std::is_same_v>)); +} + +TEST(CallableTraitsGetTupleTailTest, GetTupleTailOfMultiElementTupleReturnsTail) +{ + // When extracting the tail of a tuple containing multiple types + using Tail = typename get_tuple_tail>::type; + + // Then the extracted tail is a tuple containing all types except the first one + EXPECT_TRUE((std::is_same_v>)); +} + +// 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(CallableTraitsGetTupleTailTest, GetTupleTailOfEmptyTupleFailsToCompile) +{ + // When extracting the tail of an empty tuple + // Then we should get a compiler error + using Tail = typename get_tuple_tail>::type; +} +#endif + +} // namespace score::mw::com::impl diff --git a/score/mw/com/impl/methods/method_traits_checker.cpp b/score/mw/com/impl/methods/method_traits_checker.cpp new file mode 100644 index 000000000..ccc28d1eb --- /dev/null +++ b/score/mw/com/impl/methods/method_traits_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_traits_checker.h" diff --git a/score/mw/com/impl/methods/method_traits_checker.h b/score/mw/com/impl/methods/method_traits_checker.h new file mode 100644 index 000000000..67855ac10 --- /dev/null +++ b/score/mw/com/impl/methods/method_traits_checker.h @@ -0,0 +1,206 @@ +/******************************************************************************** + * 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_TRAITS_CHECKER_H +#define SCORE_MW_COM_IMPL_METHODS_METHOD_TRAITS_CHECKER_H + +#include "score/mw/com/impl/methods/callable_traits.h" + +#include + +#include +#include +#include + +namespace score::mw::com::impl +{ +namespace detail +{ + +/// \brief Utility to extract the method return type from a tuple of the form `std::tuple` +/// The tuple can be extracter from a callabe using `get_callable_args_types`. +/// +/// \return type of the first element of the tuple (i.e. `ReturnType`) +template +struct get_method_return_type; + +template +struct get_method_return_type> +{ + using type = std::tuple_element_t<0, std::tuple>; +}; + +template +using get_method_return_type_t = typename get_method_return_type::type; + +/// \brief Utility to extract the method in argument types from a tuple of the form `std::tuple` +/// The tuple can be extracter from a callabe using `get_callable_args_types`. +/// +/// \return tuple type `std::tuple` +template +struct get_method_in_args; + +template +struct get_method_in_args> +{ + using type = get_tuple_tail_t>; +}; + +template +using get_method_in_args_t = typename get_method_in_args::type; + +} // namespace detail + +enum class FailureMode +{ + COMPILE_TIME, + RUNTIME +}; + +// TODO: We currently use FailureMode as a workaround to allow testing AssertMethodHandlerSupportsMethodSignature at runtime +// since we don't currently have infrastructure for compile time testing. When compile time testing is enabled in +// SWP-46885, we can remove FailureMode and the runtime tests and replace them with compile time tests. +template +void CompileOrRuntimeAssert(const char* message) +{ + if constexpr (failure_mode == FailureMode::COMPILE_TIME) + { + static_assert(condition); + } + else + { + if (!condition) + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(condition, message); + } + } +} + +/// \brief Checks at compile time whether a given callable type matches the expected signature for a method handler. +/// +/// For a method signature of the form `ReturnType(ArgTypes...)`, the expected callable signature is: +/// - If `ReturnType` is not `void` and there are in arguments: +/// `void(ReturnType&, const ArgTypes&...)` +/// - If `ReturnType` is not `void` and there are no in arguments: +/// `void(ReturnType&)` +/// - If `ReturnType` is `void` and there are in arguments: +/// `void(const ArgTypes&...)` +/// - If `ReturnType` is `void` and there are no in arguments: +/// `void()` +template +constexpr void AssertMethodHandlerSupportsMethodSignature() +{ + using CallableReturnType = typename get_callable_return_type::type; + CompileOrRuntimeAssert, FailureMode>( + "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!"); + + using CallableArgs = typename get_callable_args_types::type; + + constexpr bool has_in_args = (sizeof...(ArgTypes) != 0); + constexpr bool has_return_type = !std::is_same_v; + + // Since we want to be able to check assertions at runtime during testing using CompileOrRuntimeAssert, we have to + // avoid compile time assertion failures in other places. Anything in the else branch of a constexpr if statement + // will not be compiled if the condition is false. So we use else branches instead of early returns. Once compile + // time testing is enabled in SWP-46885, we can likely simplify the branching in this code. + if constexpr (!has_in_args && !has_return_type) + { + CompileOrRuntimeAssert>, FailureMode>( + "Registered method callable must not have any arguments since the method signature does not specify " + "any in arguments or return type!"); + } + else + { + if constexpr (is_tuple_empty::value) + { + CompileOrRuntimeAssert::value, FailureMode>( + "Registered method callable must have at least one argument (a return type and/or one or more in " + "args)"); + } + else + { + + if constexpr (has_in_args && has_return_type) + { + using MethodInArgTuple = detail::get_method_in_args_t; + using MethodReturnType = detail::get_method_return_type_t; + + using ExpectedInArgTypeTuple = std::tuple; + using ExpectedReturnType = ReturnType&; + + CompileOrRuntimeAssert, FailureMode>( + "Registered method callable must have the method return type as first argument as non-const " + "reference!"); + CompileOrRuntimeAssert, FailureMode>( + "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 MethodReturnType = detail::get_method_return_type_t; + using ExpectedReturnType = ReturnType&; + + CompileOrRuntimeAssert, FailureMode>( + "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 MethodInArgTuple = CallableArgs; + using ExpectedInArgTypeTuple = std::tuple; + + CompileOrRuntimeAssert, FailureMode>( + "Registered method callable must have only the in argument types from the method signature, but " + "with const reference semantics!"); + } + } + } +} + +/// \brief Checks at compile time that the given callable is not a std::bind expression. +/// +/// std::bind expressions make checking the callable signature in AssertMethodHandlerSupportsMethodSignature difficult. Since +/// the functionality of std::bind can be achieved using lambdas (which is even recommended over std::bind in general), +/// we disallow std::bind expressions as method handlers. +template +constexpr void AssertMethodCallableIsNotStdBind() +{ + CompileOrRuntimeAssert, FailureMode>( + "std::bind expressions are not allowed as method handlers as they are not supported by " + "AssertMethodHandlerSupportsMethodSignature which checks the callable signature. Please use a lambda instead."); +} + +/// \brief Checks at compile time that the method signature does not contain pointers or references in the return type +/// or in args types. +/// +/// Pointers and references in a handler will only be valid in the process which initializes them. Since method return +/// types and in args are placed in shared memory, we disallow pointers and references. +template +constexpr void AssertMethodSignatureDoesNotContainPointersOrReferences() +{ + CompileOrRuntimeAssert, FailureMode>("Method return type must not be a pointer!"); + CompileOrRuntimeAssert, FailureMode>( + "Method return type must not be a reference!"); + + constexpr bool in_args_are_not_pointers = (... && !std::is_pointer_v); + CompileOrRuntimeAssert("Method in args must not be pointers!"); + + constexpr bool in_args_are_not_references = (... && !std::is_reference_v); + CompileOrRuntimeAssert("Method in args must not be references!"); +} + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_METHODS_METHOD_TRAITS_CHECKER_H diff --git a/score/mw/com/impl/methods/method_traits_checker_test.cpp b/score/mw/com/impl/methods/method_traits_checker_test.cpp new file mode 100644 index 000000000..9b39595f6 --- /dev/null +++ b/score/mw/com/impl/methods/method_traits_checker_test.cpp @@ -0,0 +1,550 @@ +/******************************************************************************** + * 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_traits_checker.h" + +#include + +#include + +#include + +namespace score::mw::com::impl +{ +namespace +{ + +TEST(MethodTraitsCheckerReturnAndInArgsCompileTimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerReturnOnlyCompileTimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerInArgsOnlyCompileTimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerNoReturnAndNoInArgsCompileTimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerGetMethodReturnType, CallingWithTupleContainingOneElementReturnsFirstElement) +{ + using ExpectedMethodReturnType = int; + using MethodArgsTuple = std::tuple; + + // When extracting the method return type from a tuple containing a single element + using ActualMethodReturnType = detail::get_method_return_type_t; + + // Then the extracted method return type is the same as the first element of the tuple + EXPECT_TRUE((std::is_same_v)); +} + +TEST(MethodTraitsCheckerGetMethodReturnType, CallingWithTupleContainingMultipleElementsReturnsFirstElement) +{ + using ExpectedMethodReturnType = int; + using MethodArgsTuple = std::tuple; + + // When extracting the method return type from a tuple containing multiple elements + using ActualMethodReturnType = detail::get_method_return_type_t; + + // Then the extracted method return type is the same as the first element of the tuple + EXPECT_TRUE((std::is_same_v)); +} + +// 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(MethodTraitsCheckerGetMethodReturnType, CallingWithEmptyTupleFailsToCompile) +{ + // When extracting the method return type from an empty tuple + // Then we should get a compiler error + using ActualMethodReturnType = detail::get_method_return_type_t>; +} + +TEST(MethodTraitsCheckerGetMethodReturnType, CallingWithCallableFailsToCompile) +{ + // When extracting the method return type from a callable type + // Then we should get a compiler error + using ActualMethodReturnType = detail::get_method_return_type_t>; +} + +#endif + +TEST(MethodTraitsCheckerGetMethodInArgType, CallingWithTupleContainingOneElementReturnsEmptyTuple) +{ + using MethodArgsTuple = std::tuple; + + // When extracting the method in argument types from a tuple containing a single element + using ActualMethodInArgTypes = detail::get_method_in_args_t; + + // Then the extracted method in argument types is an empty tuple (since the first argument of the tuple is the + // return type) + EXPECT_TRUE((std::is_same_v>)); +} + +TEST(MethodTraitsCheckerGetMethodInArgType, CallingWithTupleContainingMultipleElementsReturnsTupleTail) +{ + using ExpectedMethodInArgTypes = std::tuple; + using MethodArgsTuple = std::tuple; + + // When extracting the method in argument types from a tuple containing multiple elements + using ActualMethodInArgTypes = detail::get_method_in_args_t; + + // Then the extracted method in argument types is a tuple containing all elements of the original tuple except the + // first element (since the first element of the tuple is the return type) + EXPECT_TRUE((std::is_same_v)); +} + +// 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(MethodTraitsCheckerGetMethodInArgType, CallingWithEmptyTupleFailsToCompile) +{ + // When extracting the method in argument types from an empty tuple + // Then we should get a compiler error + using ActualMethodInArgTypes = detail::get_method_in_args_t>; +} + +TEST(MethodTraitsCheckerGetMethodInArgType, CallingWithCallableFailsToCompile) +{ + // When extracting the method in argument types from a callable type + // Then we should get a compiler error + using ActualMethodInArgTypes = detail::get_method_in_args_t>; +} + +#endif + +// TODO: We currently use FailureMode as a workaround to allow testing AssertMethodHandlerSupportsMethodSignature at runtime +// since we don't currently have infrastructure for compile time testing. When compile time testing is enabled in +// SWP-46885, we can remove FailureMode and the runtime tests and replace them with compile time tests. +TEST(MethodTraitsCheckerReturnAndInArgsRuntimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerReturnAndInArgsRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerReturnAndInArgsRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerReturnAndInArgsRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerReturnAndInArgsRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerReturnOnlyRuntimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerReturnOnlyRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerReturnOnlyRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerInArgsOnlyRuntimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerInArgsOnlyRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerInArgsOnlyRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerNoReturnAndNoInArgsRuntimeTest, CallingAssertCallableWithCallableDoesNotTerminate) +{ + // 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the call does not terminate + AssertMethodHandlerSupportsMethodSignature(); +} + +TEST(MethodTraitsCheckerNoReturnAndNoInArgsRuntimeTest, CallingAssertCallableWithCallableWithInArgTerminates) +{ + // Given a method with no return type and no InArgs. + using MethodReturnType = void; + + // and given a callable that accepts an InArg. + using Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerNoReturnAndNoInArgsRuntimeTest, 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 Callable = std::function; + + // When calling AssertMethodHandlerSupportsMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodHandlerSupportsMethodSignature())); +} + +TEST(MethodTraitsCheckerAssertMethodCallableIsNotStdBindCompileTimeTest, CallingWithNonStdBindCallableDoesNotTerminate) +{ + // When calling AssertMethodCallableIsNotStdBind with a non-std::bind type + // Then the call does not terminate + auto non_std_bind_callable = []() {}; + AssertMethodCallableIsNotStdBind(); +} + +TEST(MethodTraitsCheckerAssertMethodCallableIsNotStdBindRuntimeTimeTest, CallingWithNonStdBindCallableDoesNotTerminate) +{ + // When calling AssertMethodCallableIsNotStdBind with a non-std::bind type + // Then the call does not terminate + auto non_std_bind_callable = []() {}; + AssertMethodCallableIsNotStdBind(); +} + +TEST(MethodTraitsCheckerAssertMethodCallableIsNotStdBindRuntimeTimeTest, CallingWithStdBindTypeTerminates) +{ + // When calling AssertMethodCallableIsNotStdBind with a std::bind type + // Then the function terminates with a contract violation + auto std_bind_expression = std::bind([]() {}); + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodCallableIsNotStdBind())); +} + +TEST(AssertMethodSignatureDoesNotContainPointersOrReferencesCompileTimeTest, + CallingWithSignatureWithNoPointerOrReferenceArgsDoesNotTerminate) +{ + // Given a method with no pointer or reference arguments. + using MethodReturnType = int; + using FirstInArgType = double; + using SecondInArgType = std::string; + + // When calling AssertMethodSignatureDoesNotContainPointersOrReferences with a method signature where no argument is + // a pointer or reference + // Then the call does not terminate + AssertMethodSignatureDoesNotContainPointersOrReferences(); +} + +TEST(AssertMethodSignatureDoesNotContainPointersOrReferencesRuntimeTimeTest, + CallingWithSignatureWithNoPointerOrReferenceArgsDoesNotTerminate) +{ + // Given a method with no pointer or reference arguments. + using MethodReturnType = int; + using FirstInArgType = double; + using SecondInArgType = std::string; + + // When calling AssertMethodSignatureDoesNotContainPointersOrReferences with a method signature where no argument is + // a pointer or reference + // Then the call does not terminate + AssertMethodSignatureDoesNotContainPointersOrReferences(); +} + +TEST(AssertMethodSignatureDoesNotContainPointersOrReferencesRuntimeTimeTest, + CallingWithSignatureWithPointerArgTerminates) +{ + // Given a method with a pointer argument. + using MethodReturnType = int; + using FirstInArgType = double*; + using SecondInArgType = std::string; + + // When calling AssertMethodSignatureDoesNotContainPointersOrReferences with a method signature where one of the + // arguments is a pointer + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodSignatureDoesNotContainPointersOrReferences())); +} + +TEST(AssertMethodSignatureDoesNotContainPointersOrReferencesRuntimeTimeTest, + CallingWithSignatureWithPointerReturnTerminates) +{ + // Given a method with a pointer return type. + using MethodReturnType = double*; + using FirstInArgType = int; + using SecondInArgType = std::string; + + // When calling AssertMethodSignatureDoesNotContainPointersOrReferences with a method signature where the return + // type is a pointer + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodSignatureDoesNotContainPointersOrReferences())); +} + +TEST(AssertMethodSignatureDoesNotContainPointersOrReferencesRuntimeTimeTest, + CallingWithSignatureWithReferenceArgTerminates) +{ + // Given a method with a reference argument. + using MethodReturnType = int; + using FirstInArgType = double&; + using SecondInArgType = std::string; + + // When calling AssertMethodSignatureDoesNotContainPointersOrReferences with a method signature where one of the + // arguments is a reference + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodSignatureDoesNotContainPointersOrReferences())); +} + +TEST(AssertMethodSignatureDoesNotContainPointersOrReferencesRuntimeTimeTest, + CallingWithSignatureWithReferenceReturnTerminates) +{ + // Given a method with a reference return type. + using MethodReturnType = double&; + using FirstInArgType = int; + using SecondInArgType = std::string; + + // When calling AssertMethodSignatureDoesNotContainPointersOrReferences with a method signature where the return + // type is a reference + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertMethodSignatureDoesNotContainPointersOrReferences())); +} + +} // namespace +} // namespace score::mw::com::impl diff --git a/score/mw/com/impl/methods/skeleton_method.h b/score/mw/com/impl/methods/skeleton_method.h index d8445c933..f35a0e7de 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_traits_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 @@ -46,13 +47,6 @@ class SkeletonMethod template class SkeletonMethod final : public SkeletonMethodBase { - static constexpr bool no_in_args_are_pointers = (... && !std::is_pointer_v); - static_assert(no_in_args_are_pointers, "InArgs can not be pointers, since we can not put them in shared memory."); - - static constexpr bool return_value_is_not_a_pointer = (!std::is_pointer_v); - static_assert(return_value_is_not_a_pointer, - "Return value can not be a pointer, since we can not put them in shared memory."); - template // coverity[autosar_cpp14_a11_3_1_violation] friend class SkeletonField; @@ -89,9 +83,13 @@ class SkeletonMethod final : public SkeletonMethodBase method_type), method_type) { + AssertMethodSignatureDoesNotContainPointersOrReferences(); } - /// \brief testonly constructor, which allows for direct injection of a mock binding + /// \brief Delegated constructor which registers the method with the parent skeleton. + /// + /// This constructor is only called directly by tests, since it allows for direct injection of a mock binding. In + /// production code, the other constructors are used which create the binding internally using the factory. SkeletonMethod(SkeletonBase& skeleton_base, const std::string_view method_name, std::unique_ptr skeleton_method_binding, @@ -102,16 +100,14 @@ class SkeletonMethod final : public SkeletonMethodBase SkeletonMethod(const SkeletonMethod&) = delete; SkeletonMethod& operator=(const SkeletonMethod&) & = delete; - SkeletonMethod(SkeletonMethod&& other) noexcept; - SkeletonMethod& operator=(SkeletonMethod&& other) & noexcept; + SkeletonMethod(SkeletonMethod&& other) noexcept = default; + SkeletonMethod& operator=(SkeletonMethod&& other) & noexcept = default; /// \brief Register a handler with the binding, which will be executed by the binding when the Proxy calls this /// method. /// \return score::cpp::blank on success and ComErrc code specified by the binding on failiure template Result RegisterHandler(Callable&& callback); - - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept; }; template @@ -121,88 +117,85 @@ SkeletonMethod::SkeletonMethod(SkeletonBase& skeleton_b ::score::mw::com::impl::MethodType method_type) : SkeletonMethodBase(skeleton_base, method_name, std::move(skeleton_method_binding), method_type) { - auto skeleton_base_view = SkeletonBaseView{skeleton_base}; - skeleton_base_view.RegisterMethod(method_name_, *this); -} - -template -SkeletonMethod::SkeletonMethod(SkeletonMethod&& other) noexcept - : SkeletonMethodBase(std::move(other)) -{ - - SkeletonBaseView skeleton_base_view{skeleton_base_.get()}; - skeleton_base_view.UpdateMethod(method_name_, *this); -} - -template -SkeletonMethod& SkeletonMethod::operator=( - SkeletonMethod&& other) & noexcept -{ - if (this != &other) - { - SkeletonMethodBase::operator=(std::move(other)); - - SkeletonBaseView skeleton_base_view{skeleton_base_.get()}; - skeleton_base_view.UpdateMethod(method_name_, *this); - } - return *this; -} - -template -void SkeletonMethod::UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept -{ - skeleton_base_ = skeleton_base; + SkeletonBaseView{skeleton_base}.RegisterMethod(method_name_, reference_to_moveable_owner_.Get()); } template template -Result SkeletonMethod::RegisterHandler(Callable&& callback) +Result SkeletonMethod::RegisterHandler(Callable&& user_callback) { - 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)...); + AssertMethodCallableIsNotStdBind(); + AssertMethodHandlerSupportsMethodSignature(); + + // Since user_callback can be an lvalue reference or an rvalue reference, we ideally would store it as a universal + // reference in the type_erased_handler. However, in C++17, this is not supported. Instead, we create an callable + // here which will be called below by another lambda which explicitly stores the callback as either an lvalue + // reference or an rvalue reference depending on how it was passed in. + static auto stateless_type_erased_handler = [](Callable& actual_callback, + std::optional> type_erased_in_args, + std::optional> type_erased_return) { + using InArgPtrTuple = std::tuple; + InArgPtrTuple typed_in_arg_ptrs{}; + + constexpr bool is_in_arg_pack_empty = (sizeof...(ArgTypes) == 0); + + if constexpr (!is_in_arg_pack_empty) + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + type_erased_in_args.has_value(), + "ArgTypes is non void. Thus, type_erased_in_args needs to have a value!"); + typed_in_arg_ptrs = DeserializeArgs(type_erased_in_args.value()); + } + + constexpr bool is_return_type_not_void = !std::is_same_v; + if constexpr (is_return_type_not_void) + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( + type_erased_return.has_value(), + "ReturnType is non void. Thus, type_erased_result needs to have a value!"); + auto* const typed_return_ptr = DeserializeArg(type_erased_return.value()); + + // 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( + [&actual_callback, typed_return_ptr](ArgTypes*... typed_in_arg_ptrs) { + std::invoke(actual_callback, *typed_return_ptr, *typed_in_arg_ptrs...); + }, + typed_in_arg_ptrs); + } + else + { + // Call the callable with the typed_in_arg_ptrs which are unpacked from the tuple into individual + // arguments. + std::apply( + [&actual_callback](ArgTypes*... typed_in_arg_ptrs) { + std::invoke(actual_callback, *typed_in_arg_ptrs...); + }, + typed_in_arg_ptrs); + } }; - SkeletonMethodBinding::TypeErasedHandler type_erased_callable = - [callable_invoker = std::move(callable_invoker)]( - std::optional> type_erased_in_args, - std::optional> type_erased_return) { - using InArgPtrTuple = std::tuple; - InArgPtrTuple typed_in_arg_ptrs{}; - - constexpr bool is_in_arg_pack_empty = (sizeof...(ArgTypes) == 0); - - if constexpr (!is_in_arg_pack_empty) - { - SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE( - type_erased_in_args.has_value(), - "ArgTypes is non void. Thus, type_erased_in_args needs to have a value!"); - typed_in_arg_ptrs = DeserializeArgs(type_erased_in_args.value()); - } - - constexpr bool is_return_type_not_void = !std::is_same_v; - if constexpr (is_return_type_not_void) - { - 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); - } - else - { - std::apply(callable_invoker, std::forward(typed_in_arg_ptrs)); - } - }; - - return binding_->RegisterHandler(std::move(type_erased_callable)); + if constexpr (std::is_lvalue_reference_v) + { + SkeletonMethodBinding::TypeErasedHandler type_erased_handler = + [&user_callback](std::optional> type_erased_in_args, + std::optional> type_erased_return) mutable { + return stateless_type_erased_handler(user_callback, type_erased_in_args, type_erased_return); + }; + return binding_->RegisterHandler(std::move(type_erased_handler)); + } + else + { + SkeletonMethodBinding::TypeErasedHandler type_erased_handler = + [callback = std::move(user_callback)]( + std::optional> type_erased_in_args, + std::optional> type_erased_return) mutable { + return stateless_type_erased_handler(callback, type_erased_in_args, type_erased_return); + }; + return binding_->RegisterHandler(std::move(type_erased_handler)); + } } } // namespace score::mw::com::impl + #endif // SCORE_MW_COM_IMPL_METHODS_SKELETON_METHOD_H diff --git a/score/mw/com/impl/methods/skeleton_method_base.cpp b/score/mw/com/impl/methods/skeleton_method_base.cpp index a0ee7fdf0..e0c182f51 100644 --- a/score/mw/com/impl/methods/skeleton_method_base.cpp +++ b/score/mw/com/impl/methods/skeleton_method_base.cpp @@ -11,11 +11,3 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #include "score/mw/com/impl/methods/skeleton_method_base.h" - -namespace score::mw::com::impl -{ -void SkeletonMethodBase::UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept -{ - skeleton_base_ = skeleton_base; -} -} // namespace score::mw::com::impl diff --git a/score/mw/com/impl/methods/skeleton_method_base.h b/score/mw/com/impl/methods/skeleton_method_base.h index d72ed1272..7296a8f4d 100644 --- a/score/mw/com/impl/methods/skeleton_method_base.h +++ b/score/mw/com/impl/methods/skeleton_method_base.h @@ -15,8 +15,8 @@ #include "score/mw/com/impl/method_type.h" #include "score/mw/com/impl/methods/skeleton_method_binding.h" +#include "score/mw/com/impl/reference_to_moveable_owner.h" -#include #include namespace score::mw::com::impl @@ -36,14 +36,14 @@ class SkeletonMethodBase friend SkeletonMethodBaseView; public: - SkeletonMethodBase(SkeletonBase& skeleton_base, + SkeletonMethodBase(SkeletonBase&, const std::string_view method_name, std::unique_ptr skeleton_method_binding, MethodType method_type = MethodType::kMethod) : method_name_{method_name}, method_type_{method_type}, binding_{std::move(skeleton_method_binding)}, - skeleton_base_{skeleton_base} + reference_to_moveable_owner_{*this} { } @@ -52,16 +52,42 @@ class SkeletonMethodBase SkeletonMethodBase(const SkeletonMethodBase&) = delete; SkeletonMethodBase& operator=(const SkeletonMethodBase&) & = delete; - SkeletonMethodBase(SkeletonMethodBase&&) noexcept = default; - SkeletonMethodBase& operator=(SkeletonMethodBase&&) & noexcept = default; + SkeletonMethodBase(SkeletonMethodBase&& other) noexcept + : method_name_{other.method_name_}, + method_type_{other.method_type_}, + binding_{std::move(other.binding_)}, + reference_to_moveable_owner_{std::move(other.reference_to_moveable_owner_)} + { + reference_to_moveable_owner_.Update(*this); + } + + SkeletonMethodBase& operator=(SkeletonMethodBase&& other) & noexcept + { + if (this == &other) + { + return *this; + } - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept; + method_name_ = other.method_name_; + method_type_ = other.method_type_; + binding_ = std::move(other.binding_); + reference_to_moveable_owner_ = std::move(other.reference_to_moveable_owner_); + + reference_to_moveable_owner_.Update(*this); + return *this; + } protected: std::string_view method_name_; MethodType method_type_; std::unique_ptr binding_; - std::reference_wrapper skeleton_base_; + + /// \brief Helper class for creating reference to this SkeletonMethodBase which is provided to SkeletonBase when + /// registering this event. + /// + /// Contains a heap allocated reference to this SkeletonMethodBase which is updated in the move constructor and move + /// assignment operator so that it's always valid, even after moving the SkeletonMethodBase. + ReferenceToMoveableOwner reference_to_moveable_owner_; }; class SkeletonMethodBaseView diff --git a/score/mw/com/impl/methods/skeleton_method_base_test.cpp b/score/mw/com/impl/methods/skeleton_method_base_test.cpp index 9674d717a..8afd1852a 100644 --- a/score/mw/com/impl/methods/skeleton_method_base_test.cpp +++ b/score/mw/com/impl/methods/skeleton_method_base_test.cpp @@ -12,54 +12,13 @@ ********************************************************************************/ #include "score/mw/com/impl/methods/skeleton_method_base.h" -#include "score/mw/com/impl/bindings/mock_binding/skeleton.h" -#include "score/mw/com/impl/bindings/mock_binding/skeleton_method.h" -#include "score/mw/com/impl/instance_identifier.h" -#include "score/mw/com/impl/methods/skeleton_method.h" -#include "score/mw/com/impl/skeleton_base.h" - -#include #include -#include -#include namespace score::mw::com::impl { namespace { -using ::testing::_; -using ::testing::Invoke; -using ::testing::Return; -using ::testing::StrictMock; - -SkeletonBase MakeEmptySkeleton(std::string_view service_name) -{ - const ServiceTypeDeployment empty_type_deployment{score::cpp::blank{}}; - const ServiceIdentifierType service{make_ServiceIdentifierType(std::string{service_name})}; - const auto instance_specifier = InstanceSpecifier::Create(std::string{"/dummy_instance_specifier"}).value(); - const ServiceInstanceDeployment empty_instance_deployment{ - service, score::cpp::blank{}, QualityType::kASIL_QM, instance_specifier}; - - return SkeletonBase(std::make_unique(), - make_InstanceIdentifier(empty_instance_deployment, empty_type_deployment)); -} - -const auto kMethodName{"DummyMethod1"}; - -auto kEmptySkeleton1 = MakeEmptySkeleton("bla"); -auto kEmptySkeleton2 = MakeEmptySkeleton("blabla"); - -class MyDummyMethod final : public SkeletonMethodBase -{ - public: - MyDummyMethod() : SkeletonMethodBase{kEmptySkeleton1, kMethodName, nullptr} {} - - std::reference_wrapper GetSkeletonReference() - { - return skeleton_base_; - } -}; TEST(SkeletonMethodBaseTests, NotCopyable) { @@ -73,21 +32,6 @@ TEST(SkeletonMethodBaseTests, IsMoveable) static_assert(std::is_move_assignable::value, "Is not move assignable"); } -TEST(SkeletonMethodBase, UpdateSkeletonReferenceUpdatesTheReference) -{ - // Given a constructed SkeletonMethod with a valid reference to a Skeleton base class - auto skeleton_method = std::make_unique(); - EXPECT_EQ(std::addressof(kEmptySkeleton1), std::addressof(skeleton_method->GetSkeletonReference().get())); - - // When UpdateSkeletonReference is called with a reference to a new Skeleton base class - skeleton_method->UpdateSkeletonReference(kEmptySkeleton2); - - // Then the reference in the skeleton method is updated correctly - EXPECT_EQ(std::addressof(kEmptySkeleton2), std::addressof(skeleton_method->GetSkeletonReference().get())); - - // and that the result is blank -} - } // namespace } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/methods/skeleton_method_test.cpp b/score/mw/com/impl/methods/skeleton_method_test.cpp index 1340a8402..61c40b93a 100644 --- a/score/mw/com/impl/methods/skeleton_method_test.cpp +++ b/score/mw/com/impl/methods/skeleton_method_test.cpp @@ -12,11 +12,13 @@ ********************************************************************************/ #include "score/mw/com/impl/methods/skeleton_method.h" +#include "gmock/gmock.h" #include "score/mw/com/impl/bindings/mock_binding/skeleton.h" #include "score/mw/com/impl/bindings/mock_binding/skeleton_method.h" #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/methods/test/callable_traits_resources.h" #include "score/mw/com/impl/skeleton_base.h" #include "score/mw/com/impl/test/binding_factory_resources.h" #include "score/result/result.h" @@ -24,6 +26,7 @@ #include #include #include +#include #include #include @@ -54,7 +57,8 @@ class EmptySkeleton final : public SkeletonBase using SkeletonBase::SkeletonBase; }; -using TestMethodType = bool(int, bool); +using TestMethodType = bool(const int, const bool); +using TestMethodHandlerType = void(bool&, const int&, const bool&); TEST(SkeletonMethodTests, NotCopyable) { @@ -82,18 +86,24 @@ TEST(SkeletonMethodTest, ClassTypeDependsOnMethodType) "Class type does not depend on method signature."); } +struct MyDataStruct +{ + bool b; + int i; + double d; + float f[4]; +}; + template -class SkeletonMethodTypedTest : public ::testing::Test +class SkeletonMethodFixture : public ::testing::Test { public: - using Type = MethodType; - void SetUp() override { ON_CALL(mock_method_binding_, RegisterHandler(_)).WillByDefault(Return(Result{})); } - SkeletonMethodTypedTest& GivenASkeletonMethod() + SkeletonMethodFixture& GivenASkeletonMethod() { auto mock_method_binding_ptr = std::make_unique(mock_method_binding_); @@ -102,7 +112,7 @@ class SkeletonMethodTypedTest : public ::testing::Test return *this; } - SkeletonMethodTypedTest& WithAMockedMethodBindingfactory() + SkeletonMethodFixture& WithAMockedMethodBindingfactory() { skeleton_method_binding_factory_mock_guard_ = std::make_unique(); return *this; @@ -116,25 +126,84 @@ class SkeletonMethodTypedTest : public ::testing::Test std::unique_ptr skeleton_method_binding_factory_mock_guard_{nullptr}; }; -struct MyDataStruct +template +struct FactoryAndMethodType { - bool b; - int i; - double d; - float f[4]; + using MethodHandlerFactory = MethodHandlerFactoryIn; + using MethodType = MethodTypeIn; +}; + +template +class SkeletonMethodTypedFixture : public SkeletonMethodFixture +{ + public: + using MethodHandlerFactory = typename TestTypes::MethodHandlerFactory; + using MethodType = typename TestTypes::MethodType; + + auto CreateHandler() + { + return MethodHandlerFactory::Create(); + } }; -using RegisteredFunctionTypes = ::testing::Types; -TYPED_TEST_SUITE(SkeletonMethodTypedTest, RegisteredFunctionTypes, ); - -using SkeletonMethodTestFixture = SkeletonMethodTypedTest; - -TYPED_TEST(SkeletonMethodTypedTest, AnyCombinationOfReturnAndInputArgTypesCanBeRegistered) + +// clang-format off +using RegisteredFunctionTypes = ::testing::Types< + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()>, + + FactoryAndMethodType, int()>, + FactoryAndMethodType, float(double, bool)>, + FactoryAndMethodType, void(MyDataStruct, bool)>, + FactoryAndMethodType, void()> + +>; +// clang-format on + +TYPED_TEST_SUITE(SkeletonMethodTypedFixture, RegisteredFunctionTypes, ); + +TYPED_TEST(SkeletonMethodTypedFixture, AnyCombinationOfReturnAndInputArgTypesCanBeRegistered) { // Given A skeleton Method with a mock method binding this->GivenASkeletonMethod(); @@ -143,12 +212,11 @@ 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{}; + auto test_callback = this->CreateHandler(); std::ignore = this->method_->RegisterHandler(std::move(test_callback)); } -TYPED_TEST(SkeletonMethodTypedTest, TwoParameterConstructorCorrectlyCallsBindingFactoryAndSkeletonMethodIsCreated) +TYPED_TEST(SkeletonMethodTypedFixture, TwoParameterConstructorCorrectlyCallsBindingFactoryAndSkeletonMethodIsCreated) { this->WithAMockedMethodBindingfactory(); @@ -159,15 +227,15 @@ 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{}; + auto test_callback = this->CreateHandler(); EXPECT_TRUE(method.RegisterHandler(std::move(test_callback))); } -TYPED_TEST(SkeletonMethodTypedTest, TwoParameterConstructorMarksBindingsAsInvalidWhenFactoryReturnsNullptr) +TYPED_TEST(SkeletonMethodTypedFixture, TwoParameterConstructorMarksBindingsAsInvalidWhenFactoryReturnsNullptr) { this->WithAMockedMethodBindingfactory(); @@ -177,23 +245,22 @@ TYPED_TEST(SkeletonMethodTypedTest, TwoParameterConstructorMarksBindingsAsInvali .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 EXPECT_FALSE(SkeletonBaseView{this->empty_skeleton_}.AreBindingsValid()); } -TEST_F(SkeletonMethodTestFixture, ACallbackWithAPointerAsStateCanBeRegistered) +using SkeletonMethodStatefulCallbackFixture = SkeletonMethodFixture; +TEST_F(SkeletonMethodStatefulCallbackFixture, ACallbackWithAPointerAsStateCanBeRegistered) { + // Given A skeleton Method with a mock method binding GivenASkeletonMethod(); // 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)]() noexcept {}; // Expecting that the register call is dispatched to the binding without an error EXPECT_CALL(mock_method_binding_, RegisterHandler(_)); @@ -202,37 +269,87 @@ TEST_F(SkeletonMethodTestFixture, ACallbackWithAPointerAsStateCanBeRegistered) std::ignore = method_->RegisterHandler(std::move(test_callback_with_state)); } +TEST_F(SkeletonMethodStatefulCallbackFixture, PassingReferenceToHandlerUpdatesStateInPlace) +{ + static constexpr int kInitialValue = 42; + static constexpr int kModifiedValue = 43; + + // Given A skeleton Method with a mock method binding + GivenASkeletonMethod(); + + // And a functor which modifies its internal state when called + class DummyMethodFunctor + { + public: + void operator()() + { + i_ = kModifiedValue; + } + + int i_{kInitialValue}; + }; + DummyMethodFunctor test_functor{}; + + // Expecting that the register call is dispatched to the binding which captures the handler + std::optional captured_set_handler{}; + EXPECT_CALL(mock_method_binding_, RegisterHandler(_)).WillOnce(Invoke([&captured_set_handler](auto handler) { + captured_set_handler = std::move(handler); + return Result{}; + })); + + // and given a Register call is issued at the binding independent level with an lvalue reference to the functor + std::ignore = method_->RegisterHandler(test_functor); + + // When the type erased call is executed by the binding + captured_set_handler.value()({}, {}); + + // Then the state of the functor is updated in place when the handler is called by the binding + EXPECT_EQ(test_functor.i_, kModifiedValue); +} + +template +struct MethodTypeAndHandlerType +{ + using HandlerType = HandlerTypeIn; + using MethodType = MethodTypeIn; +}; + 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: + static constexpr std::size_t in_args_buffer_size = sizeof(InType1) + sizeof(InType2) + sizeof(InType2); + 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() { @@ -241,8 +358,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_{}; }; @@ -263,11 +380,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 will 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_); @@ -293,7 +414,9 @@ 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)); + 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())); @@ -320,11 +443,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()); + 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 @@ -345,7 +469,7 @@ TEST_F(SkeletonMethodVoidVoidFixture, DataTransferBetweenTypedAndTypeErasedCallb })); // Expecting that a typed callable will be called without inargs and will not return a value - EXPECT_CALL(typed_callback_mock_, Call()).WillOnce(Return()); + EXPECT_CALL(typed_callback_mock_, Call()); EXPECT_TRUE(method_->RegisterHandler(typed_callback_mock_.AsStdFunction())); // When the type erased call is executed by the binding @@ -353,5 +477,4 @@ TEST_F(SkeletonMethodVoidVoidFixture, DataTransferBetweenTypedAndTypeErasedCallb } } // namespace - } // namespace score::mw::com::impl diff --git a/score/mw/com/impl/methods/test/BUILD b/score/mw/com/impl/methods/test/BUILD new file mode 100644 index 000000000..770ae9ead --- /dev/null +++ b/score/mw/com/impl/methods/test/BUILD @@ -0,0 +1,24 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_library") +load("//score/mw:common_features.bzl", "COMPILER_WARNING_FEATURES") + +cc_library( + name = "callable_traits_resources", + hdrs = ["callable_traits_resources.h"], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/mw/com/impl/methods:__pkg__", + ], +) diff --git a/score/mw/com/impl/methods/test/callable_traits_resources.cpp b/score/mw/com/impl/methods/test/callable_traits_resources.cpp new file mode 100644 index 000000000..00659a00d --- /dev/null +++ b/score/mw/com/impl/methods/test/callable_traits_resources.cpp @@ -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/methods/test/callable_traits_resources.h" diff --git a/score/mw/com/impl/methods/test/callable_traits_resources.h b/score/mw/com/impl/methods/test/callable_traits_resources.h new file mode 100644 index 000000000..4e8a1c87e --- /dev/null +++ b/score/mw/com/impl/methods/test/callable_traits_resources.h @@ -0,0 +1,214 @@ +/******************************************************************************** + * 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_TEST_CALLABLE_TRAITS_RESOURCES_H +#define SCORE_MW_COM_IMPL_METHODS_TEST_CALLABLE_TRAITS_RESOURCES_H + +namespace score::mw::com::impl +{ + +/// \brief Factory that default-constructs a CallableWrapper via a static Create(). +/// +/// \tparam CallableWrapper A template-template parameter such as score::cpp::callback or std::function. +/// \tparam HandlerSignature The concrete function signature passed to CallableWrapper, e.g. void(int&). +template