From 0a8e7bcd9c1ed38d1d79ae4dd6cc244a545e7f1d Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Thu, 18 Jun 2026 12:15:05 +0200 Subject: [PATCH 1/7] mw/com: Add integration tests for moving SkeletonEvent In the near future, we should implement similar tests for SkeletonFields and SkeletonMethods. We should also implement similar tests on proxy side. --- .../test/methods/methods_test_resources/BUILD | 6 +- .../process_synchronizer.cpp | 5 + .../process_synchronizer.h | 2 + .../skeleton_container.h | 7 + .../mw/com/test/skeleton_move_semantics/BUILD | 166 ++++++++ .../config/logging.json | 7 + .../config/mw_com_config.json | 105 +++++ .../test/skeleton_move_semantics/consumer.cpp | 212 ++++++++++ .../test/skeleton_move_semantics/consumer.h | 31 ++ .../integration_test/BUILD | 95 +++++ ...offered_skeleton_different_process_test.py | 22 ++ ..._not_offered_skeleton_same_process_test.py | 21 + ...offered_skeleton_different_process_test.py | 22 ++ ...sign_offered_skeleton_same_process_test.py | 21 + ...offered_skeleton_different_process_test.py | 22 ++ ..._not_offered_skeleton_same_process_test.py | 21 + ...offered_skeleton_different_process_test.py | 22 ++ ...ruct_offered_skeleton_same_process_test.py | 21 + .../integration_test/test_fixture.py | 40 ++ .../skeleton_move_semantics/main_consumer.cpp | 138 +++++++ .../main_consumer_and_provider.cpp | 146 +++++++ .../skeleton_move_semantics/main_provider.cpp | 123 ++++++ .../test/skeleton_move_semantics/provider.cpp | 361 ++++++++++++++++++ .../test/skeleton_move_semantics/provider.h | 29 ++ .../test_event_datatype.cpp | 13 + .../test_event_datatype.h | 37 ++ .../test_parameters.cpp | 50 +++ .../skeleton_move_semantics/test_parameters.h | 46 +++ 28 files changed, 1788 insertions(+), 3 deletions(-) create mode 100644 score/mw/com/test/skeleton_move_semantics/BUILD create mode 100644 score/mw/com/test/skeleton_move_semantics/config/logging.json create mode 100644 score/mw/com/test/skeleton_move_semantics/config/mw_com_config.json create mode 100644 score/mw/com/test/skeleton_move_semantics/consumer.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/consumer.h create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/BUILD create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_different_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_same_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_different_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_same_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_different_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_same_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_different_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_same_process_test.py create mode 100644 score/mw/com/test/skeleton_move_semantics/integration_test/test_fixture.py create mode 100644 score/mw/com/test/skeleton_move_semantics/main_consumer.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/main_consumer_and_provider.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/main_provider.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/provider.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/provider.h create mode 100644 score/mw/com/test/skeleton_move_semantics/test_event_datatype.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/test_event_datatype.h create mode 100644 score/mw/com/test/skeleton_move_semantics/test_parameters.cpp create mode 100644 score/mw/com/test/skeleton_move_semantics/test_parameters.h diff --git a/score/mw/com/test/methods/methods_test_resources/BUILD b/score/mw/com/test/methods/methods_test_resources/BUILD index aa1d2a7f5..28d6f1748 100644 --- a/score/mw/com/test/methods/methods_test_resources/BUILD +++ b/score/mw/com/test/methods/methods_test_resources/BUILD @@ -21,7 +21,7 @@ cc_library( hdrs = ["proxy_container.h"], features = COMPILER_WARNING_FEATURES, visibility = [ - "//score/mw/com/test/methods:__subpackages__", + "//score/mw/com/test:__subpackages__", ], deps = [ "//score/mw/com", @@ -35,7 +35,7 @@ cc_library( hdrs = ["skeleton_container.h"], features = COMPILER_WARNING_FEATURES, visibility = [ - "//score/mw/com/test/methods:__subpackages__", + "//score/mw/com/test:__subpackages__", ], deps = [ "//score/mw/com", @@ -84,7 +84,7 @@ cc_library( hdrs = ["process_synchronizer.h"], features = COMPILER_WARNING_FEATURES, visibility = [ - "//score/mw/com/test/methods:__subpackages__", + "//score/mw/com/test:__subpackages__", ], deps = [ "//score/mw/com/test/common_test_resources:fail_test", diff --git a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp index d8b82aece..0581367e0 100644 --- a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp +++ b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp @@ -70,4 +70,9 @@ bool ProcessSynchronizer::WaitWithAbort(score::cpp::stop_token stop_token) return interprocess_notifier_.waitWithAbort(stop_token); } +void ProcessSynchronizer::Reset() +{ + interprocess_notifier_.reset(); +} + } // namespace score::mw::com::test diff --git a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h index 9430db8cf..b54e899d6 100644 --- a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h +++ b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h @@ -38,6 +38,8 @@ class ProcessSynchronizer bool WaitWithAbort(score::cpp::stop_token stop_token); + void Reset(); + private: SharedMemoryObjectCreator interprocess_notifier_creator_; SharedMemoryObjectGuard interprocess_notifier_guard_; diff --git a/score/mw/com/test/methods/methods_test_resources/skeleton_container.h b/score/mw/com/test/methods/methods_test_resources/skeleton_container.h index dbef02d3b..7e32e59db 100644 --- a/score/mw/com/test/methods/methods_test_resources/skeleton_container.h +++ b/score/mw/com/test/methods/methods_test_resources/skeleton_container.h @@ -37,6 +37,13 @@ class SkeletonContainer return *skeleton_; } + Skeleton&& Extract() + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(skeleton_ != nullptr, + "Skeleton was not successfully created! Cannot extract it!"); + return std::move(*skeleton_); + } + private: std::unique_ptr skeleton_; }; diff --git a/score/mw/com/test/skeleton_move_semantics/BUILD b/score/mw/com/test/skeleton_move_semantics/BUILD new file mode 100644 index 000000000..c998893a8 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/BUILD @@ -0,0 +1,166 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") +load("//score/mw/com/test:pkg_application.bzl", "pkg_application") + +validate_json_schema_test( + name = "validate_config_schema", + json = "config/mw_com_config.json", + schema = "//score/mw/com:config_schema", + tags = ["lint"], +) + +cc_library( + name = "test_event_datatype", + srcs = ["test_event_datatype.cpp"], + hdrs = ["test_event_datatype.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + ], +) + +cc_library( + name = "test_parameters", + srcs = ["test_parameters.cpp"], + hdrs = ["test_parameters.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com/test/common_test_resources:command_line_parser", + "//score/mw/com/test/common_test_resources:fail_test", + ], +) + +cc_library( + name = "provider", + srcs = ["provider.cpp"], + hdrs = ["provider.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":test_event_datatype", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/methods/methods_test_resources:process_synchronizer", + "//score/mw/com/test/methods/methods_test_resources:skeleton_container", + ], +) + +cc_library( + name = "consumer", + srcs = ["consumer.cpp"], + hdrs = ["consumer.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":test_event_datatype", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/methods/methods_test_resources:process_synchronizer", + "//score/mw/com/test/methods/methods_test_resources:proxy_container", + ], +) + +cc_binary( + name = "main_provider", + srcs = ["main_provider.cpp"], + data = ["config/mw_com_config.json"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":provider", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:assert_handler", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:stop_token_sig_term_handler", + ], +) + +cc_binary( + name = "main_consumer", + srcs = ["main_consumer.cpp"], + data = ["config/mw_com_config.json"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":consumer", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:assert_handler", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:stop_token_sig_term_handler", + ], +) + +cc_binary( + name = "main_consumer_and_provider", + srcs = ["main_consumer_and_provider.cpp"], + data = ["config/mw_com_config.json"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":consumer", + ":provider", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:assert_handler", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:stop_token_sig_term_handler", + ], +) + +pkg_application( + name = "main_provider-pkg", + app_name = "MainProviderApp", + bin = [":main_provider"], + etc = [ + "config/mw_com_config.json", + "config/logging.json", + ], + visibility = [ + "//score/mw/com/test/skeleton_move_semantics:__subpackages__", + ], +) + +pkg_application( + name = "main_consumer-pkg", + app_name = "MainConsumerApp", + bin = [":main_consumer"], + etc = [ + "config/mw_com_config.json", + "config/logging.json", + ], + visibility = [ + "//score/mw/com/test/skeleton_move_semantics:__subpackages__", + ], +) + +pkg_application( + name = "main_consumer_and_provider-pkg", + app_name = "MainConsumerAndProviderApp", + bin = [":main_consumer_and_provider"], + etc = [ + "config/mw_com_config.json", + "config/logging.json", + ], + visibility = [ + "//score/mw/com/test/skeleton_move_semantics:__subpackages__", + ], +) diff --git a/score/mw/com/test/skeleton_move_semantics/config/logging.json b/score/mw/com/test/skeleton_move_semantics/config/logging.json new file mode 100644 index 000000000..8e9a79208 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/config/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "SMSM", + "appDesc": "skeleton_move_semantics", + "logLevel": "kDebug", + "logLevelThresholdConsole": "kDebug", + "logMode": "kRemote|kConsole" +} diff --git a/score/mw/com/test/skeleton_move_semantics/config/mw_com_config.json b/score/mw/com/test/skeleton_move_semantics/config/mw_com_config.json new file mode 100644 index 000000000..d6198b6ca --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/config/mw_com_config.json @@ -0,0 +1,105 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/skeleton_move_semantics/MoveEventInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 2100, + "events": [ + { + "eventName": "moved_event", + "eventId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "test/skeleton_move_semantics/MoveEventInterfaceMovedTo", + "serviceTypeName": "/test/skeleton_move_semantics/MoveEventInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "moved_event", + "numberOfSampleSlots": 15, + "maxSubscribers": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + }, + { + "instanceSpecifier": "test/skeleton_move_semantics/MoveEventInterfaceMovedFrom", + "serviceTypeName": "/test/skeleton_move_semantics/MoveEventInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 2, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "moved_event", + "numberOfSampleSlots": 15, + "maxSubscribers": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM", + "applicationID": 4001 + } +} diff --git a/score/mw/com/test/skeleton_move_semantics/consumer.cpp b/score/mw/com/test/skeleton_move_semantics/consumer.cpp new file mode 100644 index 000000000..eface641d --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/consumer.cpp @@ -0,0 +1,212 @@ +/******************************************************************************** + * 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/test/skeleton_move_semantics/consumer.h" + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/methods/methods_test_resources/process_synchronizer.h" +#include "score/mw/com/test/methods/methods_test_resources/proxy_container.h" +#include "score/mw/com/test/skeleton_move_semantics/test_event_datatype.h" +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::string kInterprocessNotificationShmPath{"/skeleton_move_semantics_interprocess_notification"}; + +template +class ProxyStateChangeNotifier +{ + public: + explicit ProxyStateChangeNotifier(ProxyEventType& proxy_event) : proxy_event_{proxy_event} + { + auto state_change_handler = [this](const SubscriptionState new_state) -> bool { + std::cout << "Service state changed, new state: " << static_cast(new_state) << std::endl; + std::lock_guard lock(mutex_); + last_seen_state_ = new_state; + condition_variable_.notify_all(); + return true; + }; + proxy_event_.SetSubscriptionStateChangeHandler(state_change_handler); + } + + ~ProxyStateChangeNotifier() + { + proxy_event_.UnsetSubscriptionStateChangeHandler(); + } + + ProxyStateChangeNotifier(const ProxyStateChangeNotifier&) = delete; + ProxyStateChangeNotifier& operator=(const ProxyStateChangeNotifier&) = delete; + ProxyStateChangeNotifier(const ProxyStateChangeNotifier&&) = delete; + ProxyStateChangeNotifier& operator=(const ProxyStateChangeNotifier&&) = delete; + + bool WaitForStateChange(const score::cpp::stop_token& stop_token, SubscriptionState desired_state) + { + std::unique_lock lock(mutex_); + return condition_variable_.wait(lock, stop_token, [this, desired_state]() { + return last_seen_state_.has_value() && last_seen_state_.value() == desired_state; + }); + } + + private: + std::mutex mutex_{}; + concurrency::InterruptibleConditionalVariable condition_variable_{}; + std::optional last_seen_state_{}; + ProxyEventType& proxy_event_; +}; + +template +class ProxyEventReceiver +{ + public: + explicit ProxyEventReceiver(ProxyEventType& proxy_event) : proxy_event_{proxy_event} + { + auto receive_handler = [&received_sample_notification = received_sample_notification_]() { + std::cout << "Received event notification" << std::endl; + received_sample_notification.notify(); + }; + proxy_event_.SetReceiveHandler(receive_handler); + } + + ~ProxyEventReceiver() + { + proxy_event_.UnsetReceiveHandler(); + } + + ProxyEventReceiver(const ProxyEventReceiver&) = delete; + ProxyEventReceiver& operator=(const ProxyEventReceiver&) = delete; + ProxyEventReceiver(const ProxyEventReceiver&&) = delete; + ProxyEventReceiver& operator=(const ProxyEventReceiver&&) = delete; + + void WaitForSamples(const score::cpp::stop_token& stop_token, const std::size_t num_samples_to_receive) + { + std::size_t received_count{0U}; + while (!stop_token.stop_requested()) + { + auto get_samples_result = proxy_event_.GetNewSamples( + [this](SamplePtr sample) { + GetNewSamplesCallback(std::move(sample)); + }, + num_samples_to_receive); + if (!get_samples_result.has_value()) + { + FailTest("skeleton_move_semantics consumer failed: GetNewSamples failed: ", get_samples_result.error()); + } + + received_count += get_samples_result.value(); + std::cout << "Received " << get_samples_result.value() << " samples. " << received_count + << " samples so far" << std::endl; + + if (received_count == num_samples_to_receive) + { + break; + } + + const auto notification_received = received_sample_notification_.waitWithAbort(stop_token); + if (!notification_received) + { + // spurious wake-up or stop requested, either way we should check the stop token and exit if stop was + // requested + continue; + } + + received_sample_notification_.reset(); + } + std::cout << "\nConsumer: Done receiving samples, received " << received_count << " samples in total\n"; + } + + private: + void GetNewSamplesCallback(SamplePtr sample) + { + if (sample == nullptr) + { + FailTest("skeleton_move_semantics consumer failed: received null sample"); + } + const std::uint32_t expected_value = latest_value_.has_value() ? latest_value_.value() + 1U : 1U; + if (*sample != expected_value) + { + FailTest("skeleton_move_semantics consumer failed: received value ", + *sample, + " does not match expected value ", + expected_value); + } + latest_value_ = *sample; + } + + score::concurrency::Notification received_sample_notification_{}; + std::mutex mutex_{}; + concurrency::InterruptibleConditionalVariable condition_variable_{}; + std::optional latest_value_{}; + ProxyEventType& proxy_event_; +}; + +} // namespace + +void RunConsumer(const InstanceSpecifier& instance_specifier, + const std::size_t num_samples_to_receive, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token) +{ + const auto name = filesystem::Path{instance_specifier.ToString()}.Filename().Native(); + auto process_synchronizer_result = + ProcessSynchronizer::Create(kInterprocessNotificationShmPath + std::string{name}); + if (!process_synchronizer_result.has_value()) + { + FailTest("skeleton_move_semantics consumer failed: could not create ready synchronizer"); + } + + ExitFunctionGuard done_guard{[&process_synchronizer_result]() { + process_synchronizer_result->Notify(); + }}; + + ProxyContainer proxy_container{}; + + // Step 1. Find service and create proxy + std::cout << "\nConsumer: Step 1 - Find service and create proxy" << std::endl; + proxy_container.CreateProxy(instance_specifier, "skeleton_move_semantics"); + auto& proxy = proxy_container.GetProxy(); + + // Step 2. Register receive handler + std::cout << "\nConsumer: Step 2 - Register receive handler" << std::endl; + ProxyEventReceiver proxy_event_receiver{proxy.moved_event_}; + + // Step 3. Register state change handler + std::cout << "\nConsumer: Step 3 - Register state change handler" << std::endl; + ProxyStateChangeNotifier proxy_state_change_notifier{proxy.moved_event_}; + + // Step 4. Subscribe + std::cout << "\nConsumer: Step 4 - Subscribe" << std::endl; + auto subscribe_result = proxy.moved_event_.Subscribe(num_samples_to_receive); + if (!subscribe_result.has_value()) + { + FailTest("skeleton_move_semantics consumer failed: Subscribe failed: ", subscribe_result.error()); + } + + // Step 5. Wait for provider to send values and notify + std::cout << "\nConsumer: Step 5 - Wait for provider to send values and notify" << std::endl; + for (std::size_t iteration = 0U; iteration < num_send_iterations; ++iteration) + { + std::cout << "\nConsumer: Iteration " << (iteration + 1) << " of " << num_send_iterations << std::endl; + proxy_state_change_notifier.WaitForStateChange(stop_token, SubscriptionState::kSubscribed); + + proxy_event_receiver.WaitForSamples(stop_token, num_samples_to_receive); + std::cout << "\nConsumer: Done receiving samples, received " << num_samples_to_receive << " samples in total\n"; + process_synchronizer_result->Notify(); + } + std::cout << "Consumer: Done with all iterations, exiting" << std::endl; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/skeleton_move_semantics/consumer.h b/score/mw/com/test/skeleton_move_semantics/consumer.h new file mode 100644 index 000000000..9f0f8af7f --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/consumer.h @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_CONSUMER_H +#define SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_CONSUMER_H + +#include "score/mw/com/test/skeleton_move_semantics/test_parameters.h" +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +void RunConsumer(const InstanceSpecifier& instance_specifier, + const std::size_t num_samples_to_receive, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_CONSUMER_H diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/BUILD b/score/mw/com/test/skeleton_move_semantics/integration_test/BUILD new file mode 100644 index 000000000..01217023c --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/BUILD @@ -0,0 +1,95 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +integration_test( + name = "move_construct_not_offered_skeleton_same_process_test", + srcs = [ + "move_construct_not_offered_skeleton_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/skeleton_move_semantics:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_construct_offered_skeleton_same_process_test", + srcs = [ + "move_construct_offered_skeleton_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/skeleton_move_semantics:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_assign_not_offered_skeleton_same_process_test", + srcs = [ + "move_assign_not_offered_skeleton_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/skeleton_move_semantics:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_assign_offered_skeleton_same_process_test", + srcs = [ + "move_assign_offered_skeleton_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/skeleton_move_semantics:main_consumer_and_provider-pkg", +) + +pkg_filegroup( + name = "different_processes_filesystem", + srcs = [ + "//score/mw/com/test/skeleton_move_semantics:main_consumer-pkg", + "//score/mw/com/test/skeleton_move_semantics:main_provider-pkg", + ], +) + +integration_test( + name = "move_construct_not_offered_skeleton_different_process_test", + srcs = [ + "move_construct_not_offered_skeleton_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_construct_offered_skeleton_different_process_test", + srcs = [ + "move_construct_offered_skeleton_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_assign_not_offered_skeleton_different_process_test", + srcs = [ + "move_assign_not_offered_skeleton_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_assign_offered_skeleton_different_process_test", + srcs = [ + "move_assign_offered_skeleton_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_different_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_different_process_test.py new file mode 100644 index 000000000..8c753bc14 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_different_process_test.py @@ -0,0 +1,22 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer, provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_assign_not_offered_same_process(target): + with consumer(target, SkeletonMoveScenario.MOVE_ASSIGN_NOT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + with provider(target, SkeletonMoveScenario.MOVE_ASSIGN_NOT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_same_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_same_process_test.py new file mode 100644 index 000000000..7db5c3cb4 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_not_offered_skeleton_same_process_test.py @@ -0,0 +1,21 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer_and_provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_assign_not_offered_same_process(target): + with consumer_and_provider(target, SkeletonMoveScenario.MOVE_ASSIGN_NOT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_different_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_different_process_test.py new file mode 100644 index 000000000..730142b0e --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_different_process_test.py @@ -0,0 +1,22 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer, provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_assign_offered_same_process(target): + with consumer(target, SkeletonMoveScenario.MOVE_ASSIGN_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + with provider(target, SkeletonMoveScenario.MOVE_ASSIGN_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_same_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_same_process_test.py new file mode 100644 index 000000000..77c9b7c54 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_assign_offered_skeleton_same_process_test.py @@ -0,0 +1,21 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer_and_provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_assign_offered_same_process(target): + with consumer_and_provider(target, SkeletonMoveScenario.MOVE_ASSIGN_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_different_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_different_process_test.py new file mode 100644 index 000000000..4e89bd1e1 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_different_process_test.py @@ -0,0 +1,22 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer, provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_construct_not_offered_same_process(target): + with consumer(target, SkeletonMoveScenario.MOVE_CONSTRUCT_NOT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + with provider(target, SkeletonMoveScenario.MOVE_CONSTRUCT_NOT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_same_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_same_process_test.py new file mode 100644 index 000000000..086eb6a65 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_not_offered_skeleton_same_process_test.py @@ -0,0 +1,21 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer_and_provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_construct_not_offered_same_process(target): + with consumer_and_provider(target, SkeletonMoveScenario.MOVE_CONSTRUCT_NOT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_different_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_different_process_test.py new file mode 100644 index 000000000..ff997e8ab --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_different_process_test.py @@ -0,0 +1,22 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer, provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_construct_offered_same_process(target): + with consumer(target, SkeletonMoveScenario.MOVE_CONSTRUCT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + with provider(target, SkeletonMoveScenario.MOVE_CONSTRUCT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_same_process_test.py b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_same_process_test.py new file mode 100644 index 000000000..0fcdaf264 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/move_construct_offered_skeleton_same_process_test.py @@ -0,0 +1,21 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from test_fixture import consumer_and_provider, SkeletonMoveScenario + +NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER = 10 + + +def test_move_construct_offered_same_process(target): + with consumer_and_provider(target, SkeletonMoveScenario.MOVE_CONSTRUCT_OFFERED, NUMBER_OF_SAMPLES_TO_SEND_PER_OFFER): + pass + diff --git a/score/mw/com/test/skeleton_move_semantics/integration_test/test_fixture.py b/score/mw/com/test/skeleton_move_semantics/integration_test/test_fixture.py new file mode 100644 index 000000000..1db9c31d4 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/integration_test/test_fixture.py @@ -0,0 +1,40 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +from enum import IntEnum + + +class SkeletonMoveScenario(IntEnum): + MOVE_CONSTRUCT_NOT_OFFERED = 0 + MOVE_CONSTRUCT_OFFERED = 1 + MOVE_ASSIGN_NOT_OFFERED = 2 + MOVE_ASSIGN_OFFERED = 3 + + +def consumer_and_provider(target, scenario, number_of_samples_to_send_per_offer, **kwargs): + args = ["--scenario", str(int(scenario)), "--number-of-samples-to-send", str(number_of_samples_to_send_per_offer), "--service-instance-manifest", f"./etc/mw_com_config.json"] + return target.wrap_exec( + "bin/main_consumer_and_provider", args, cwd="/opt/MainConsumerAndProviderApp", wait_on_exit=True, **kwargs + ) + +def consumer(target, scenario, number_of_samples_to_send_per_offer, **kwargs): + args = ["--scenario", str(int(scenario)), "--number-of-samples-to-send", str(number_of_samples_to_send_per_offer), "--service-instance-manifest", f"./etc/mw_com_config.json"] + return target.wrap_exec( + "bin/main_consumer", args, cwd="/opt/MainConsumerApp", wait_on_exit=True, **kwargs + ) + +def provider(target, scenario, number_of_samples_to_send_per_offer, **kwargs): + args = ["--scenario", str(int(scenario)), "--number-of-samples-to-send", str(number_of_samples_to_send_per_offer), "--service-instance-manifest", f"./etc/mw_com_config.json"] + return target.wrap_exec( + "bin/main_provider", args, cwd="/opt/MainProviderApp", wait_on_exit=True, **kwargs + ) + diff --git a/score/mw/com/test/skeleton_move_semantics/main_consumer.cpp b/score/mw/com/test/skeleton_move_semantics/main_consumer.cpp new file mode 100644 index 000000000..d56185062 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/main_consumer.cpp @@ -0,0 +1,138 @@ +/******************************************************************************** + * 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/runtime.h" +#include "score/mw/com/types.h" + +#include "score/mw/com/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" +#include "score/mw/com/test/skeleton_move_semantics/consumer.h" +#include "score/mw/com/test/skeleton_move_semantics/test_parameters.h" + +#include +#include +#include +#include +#include + +namespace +{ + +const score::mw::com::InstanceSpecifier kInstanceSpecifierMovedTo = + score::mw::com::InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedTo"}) + .value(); +const score::mw::com::InstanceSpecifier kInstanceSpecifierMovedFrom = + score::mw::com::InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedFrom"}) + .value(); + +} // namespace + +int main(int argc, const char** argv) +{ + auto test_configuration{score::mw::com::test::ReadCommandLineArguments(argc, argv)}; + + score::mw::com::test::SetupAssertHandler(); + score::mw::com::runtime::InitializeRuntime(argc, argv); + + score::cpp::stop_source stop_source{}; + const bool sig_term_handler_setup_success = score::mw::com::SetupStopTokenSigTermHandler(stop_source); + if (!sig_term_handler_setup_success) + { + std::cerr << "Unable to set signal handler for SIGINT and/or SIGTERM, cautiously continuing" << std::endl; + } + + const auto num_send_iterations = [&test_configuration]() { + if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveConstructNotOffered) + { + // In this scenario, the provider will: + // - Create a skeleton + // - move construct the skeleton + // - offer the service + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects two send iterations. + return 2U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveConstructOffered) + { + // In this scenario, the provider will: + // - Create a skeleton + // - offer the service + // - send samples + // - move construct the skeleton while the service is offered + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects three send iterations. + return 3U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignNotOffered) + { + // In this scenario, the provider will: + // - Create 2 skeletons + // - move assign the skeleton + // - offer the moved-to service + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects two send iterations. + return 2U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignOffered) + { + // In this scenario, the provider will: + // - Create 2 skeletons + // - offer both services + // - send samples in both services + // - move assign the skeleton + // - send samples from moved-to service + // - stop offering the moved-to service + // - offer the moved-to service again + // - send samples again + // The receiver connected to the moved-from service expects one send iteration. The receiver connected to + // the moved-to service expects three send iterations. + return 3U; + } + + score::mw::com::test::FailTest("Unknown scenario, cannot determine number of send iterations"); + return 0U; + }(); + + std::cout << "Starting provider and consumer with scenario " + << static_cast(test_configuration.scenario) << ", number of samples to send per offer " + << test_configuration.number_of_samples_to_send_per_offer << " and number of send iterations" + << num_send_iterations << std::endl; + + auto consumer_future = std::async(score::mw::com::test::RunConsumer, + kInstanceSpecifierMovedTo, + test_configuration.number_of_samples_to_send_per_offer, + num_send_iterations, + stop_source.get_token()); + + if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignOffered) + { + const std::size_t num_moved_to_send_iterations{1U}; + score::mw::com::test::RunConsumer(kInstanceSpecifierMovedFrom, + test_configuration.number_of_samples_to_send_per_offer, + num_moved_to_send_iterations, + stop_source.get_token()); + } + + consumer_future.get(); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/skeleton_move_semantics/main_consumer_and_provider.cpp b/score/mw/com/test/skeleton_move_semantics/main_consumer_and_provider.cpp new file mode 100644 index 000000000..47f4cc1ba --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/main_consumer_and_provider.cpp @@ -0,0 +1,146 @@ +/******************************************************************************** + * 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/runtime.h" +#include "score/mw/com/types.h" + +#include "score/mw/com/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" +#include "score/mw/com/test/skeleton_move_semantics/consumer.h" +#include "score/mw/com/test/skeleton_move_semantics/provider.h" +#include "score/mw/com/test/skeleton_move_semantics/test_parameters.h" + +#include +#include +#include +#include +#include + +namespace +{ + +const score::mw::com::InstanceSpecifier kInstanceSpecifierMovedTo = + score::mw::com::InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedTo"}) + .value(); +const score::mw::com::InstanceSpecifier kInstanceSpecifierMovedFrom = + score::mw::com::InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedFrom"}) + .value(); + +} // namespace + +int main(int argc, const char** argv) +{ + auto test_configuration{score::mw::com::test::ReadCommandLineArguments(argc, argv)}; + + score::mw::com::test::SetupAssertHandler(); + score::mw::com::runtime::InitializeRuntime(argc, argv); + + score::cpp::stop_source stop_source{}; + const bool sig_term_handler_setup_success = score::mw::com::SetupStopTokenSigTermHandler(stop_source); + if (!sig_term_handler_setup_success) + { + std::cerr << "Unable to set signal handler for SIGINT and/or SIGTERM, cautiously continuing" << std::endl; + } + + const auto num_send_iterations = [&test_configuration]() { + if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveConstructNotOffered) + { + // In this scenario, the provider will: + // - Create a skeleton + // - move construct the skeleton + // - offer the service + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects two send iterations. + return 2U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveConstructOffered) + { + // In this scenario, the provider will: + // - Create a skeleton + // - offer the service + // - send samples + // - move construct the skeleton while the service is offered + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects three send iterations. + return 3U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignNotOffered) + { + // In this scenario, the provider will: + // - Create 2 skeletons + // - move assign the skeleton + // - offer the moved-to service + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects two send iterations. + return 2U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignOffered) + { + // In this scenario, the provider will: + // - Create 2 skeletons + // - offer both services + // - send samples in both services + // - move assign the skeleton + // - send samples from moved-to service + // - stop offering the moved-to service + // - offer the moved-to service again + // - send samples again + // The receiver connected to the moved-from service expects one send iteration. The receiver connected to + // the moved-to service expects three send iterations. + return 3U; + } + + score::mw::com::test::FailTest("Unknown scenario, cannot determine number of send iterations"); + return 0U; + }(); + + std::cout << "Starting provider and consumer with scenario " + << static_cast(test_configuration.scenario) << ", number of samples to send per offer " + << test_configuration.number_of_samples_to_send_per_offer << " and number of send iterations" + << num_send_iterations << std::endl; + + auto provider_future = std::async(score::mw::com::test::RunProvider, + test_configuration.scenario, + test_configuration.number_of_samples_to_send_per_offer, + stop_source.get_token()); + auto consumer_future = std::async(score::mw::com::test::RunConsumer, + kInstanceSpecifierMovedTo, + test_configuration.number_of_samples_to_send_per_offer, + num_send_iterations, + stop_source.get_token()); + + if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignOffered) + { + const std::size_t num_moved_to_send_iterations{1U}; + auto moved_from_consumer_future = std::async(score::mw::com::test::RunConsumer, + kInstanceSpecifierMovedFrom, + test_configuration.number_of_samples_to_send_per_offer, + num_moved_to_send_iterations, + stop_source.get_token()); + moved_from_consumer_future.get(); + } + + provider_future.get(); + consumer_future.get(); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/skeleton_move_semantics/main_provider.cpp b/score/mw/com/test/skeleton_move_semantics/main_provider.cpp new file mode 100644 index 000000000..08e8cabb7 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/main_provider.cpp @@ -0,0 +1,123 @@ +/******************************************************************************** + * 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/runtime.h" +#include "score/mw/com/types.h" + +#include "score/mw/com/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" +#include "score/mw/com/test/skeleton_move_semantics/provider.h" +#include "score/mw/com/test/skeleton_move_semantics/test_parameters.h" + +#include +#include +#include +#include + +namespace +{ + +const score::mw::com::InstanceSpecifier kInstanceSpecifierMovedTo = + score::mw::com::InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedTo"}) + .value(); +const score::mw::com::InstanceSpecifier kInstanceSpecifierMovedFrom = + score::mw::com::InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedFrom"}) + .value(); + +} // namespace + +int main(int argc, const char** argv) +{ + auto test_configuration{score::mw::com::test::ReadCommandLineArguments(argc, argv)}; + + score::mw::com::test::SetupAssertHandler(); + score::mw::com::runtime::InitializeRuntime(argc, argv); + + score::cpp::stop_source stop_source{}; + const bool sig_term_handler_setup_success = score::mw::com::SetupStopTokenSigTermHandler(stop_source); + if (!sig_term_handler_setup_success) + { + std::cerr << "Unable to set signal handler for SIGINT and/or SIGTERM, cautiously continuing" << std::endl; + } + + const auto num_send_iterations = [&test_configuration]() { + if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveConstructNotOffered) + { + // In this scenario, the provider will: + // - Create a skeleton + // - move construct the skeleton + // - offer the service + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects two send iterations. + return 2U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveConstructOffered) + { + // In this scenario, the provider will: + // - Create a skeleton + // - offer the service + // - send samples + // - move construct the skeleton while the service is offered + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects three send iterations. + return 3U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignNotOffered) + { + // In this scenario, the provider will: + // - Create 2 skeletons + // - move assign the skeleton + // - offer the moved-to service + // - send samples + // - stop offering the service + // - offer the service again + // - send samples again + // The receiver therefore expects two send iterations. + return 2U; + } + else if (test_configuration.scenario == score::mw::com::test::SkeletonMoveScenario::kMoveAssignOffered) + { + // In this scenario, the provider will: + // - Create 2 skeletons + // - offer both services + // - send samples in both services + // - move assign the skeleton + // - send samples from moved-to service + // - stop offering the moved-to service + // - offer the moved-to service again + // - send samples again + // The receiver connected to the moved-from service expects one send iteration. The receiver connected to + // the moved-to service expects three send iterations. + return 3U; + } + + score::mw::com::test::FailTest("Unknown scenario, cannot determine number of send iterations"); + return 0U; + }(); + + std::cout << "Starting provider and consumer with scenario " + << static_cast(test_configuration.scenario) << ", number of samples to send per offer " + << test_configuration.number_of_samples_to_send_per_offer << " and number of send iterations" + << num_send_iterations << std::endl; + + score::mw::com::test::RunProvider( + test_configuration.scenario, test_configuration.number_of_samples_to_send_per_offer, stop_source.get_token()); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/skeleton_move_semantics/provider.cpp b/score/mw/com/test/skeleton_move_semantics/provider.cpp new file mode 100644 index 000000000..32ecf554d --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/provider.cpp @@ -0,0 +1,361 @@ +/******************************************************************************** + * 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/test/skeleton_move_semantics/provider.h" + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/methods/methods_test_resources/process_synchronizer.h" +#include "score/mw/com/test/methods/methods_test_resources/skeleton_container.h" +#include "score/mw/com/test/skeleton_move_semantics/test_event_datatype.h" + +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::string kInterprocessNotificationShmPath{"/skeleton_move_semantics_interprocess_notification"}; + +const InstanceSpecifier kInstanceSpecifierMovedTo = + InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedTo"}).value(); +const InstanceSpecifier kInstanceSpecifierMovedFrom = + InstanceSpecifier::Create(std::string{"test/skeleton_move_semantics/MoveEventInterfaceMovedFrom"}).value(); + +void SendSamples(SkeletonMoveSemanticsSkeleton& skeleton, + const std::size_t number_of_samples_to_send_per_offer, + const std::uint32_t initial_value) +{ + std::cout << "\nProvider: Sending " << number_of_samples_to_send_per_offer << " samples" << std::endl; + for (std::uint32_t i = 0; i < number_of_samples_to_send_per_offer; ++i) + { + auto send_result = skeleton.moved_event_.Send(i + initial_value); + if (!send_result.has_value()) + { + FailTest("Provider: Send failed: ", send_result.error()); + } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } +} + +void RunProviderMoveConstructNotOfferedSkeleton(const score::cpp::stop_token& stop_token, + const std::size_t number_of_samples_to_send_per_offer, + ProcessSynchronizer& proxy_done_synchronizer) +{ + // Step 1. Create skeleton + std::cout << "\nProvider: Step 1 - Create skeleton" << std::endl; + SkeletonContainer skeleton_container{}; + skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_move_semantics"); + auto original_skeleton = skeleton_container.Extract(); + + // Step 2. Move construct skeleton + std::cout << "\nProvider: Step 2 - Move construct skeleton" << std::endl; + auto moved_to_skeleton = std::move(original_skeleton); + + // Step 3. Offer skeleton + std::cout << "\nProvider: Step 3 - Offer skeleton" << std::endl; + auto offer_service_result = moved_to_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 4. Send n values + std::cout << "\nProvider: Step 4 - Send " << number_of_samples_to_send_per_offer << " samples" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, 1U); + + // Step 5. Wait for consumer to notify that it received the first n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } + proxy_done_synchronizer.Reset(); + + // Step 6. Stop offering skeleton + std::cout << "\nProvider: Step 6 - Stop offering skeleton" << std::endl; + moved_to_skeleton.StopOfferService(); + + // Step 7. Offer skeleton again + std::cout << "\nProvider: Step 7 - Offer skeleton again" << std::endl; + offer_service_result = moved_to_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 8. Send n values + std::cout << "\nProvider: Step 8 - Send " << number_of_samples_to_send_per_offer + << " samples again after re-offering" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, number_of_samples_to_send_per_offer + 1U); + + // Step 9. Wait for consumer to notify that it received the second n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } +} + +void RunProviderMoveConstructOfferedSkeleton(const score::cpp::stop_token& stop_token, + const std::size_t number_of_samples_to_send_per_offer, + ProcessSynchronizer& proxy_done_synchronizer) +{ + // Step 1. Create skeleton + std::cout << "\nProvider: Step 1 - Create skeleton" << std::endl; + SkeletonContainer skeleton_container{}; + skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_move_semantics"); + + // Step 2. Offer skeleton + std::cout << "\nProvider: Step 2 - Offer skeleton" << std::endl; + skeleton_container.OfferService("skeleton_move_semantics"); + + // Step 3. Send n values + std::cout << "\nProvider: Step 3 - Send " << number_of_samples_to_send_per_offer << " samples" << std::endl; + SendSamples(skeleton_container.GetSkeleton(), number_of_samples_to_send_per_offer, 1U); + + // Step 4. Wait for consumer to notify that it received the first n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } + proxy_done_synchronizer.Reset(); + + // Step 5. Move construct skeleton while offered + std::cout << "\nProvider: Step 5 - Move construct skeleton while offered" << std::endl; + auto moved_to_skeleton = std::move(skeleton_container.GetSkeleton()); + + // Step 6. Send n values + std::cout << "\nProvider: Step 6 - Send " << number_of_samples_to_send_per_offer + << " samples again after re-offering" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, number_of_samples_to_send_per_offer + 1U); + + // Step 7. Wait for consumer to notify that it received the second n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } + proxy_done_synchronizer.Reset(); + + // Step 8. Stop offering skeleton + std::cout << "\nProvider: Step 8 - Stop offering skeleton" << std::endl; + moved_to_skeleton.StopOfferService(); + + // Step 9. Offer skeleton again + std::cout << "\nProvider: Step 9 - Offer skeleton again" << std::endl; + auto offer_service_result = moved_to_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 10. Send n values + std::cout << "\nProvider: Step 10 - Send " << number_of_samples_to_send_per_offer + << " samples again after re-offering" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, number_of_samples_to_send_per_offer * 2U + 1U); + + // Step 11. Wait for consumer to notify that it received the third n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } + proxy_done_synchronizer.Reset(); +} + +void RunProviderMoveAssignNotOfferedSkeleton(const score::cpp::stop_token& stop_token, + const std::size_t number_of_samples_to_send_per_offer, + ProcessSynchronizer& proxy_done_synchronizer) +{ + // Step 1. Create two skeletons + std::cout << "\nProvider: Step 1 - Create two skeletons" << std::endl; + SkeletonContainer moved_from_skeleton_container{}; + moved_from_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_move_semantics"); + auto moved_from_skeleton = moved_from_skeleton_container.Extract(); + + SkeletonContainer moved_to_skeleton_container{}; + moved_to_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedFrom, "skeleton_move_semantics"); + auto moved_to_skeleton = moved_to_skeleton_container.Extract(); + + // Step 2. Move assign skeleton + std::cout << "\nProvider: Step 2 - Move assign skeleton" << std::endl; + moved_to_skeleton = std::move(moved_from_skeleton); + + // Step 3. Offer skeleton + std::cout << "\nProvider: Step 3 - Offer skeleton" << std::endl; + auto offer_service_result = moved_to_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 4. Send n values + std::cout << "\nProvider: Step 4 - Send " << number_of_samples_to_send_per_offer << " samples" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, 1U); + + // Step 5. Wait for consumer to notify that it received the first n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } + proxy_done_synchronizer.Reset(); + + // Step 6. Stop offering skeleton + std::cout << "\nProvider: Step 6 - Stop offering skeleton" << std::endl; + moved_to_skeleton.StopOfferService(); + + // Step 7. Offer skeleton again + std::cout << "\nProvider: Step 7 - Offer skeleton again" << std::endl; + offer_service_result = moved_to_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 8. Send n values + std::cout << "\nProvider: Step 8 - Send " << number_of_samples_to_send_per_offer + << " samples again after re-offering" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, number_of_samples_to_send_per_offer + 1U); + + // Step 9. Wait for consumer to notify that it received the second n values + if (!proxy_done_synchronizer.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for consumer done was aborted"); + } +} + +void RunProviderMoveAssignOfferedSkeleton(const score::cpp::stop_token& stop_token, + const std::size_t number_of_samples_to_send_per_offer, + ProcessSynchronizer& moved_to_proxy_done_synchronizer_result, + ProcessSynchronizer& moved_from_proxy_done_synchronizer_result) +{ + // Step 1. Create two skeletons + std::cout << "\nProvider: Step 1 - Create two skeletons" << std::endl; + SkeletonContainer moved_from_skeleton_container{}; + moved_from_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_move_semantics"); + + SkeletonContainer moved_to_skeleton_container{}; + moved_to_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedFrom, "skeleton_move_semantics"); + + // Step 2. Offer both skeletons + std::cout << "\nProvider: Step 2 - Offer both skeletons" << std::endl; + moved_from_skeleton_container.OfferService("skeleton_move_semantics"); + moved_to_skeleton_container.OfferService("skeleton_move_semantics"); + + // Step 3. Send n values from both skeletons + std::cout << "\nProvider: Step 3 - Send " << number_of_samples_to_send_per_offer << " samples from both skeletons" + << std::endl; + SendSamples(moved_to_skeleton_container.GetSkeleton(), number_of_samples_to_send_per_offer, 1U); + SendSamples(moved_from_skeleton_container.GetSkeleton(), number_of_samples_to_send_per_offer, 1U); + + // Step 4. Wait for both consumers to notify that they received the first n values + if (!moved_to_proxy_done_synchronizer_result.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for moved-to consumer done was aborted"); + } + moved_to_proxy_done_synchronizer_result.Reset(); + if (!moved_from_proxy_done_synchronizer_result.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for moved-from consumer done was aborted"); + } + moved_from_proxy_done_synchronizer_result.Reset(); + + // Step 5. Move assign skeleton + std::cout << "\nProvider: Step 5 - Move assign skeleton" << std::endl; + auto moved_from_skeleton = moved_from_skeleton_container.Extract(); + auto moved_to_skeleton = moved_to_skeleton_container.Extract(); + moved_to_skeleton = std::move(moved_from_skeleton); + + // Step 6. Send n values from moved-to skeleton + std::cout << "\nProvider: Step 6 - Send " << number_of_samples_to_send_per_offer + << " samples from moved-to skeleton" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, number_of_samples_to_send_per_offer + 1U); + + // Step 7. Stop offering moved-to skeleton + std::cout << "\nProvider: Step 7 - Stop offering moved-to skeleton" << std::endl; + moved_to_skeleton.StopOfferService(); + + // Step 8. Offer moved-to skeleton again + std::cout << "\nProvider: Step 8 - Offer moved-to skeleton again" << std::endl; + const auto offer_service_result = moved_to_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 9. Send n values + std::cout << "\nProvider: Step 9 - Send " << number_of_samples_to_send_per_offer + << " samples again after re-offering" << std::endl; + SendSamples(moved_to_skeleton, number_of_samples_to_send_per_offer, 2 * number_of_samples_to_send_per_offer + 1U); + + // Step 10. Wait for moved-to consumer to notify that it received the third n values + if (!moved_to_proxy_done_synchronizer_result.WaitWithAbort(stop_token)) + { + FailTest("skeleton_move_semantics provider failed: waiting for moved-to consumer done was aborted"); + } +} + +} // namespace + +void RunProvider(const SkeletonMoveScenario& scenario, + const std::size_t num_samples_to_send, + const score::cpp::stop_token& stop_token) +{ + const auto moved_to_name = filesystem::Path{kInstanceSpecifierMovedTo.ToString()}.Filename().Native(); + auto moved_to_proxy_done_synchronizer_result = + ProcessSynchronizer::Create(kInterprocessNotificationShmPath + std::string{moved_to_name}); + if (!moved_to_proxy_done_synchronizer_result.has_value()) + { + FailTest("skeleton_move_semantics provider failed: could not create ready synchronizer"); + } + + switch (scenario) + { + case SkeletonMoveScenario::kMoveConstructNotOffered: + { + RunProviderMoveConstructNotOfferedSkeleton( + stop_token, num_samples_to_send, moved_to_proxy_done_synchronizer_result.value()); + break; + } + case SkeletonMoveScenario::kMoveConstructOffered: + { + RunProviderMoveConstructOfferedSkeleton( + stop_token, num_samples_to_send, moved_to_proxy_done_synchronizer_result.value()); + break; + } + case SkeletonMoveScenario::kMoveAssignNotOffered: + { + RunProviderMoveAssignNotOfferedSkeleton( + stop_token, num_samples_to_send, moved_to_proxy_done_synchronizer_result.value()); + break; + } + case SkeletonMoveScenario::kMoveAssignOffered: + { + const auto moved_from_name = filesystem::Path{kInstanceSpecifierMovedFrom.ToString()}.Filename().Native(); + auto moved_from_proxy_done_synchronizer_result = + ProcessSynchronizer::Create(kInterprocessNotificationShmPath + std::string{moved_from_name}); + if (!moved_from_proxy_done_synchronizer_result.has_value()) + { + FailTest("skeleton_move_semantics provider failed: could not create ready synchronizer"); + } + RunProviderMoveAssignOfferedSkeleton(stop_token, + num_samples_to_send, + moved_to_proxy_done_synchronizer_result.value(), + moved_from_proxy_done_synchronizer_result.value()); + break; + } + case SkeletonMoveScenario::kNumberOfScenarios: + [[fallthrough]]; + default: + FailTest("skeleton_move_semantics provider failed: unknown scenario"); + } +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/skeleton_move_semantics/provider.h b/score/mw/com/test/skeleton_move_semantics/provider.h new file mode 100644 index 000000000..cd1ae1486 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/provider.h @@ -0,0 +1,29 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_PROVIDER_H +#define SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_PROVIDER_H + +#include "score/mw/com/test/skeleton_move_semantics/test_parameters.h" + +#include + +namespace score::mw::com::test +{ + +void RunProvider(const SkeletonMoveScenario& scenario, + const std::size_t num_samples_to_send, + const score::cpp::stop_token& stop_token); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_PROVIDER_H diff --git a/score/mw/com/test/skeleton_move_semantics/test_event_datatype.cpp b/score/mw/com/test/skeleton_move_semantics/test_event_datatype.cpp new file mode 100644 index 000000000..8ee594648 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/test_event_datatype.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/test/skeleton_move_semantics/test_event_datatype.h" diff --git a/score/mw/com/test/skeleton_move_semantics/test_event_datatype.h b/score/mw/com/test/skeleton_move_semantics/test_event_datatype.h new file mode 100644 index 000000000..9f5aac0a0 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/test_event_datatype.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_TEST_EVENT_DATATYPE_H +#define SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_TEST_EVENT_DATATYPE_H + +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +template +class SkeletonMoveSemanticsInterface : public T::Base +{ + public: + using T::Base::Base; + + typename T::template Event moved_event_{*this, "moved_event"}; +}; + +using SkeletonMoveSemanticsProxy = score::mw::com::AsProxy; +using SkeletonMoveSemanticsSkeleton = score::mw::com::AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_TEST_EVENT_DATATYPE_H diff --git a/score/mw/com/test/skeleton_move_semantics/test_parameters.cpp b/score/mw/com/test/skeleton_move_semantics/test_parameters.cpp new file mode 100644 index 000000000..8279a162e --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/test_parameters.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * 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/test/skeleton_move_semantics/test_parameters.h" + +#include "score/mw/com/test/common_test_resources/command_line_parser.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" + +namespace score::mw::com::test +{ + +CombinedTestConfiguration ReadCommandLineArguments(int argc, const char** argv) +{ + auto args = ParseCommandLineArguments( + argc, argv, {{kScenario, ""}, {kNumberOfSamplesToSend, ""}, {kServiceInstanceManifest, ""}}); + + const auto scenario_index = GetValue(args, kScenario); + if (scenario_index >= static_cast(SkeletonMoveScenario::kNumberOfScenarios)) + { + FailTest("Consumer: ", + kScenario, + " value ", + scenario_index, + " is out of range. Valid values are between 0 and ", + static_cast(SkeletonMoveScenario::kNumberOfScenarios) - 1, + "."); + } + const auto scenario = static_cast(scenario_index); + + const auto num_samples_to_send = GetValue(args, kNumberOfSamplesToSend); + if (num_samples_to_send == 0U) + { + FailTest("Consumer: ", kNumberOfSamplesToSend, " value must be larger than 0."); + } + + auto service_instance_manifest = GetValue(args, kServiceInstanceManifest); + + return {scenario, num_samples_to_send, service_instance_manifest}; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/skeleton_move_semantics/test_parameters.h b/score/mw/com/test/skeleton_move_semantics/test_parameters.h new file mode 100644 index 000000000..7e19fbab2 --- /dev/null +++ b/score/mw/com/test/skeleton_move_semantics/test_parameters.h @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_TEST_PARAMETERS_H +#define SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_TEST_PARAMETERS_H + +#include +#include + +namespace score::mw::com::test +{ + +const std::string kScenario{"scenario"}; +const std::string kNumberOfSamplesToSend{"number-of-samples-to-send"}; +const std::string kServiceInstanceManifest{"service-instance-manifest"}; + +enum class SkeletonMoveScenario : std::uint8_t +{ + kMoveConstructNotOffered, + kMoveConstructOffered, + kMoveAssignNotOffered, + kMoveAssignOffered, + kNumberOfScenarios +}; + +struct CombinedTestConfiguration +{ + SkeletonMoveScenario scenario; + std::size_t number_of_samples_to_send_per_offer; + std::string service_instance_manifest; +}; + +CombinedTestConfiguration ReadCommandLineArguments(int argc, const char** argv); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_MOVE_SEMANTICS_TEST_PARAMETERS_H From f26f793b72898b3914ddf0f544dc240d3da4aefd Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Tue, 16 Jun 2026 19:23:04 +0200 Subject: [PATCH 2/7] mw/com: Add ReferenceToMoveableOwner and ReferenceToMoveable In various places in our codebase, we have the issue in which we hand out a reference to a class which is moveable. If the class is moved, then we need to update this reference. E.g. SkeletonBase stores a map of references to its service elements. Each service element therefore must store a reference to the SkeletonBase and update the reference to itself when it's moved. The SkeletonBase must also update the reference to itself within each service element when it is moved. To avoid such complexity, this commit introduces a class ReferenceToMoveable which can be given to the SkeletonBase which is stored in the heap so a reference to it will never be invalidated. This then stores a reference to the service element which is updated by the service element if it's moved. With this approach, the service element doesn't need to store a reference to the parent SkeletonBase so the parent SkeletonBase doesn't need to inform the service element when it moves. --- score/mw/com/impl/BUILD | 36 ++++++ score/mw/com/impl/reference_to_moveable.cpp | 13 ++ score/mw/com/impl/reference_to_moveable.h | 67 ++++++++++ .../com/impl/reference_to_moveable_owner.cpp | 13 ++ .../mw/com/impl/reference_to_moveable_owner.h | 62 +++++++++ .../impl/reference_to_moveable_owner_test.cpp | 121 ++++++++++++++++++ .../com/impl/reference_to_moveable_test.cpp | 33 +++++ 7 files changed, 345 insertions(+) create mode 100644 score/mw/com/impl/reference_to_moveable.cpp create mode 100644 score/mw/com/impl/reference_to_moveable.h create mode 100644 score/mw/com/impl/reference_to_moveable_owner.cpp create mode 100644 score/mw/com/impl/reference_to_moveable_owner.h create mode 100644 score/mw/com/impl/reference_to_moveable_owner_test.cpp create mode 100644 score/mw/com/impl/reference_to_moveable_test.cpp diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD index 2e11a3422..32c6ee92f 100644 --- a/score/mw/com/impl/BUILD +++ b/score/mw/com/impl/BUILD @@ -1044,6 +1044,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 +1323,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/reference_to_moveable.cpp b/score/mw/com/impl/reference_to_moveable.cpp new file mode 100644 index 000000000..ac1578a60 --- /dev/null +++ b/score/mw/com/impl/reference_to_moveable.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/reference_to_moveable.h" diff --git a/score/mw/com/impl/reference_to_moveable.h b/score/mw/com/impl/reference_to_moveable.h new file mode 100644 index 000000000..6ab2b70ce --- /dev/null +++ b/score/mw/com/impl/reference_to_moveable.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_REFERENCE_TO_MOVEABLE_H +#define SCORE_MW_COM_IMPL_REFERENCE_TO_MOVEABLE_H + +#include + +namespace score::mw::com::impl +{ + +template +class ReferenceToMoveableOwner; + +/// \brief Used only in conjuction with ReferenceToMoveableOwner to allow moveable classes to hand out references to +/// themselves while allowing these references to be updated when the moveable class T is moved. +/// +/// See the documentation for ReferenceToMoveableOwner for more details. +template +class ReferenceToMoveable +{ + friend class ReferenceToMoveableOwner; + + struct PrivateConstructorTag + { + }; + + public: + ReferenceToMoveable(PrivateConstructorTag, T& reference_object) : reference_object_{reference_object} {} + + ReferenceToMoveable(const ReferenceToMoveable&) = delete; + ReferenceToMoveable& operator=(const ReferenceToMoveable&) = delete; + ReferenceToMoveable(ReferenceToMoveable&&) = delete; + ReferenceToMoveable& operator=(ReferenceToMoveable&&) = delete; + ~ReferenceToMoveable() = default; + + const T& Get() const + { + return reference_object_.get(); + } + + T& Get() + { + return reference_object_.get(); + } + + private: + void Update(T& field) + { + reference_object_ = field; + } + + std::reference_wrapper reference_object_; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_REFERENCE_TO_MOVEABLE_H diff --git a/score/mw/com/impl/reference_to_moveable_owner.cpp b/score/mw/com/impl/reference_to_moveable_owner.cpp new file mode 100644 index 000000000..1e58d0b80 --- /dev/null +++ b/score/mw/com/impl/reference_to_moveable_owner.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/reference_to_moveable_owner.h" diff --git a/score/mw/com/impl/reference_to_moveable_owner.h b/score/mw/com/impl/reference_to_moveable_owner.h new file mode 100644 index 000000000..4d08bf00c --- /dev/null +++ b/score/mw/com/impl/reference_to_moveable_owner.h @@ -0,0 +1,62 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_IMPL_REFERENCE_TO_MOVEABLE_OWNER_H +#define SCORE_MW_COM_IMPL_REFERENCE_TO_MOVEABLE_OWNER_H + +#include "score/mw/com/impl/reference_to_moveable.h" + +#include + +namespace score::mw::com::impl +{ + +/// \brief Used in conjuction with ReferenceToMoveable to allow moveable classes to hand out references to +/// themselves while allowing these references to be updated when the moveable class T is moved. +/// +/// Moveable class T will store a ReferenceToMoveableOwner and hand out references to the ReferenceToMoveable +/// (instead of pointers to itself) via Get. The ReferenceToMoveable is stored on the heap and will never be moved. The +/// class T must manually update the reference to itself via Update in this class when moved. +/// +/// See the test containing DummyClass for an example of how to use this class together with ReferenceToMoveable. +template +class ReferenceToMoveableOwner +{ + public: + explicit ReferenceToMoveableOwner(T& field) + : reference_to_moveable_{ + std::make_unique>(typename ReferenceToMoveable::PrivateConstructorTag{}, field)} + { + } + + ReferenceToMoveable& Get() + { + return *(reference_to_moveable_); + } + + const ReferenceToMoveable& Get() const + { + return *(reference_to_moveable_); + } + + void Update(T& field) + { + reference_to_moveable_->Update(field); + } + + private: + std::unique_ptr> reference_to_moveable_; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_REFERENCE_TO_MOVEABLE_OWNER_H diff --git a/score/mw/com/impl/reference_to_moveable_owner_test.cpp b/score/mw/com/impl/reference_to_moveable_owner_test.cpp new file mode 100644 index 000000000..d87965efc --- /dev/null +++ b/score/mw/com/impl/reference_to_moveable_owner_test.cpp @@ -0,0 +1,121 @@ +/******************************************************************************** + * 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/reference_to_moveable_owner.h" + +#include "score/mw/com/impl/reference_to_moveable.h" + +#include + +namespace score::mw::com::impl +{ + +namespace +{ + +TEST(ReferenceToMoveableOwnerTest, NotCopyable) +{ + static_assert(!std::is_copy_constructible_v>, "Is wrongly copyable"); + static_assert(!std::is_copy_assignable_v>, "Is wrongly copyable"); +} + +TEST(ReferenceToMoveableOwnerTest, IsMoveable) +{ + static_assert(std::is_move_constructible_v>, "Is not move constructible"); + static_assert(std::is_move_assignable_v>, "Is not move assignable"); +} + +TEST(ReferenceToMoveableOwnerTest, GetReturnsReferenceToObject) +{ + // Given a ReferenceToMoveableOwner pointing to an int + const int value = 42; + ReferenceToMoveableOwner wrapper{value}; + + // When Get is called + auto& reference_to_moveable = wrapper.Get(); + + // Then the reference points to the original int + EXPECT_EQ(reference_to_moveable.Get(), value); + EXPECT_EQ(&reference_to_moveable.Get(), &value); +} + +TEST(ReferenceToMoveableOwnerTest, GetReturnsReferenceToUpdatedObject) +{ + // Given a ReferenceToMoveableOwner pointing to an int + const int value = 42; + ReferenceToMoveableOwner wrapper{value}; + + // When calling Update with a new int + const int new_value = 100; + wrapper.Update(new_value); + + // Then Get returns a reference to the ned int + auto& reference_to_moveable = wrapper.Get(); + EXPECT_EQ(reference_to_moveable.Get(), new_value); + EXPECT_EQ(&reference_to_moveable.Get(), &new_value); +} + +// Note. This class and the test below is added as a demonstration of how ReferenceToMoveableOwner and +// ReferenceToMoveable can be used together to allow moveable classes to hand out references to themselves while +// allowing these references to be updated when the moveable class is moved. +class DummyClass +{ + public: + DummyClass() : ptr_wrapper_{*this} {} + + ~DummyClass() = default; + + DummyClass(const DummyClass&) = delete; + DummyClass& operator=(const DummyClass&) & = delete; + + DummyClass(DummyClass&& other) noexcept : ptr_wrapper_{std::move(other.ptr_wrapper_)} + { + ptr_wrapper_.Update(*this); + } + DummyClass& operator=(DummyClass&& other) & noexcept + { + if (this != &other) + { + ptr_wrapper_ = std::move(other.ptr_wrapper_); + ptr_wrapper_.Update(*this); + } + return *this; + } + + ReferenceToMoveable& GetReferenceToMoveable() + { + return ptr_wrapper_.Get(); + } + + private: + ReferenceToMoveableOwner ptr_wrapper_; +}; + +TEST(ReferenceToMoveableOwnerDummyClassTest, GetReturnsReferenceToUpdatedObjectWhenMoveConstructingDummyClass) +{ + // Given an DummyClass with a ReferenceToMoveableOwner pointing to itself + DummyClass owning_class{}; + + // and given that we want to store a reference to DummyClass which should be updated when DummyClass is moved + auto& owning_class_reference = owning_class.GetReferenceToMoveable(); + + // When the DummyClass is move constructed + DummyClass moved_owning_class{std::move(owning_class)}; + + // Then the ReferenceToMoveableOwner retrieved from the original ReferenceToMoveable should point to the moved-to + // DummyClass + auto& reference_wrapper = owning_class_reference.Get(); + EXPECT_EQ(&reference_wrapper, &moved_owning_class); +} + +} // namespace +} // namespace score::mw::com::impl diff --git a/score/mw/com/impl/reference_to_moveable_test.cpp b/score/mw/com/impl/reference_to_moveable_test.cpp new file mode 100644 index 000000000..801408dc0 --- /dev/null +++ b/score/mw/com/impl/reference_to_moveable_test.cpp @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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/reference_to_moveable.h" + +#include + +// Most tests for non-moveable references are in reference_to_moveable_owner_test.cpp + +namespace score::mw::com::impl +{ +namespace +{ + +TEST(ReferenceToMoveableTest, NotCopyableOrMoveable) +{ + static_assert(!std::is_copy_constructible_v>, "Is wrongly copyable"); + static_assert(!std::is_copy_assignable_v>, "Is wrongly copyable"); + static_assert(!std::is_move_constructible_v>, "Is wrongly move constructible"); + static_assert(!std::is_move_assignable_v>, "Is wrongly move assignable"); +} + +} // namespace +} // namespace score::mw::com::impl From e1450e33e0238b4138e623b154d30c4342766494 Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Fri, 19 Jun 2026 08:46:24 +0200 Subject: [PATCH 3/7] mw/com: Remove updating of service element in SkeletonBase Previously, each service element had to store a reference to the parent SkeletonBase so that it could update the reference to itself in the SkeletonBase when the service element was moved. This meant that both SkeletonBase and the service elements had to store references to each other. Keeping these references in a valid state during moving was complex and error prone. We now use ReferenceToMoveableOwner in each service element which is updated when the service element moves. The SkeletonBase stores a ReferenceToMoveable to each service element, instead of a regular reference which is now always valid, regardless of moving of the SkeletonBase or service elements. --- .../structural_event_field_extensions.puml | 1 - .../skeleton_binding_model.puml | 8 +- .../skeleton_proxy_binding_model.puml | 8 +- score/mw/com/impl/BUILD | 5 + score/mw/com/impl/generic_skeleton_event.cpp | 2 +- score/mw/com/impl/methods/BUILD | 1 + score/mw/com/impl/methods/skeleton_method.h | 39 +----- .../com/impl/methods/skeleton_method_base.cpp | 8 -- .../com/impl/methods/skeleton_method_base.h | 40 +++++- .../methods/skeleton_method_base_test.cpp | 56 -------- .../skeleton_wrapper_class_test_view.h | 4 +- .../mw/com/impl/reference_to_moveable_owner.h | 2 +- score/mw/com/impl/skeleton_base.cpp | 130 ++++-------------- score/mw/com/impl/skeleton_base.h | 26 ++-- score/mw/com/impl/skeleton_base_test.cpp | 96 +++++++------ score/mw/com/impl/skeleton_event.h | 41 +----- score/mw/com/impl/skeleton_event_base.h | 53 ++++--- score/mw/com/impl/skeleton_event_test.cpp | 6 +- score/mw/com/impl/skeleton_field.h | 64 +-------- score/mw/com/impl/skeleton_field_base.h | 50 ++++--- .../mw/com/impl/skeleton_field_base_test.cpp | 4 - score/mw/com/impl/skeleton_field_test.cpp | 32 +---- score/mw/com/impl/tracing/BUILD | 1 + .../mw/com/impl/tracing/skeleton_tracing.cpp | 22 +-- score/mw/com/impl/tracing/skeleton_tracing.h | 10 +- .../impl/tracing/skeleton_tracing_test.cpp | 14 +- 26 files changed, 247 insertions(+), 476 deletions(-) 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 32c6ee92f..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", 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..5215e6ab7 100644 --- a/score/mw/com/impl/methods/BUILD +++ b/score/mw/com/impl/methods/BUILD @@ -140,6 +140,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", ], ) diff --git a/score/mw/com/impl/methods/skeleton_method.h b/score/mw/com/impl/methods/skeleton_method.h index d8445c933..9b0289677 100644 --- a/score/mw/com/impl/methods/skeleton_method.h +++ b/score/mw/com/impl/methods/skeleton_method.h @@ -21,7 +21,6 @@ #include "score/result/result.h" #include -#include #include #include #include @@ -102,16 +101,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,37 +118,7 @@ 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 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/mocking/skeleton_wrapper_class_test_view.h b/score/mw/com/impl/mocking/skeleton_wrapper_class_test_view.h index 8d1709ae5..5427a1345 100644 --- a/score/mw/com/impl/mocking/skeleton_wrapper_class_test_view.h +++ b/score/mw/com/impl/mocking/skeleton_wrapper_class_test_view.h @@ -191,7 +191,7 @@ class SkeletonWrapperClassTestView [&events](auto& event_mock_pair) { auto event_base_it = events.find(std::string{event_mock_pair.event_name}); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(event_base_it != events.end()); - auto& event_base = event_base_it->second.get(); + auto& event_base = event_base_it->second.get().Get(); InjectEventMock(event_base, event_mock_pair.mock); }(unpacked_tuple), ...); @@ -204,7 +204,7 @@ class SkeletonWrapperClassTestView [&fields](auto& field_mock_pair) { auto field_base_it = fields.find(std::string{field_mock_pair.field_name}); SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(field_base_it != fields.end()); - auto& field_base = field_base_it->second.get(); + auto& field_base = field_base_it->second.get().Get(); InjectFieldMock(field_base, field_mock_pair.mock); }(unpacked_tuple), ...); diff --git a/score/mw/com/impl/reference_to_moveable_owner.h b/score/mw/com/impl/reference_to_moveable_owner.h index 4d08bf00c..b1c9aeb7b 100644 --- a/score/mw/com/impl/reference_to_moveable_owner.h +++ b/score/mw/com/impl/reference_to_moveable_owner.h @@ -45,7 +45,7 @@ class ReferenceToMoveableOwner const ReferenceToMoveable& Get() const { - return *(reference_to_moveable_); + return *(reference_to_moveable_); } void Update(T& field) diff --git a/score/mw/com/impl/skeleton_base.cpp b/score/mw/com/impl/skeleton_base.cpp index 19b79cca5..62ba8e8c8 100644 --- a/score/mw/com/impl/skeleton_base.cpp +++ b/score/mw/com/impl/skeleton_base.cpp @@ -14,7 +14,7 @@ #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/methods/skeleton_method_base.h" -#include "score/mw/com/impl/plumbing/skeleton_binding_factory.h" +#include "score/mw/com/impl/reference_to_moveable.h" #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_binding.h" #include "score/mw/com/impl/skeleton_event_base.h" @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include @@ -55,7 +55,7 @@ SkeletonBinding::SkeletonEventBindings GetSkeletonEventBindingsMap(const Skeleto for (const auto& event : events) { const std::string_view event_name = event.first; - SkeletonEventBase& skeleton_event_base = event.second.get(); + SkeletonEventBase& skeleton_event_base = event.second.get().Get(); auto skeleton_event_base_view = SkeletonEventBaseView{skeleton_event_base}; auto* event_binding = skeleton_event_base_view.GetBinding(); @@ -72,7 +72,7 @@ SkeletonBinding::SkeletonFieldBindings GetSkeletonFieldBindingsMap(const Skeleto for (const auto& field : fields) { const std::string_view field_name = field.first; - SkeletonFieldBase& skeleton_field_base = field.second.get(); + SkeletonFieldBase& skeleton_field_base = field.second.get().Get(); auto skeleton_field_base_view = SkeletonFieldBaseView{skeleton_field_base}; auto* event_binding = skeleton_field_base_view.GetEventBinding(); @@ -96,49 +96,12 @@ SkeletonBase::SkeletonBase(std::unique_ptr skeleton_binding, In { } -SkeletonBase::SkeletonBase(SkeletonBase&& other) noexcept - : binding_{std::move(other.binding_)}, - events_{std::move(other.events_)}, - fields_{std::move(other.fields_)}, - methods_{std::move(other.methods_)}, - instance_id_{std::move(other.instance_id_)}, - skeleton_mock_{std::move(other.skeleton_mock_)}, - service_offered_flag_{std::move(other.service_offered_flag_)} -{ - UpdateAllServiceElementReferences(); -} - -// Suppress "AUTOSAR C++14 A6-2-1" rule violation. The rule states "Move and copy assignment operators shall either move -// or respectively copy base classes and data members of a class, without any side effects." Due to architectural -// decisions, SkeletonBase must perform a cleanup and update references to itself in its events and fields. Therefore, -// side effects are required. -// coverity[autosar_cpp14_a6_2_1_violation] -SkeletonBase& SkeletonBase::operator=(SkeletonBase&& other) noexcept -{ - if (this == &other) - { - return *this; - } - - binding_ = std::move(other.binding_); - events_ = std::move(other.events_); - fields_ = std::move(other.fields_); - methods_ = std::move(other.methods_); - instance_id_ = std::move(other.instance_id_); - skeleton_mock_ = std::move(other.skeleton_mock_); - service_offered_flag_ = std::move(other.service_offered_flag_); - - UpdateAllServiceElementReferences(); - - return *this; -} - score::Result SkeletonBase::OfferServiceEvents() const noexcept { - for (auto& event : events_) + for (const auto& event : events_) { const auto event_name = event.first; - auto& skeleton_event = event.second.get(); + auto& skeleton_event = event.second.get().Get(); const auto offer_result = skeleton_event.PrepareOffer(); if (!offer_result.has_value()) { @@ -153,10 +116,10 @@ score::Result SkeletonBase::OfferServiceEvents() const noexcept score::Result SkeletonBase::OfferServiceFields() const noexcept { - for (auto& field : fields_) + for (const auto& field : fields_) { const auto field_name = field.first; - auto& skeleton_field = field.second.get(); + auto& skeleton_field = field.second.get().Get(); const auto offer_result = skeleton_field.PrepareOffer(); if (!offer_result.has_value()) { @@ -247,11 +210,11 @@ auto SkeletonBase::StopOfferService() noexcept -> void for (auto& event : events_) { - event.second.get().PrepareStopOffer(); + event.second.get().Get().PrepareStopOffer(); } for (auto& field : fields_) { - field.second.get().PrepareStopOffer(); + field.second.get().Get().PrepareStopOffer(); } auto tracing_handler = tracing::CreateUnregisterShmObjectCallback(instance_id_, events_, fields_, *binding_); @@ -267,23 +230,23 @@ auto SkeletonBase::AreBindingsValid() const noexcept -> bool bool are_service_element_bindings_valid{true}; score::cpp::ignore = - std::for_each(events_.begin(), events_.end(), [&are_service_element_bindings_valid](const auto& element) { - if (SkeletonEventBaseView{element.second.get()}.GetBinding() == nullptr) + std::for_each(events_.begin(), events_.end(), [&are_service_element_bindings_valid](auto& element) { + if (SkeletonEventBaseView{element.second.get().Get()}.GetBinding() == nullptr) { are_service_element_bindings_valid = false; } }); score::cpp::ignore = - std::for_each(fields_.begin(), fields_.end(), [&are_service_element_bindings_valid](const auto& element) { - if (SkeletonFieldBaseView{element.second.get()}.GetEventBinding() == nullptr) + std::for_each(fields_.begin(), fields_.end(), [&are_service_element_bindings_valid](auto& element) { + if (SkeletonFieldBaseView{element.second.get().Get()}.GetEventBinding() == nullptr) { are_service_element_bindings_valid = false; } }); score::cpp::ignore = - std::for_each(methods_.begin(), methods_.end(), [&are_service_element_bindings_valid](const auto& element) { - if (SkeletonMethodBaseView{element.second.get()}.GetMethodBinding() == nullptr) + std::for_each(methods_.begin(), methods_.end(), [&are_service_element_bindings_valid](auto& element) { + if (SkeletonMethodBaseView{element.second.get().Get()}.GetMethodBinding() == nullptr) { are_service_element_bindings_valid = false; } @@ -292,22 +255,6 @@ auto SkeletonBase::AreBindingsValid() const noexcept -> bool return is_skeleton_binding_valid && are_service_element_bindings_valid; } -void SkeletonBase::UpdateAllServiceElementReferences() noexcept -{ - for (auto& event : events_) - { - event.second.get().UpdateSkeletonReference(*this); - } - for (auto& field : fields_) - { - field.second.get().UpdateSkeletonReference(*this); - } - for (auto& method : methods_) - { - method.second.get().UpdateSkeletonReference(*this); - } -} - SkeletonBaseView::SkeletonBaseView(SkeletonBase& skeleton_base) : skeleton_base_{skeleton_base} {} InstanceIdentifier SkeletonBaseView::GetAssociatedInstanceIdentifier() const @@ -320,57 +267,28 @@ SkeletonBinding* SkeletonBaseView::GetBinding() const return skeleton_base_.binding_.get(); } -void SkeletonBaseView::RegisterEvent(const std::string_view event_name, SkeletonEventBase& event) +void SkeletonBaseView::RegisterEvent(const std::string_view event_name, ReferenceToMoveable& event) { - const auto result = skeleton_base_.events_.emplace(event_name, event); + const auto result = skeleton_base_.events_.emplace(event_name, std::ref(event)); const bool was_event_inserted = result.second; SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(was_event_inserted, "Event cannot be registered as it already exists."); } -void SkeletonBaseView::RegisterField(const std::string_view field_name, SkeletonFieldBase& field) +void SkeletonBaseView::RegisterField(const std::string_view field_name, ReferenceToMoveable& field) { - const auto result = skeleton_base_.fields_.emplace(field_name, field); + const auto result = skeleton_base_.fields_.emplace(field_name, std::ref(field)); const bool was_field_inserted = result.second; SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(was_field_inserted, "Field cannot be registered as it already exists."); } -void SkeletonBaseView::RegisterMethod(const std::string_view method_name, SkeletonMethodBase& method) +void SkeletonBaseView::RegisterMethod(const std::string_view method_name, + ReferenceToMoveable& method) { - const auto result = skeleton_base_.methods_.emplace(method_name, method); + const auto result = skeleton_base_.methods_.emplace(method_name, std::ref(method)); const bool was_method_inserted = result.second; SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE(was_method_inserted, "Method cannot be registered as it already exists."); } -void SkeletonBaseView::UpdateEvent(const std::string_view event_name, SkeletonEventBase& event) noexcept -{ - auto event_name_it = skeleton_base_.events_.find(event_name); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - event_name_it != skeleton_base_.events_.cend(), - "SkeletonBaseView::UpdateEvent failed to update event because the requested event doesn't exist"); - - event_name_it->second = event; -} - -void SkeletonBaseView::UpdateField(const std::string_view field_name, SkeletonFieldBase& field) noexcept -{ - auto field_name_it = skeleton_base_.fields_.find(field_name); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - field_name_it != skeleton_base_.fields_.cend(), - "SkeletonBaseView::UpdateField failed to update field because the requested field doesn't exist"); - - field_name_it->second = field; -} - -void SkeletonBaseView::UpdateMethod(const std::string_view method_name, SkeletonMethodBase& method) noexcept -{ - auto method_it = skeleton_base_.methods_.find(method_name); - SCORE_LANGUAGE_FUTURECPP_ASSERT_MESSAGE( - method_it != skeleton_base_.methods_.cend(), - "SkeletonBaseView::UpdateMethod failed to update method because the requested method doesn't exist"); - - method_it->second = method; -} - const SkeletonBase::SkeletonEvents& SkeletonBaseView::GetEvents() const& noexcept { return skeleton_base_.events_; @@ -398,7 +316,7 @@ score::cpp::optional GetInstanceIdentifier(const InstanceSpe { return {}; } - const auto instance_identifier = instance_identifiers.front(); + auto instance_identifier = instance_identifiers.front(); return instance_identifier; } diff --git a/score/mw/com/impl/skeleton_base.h b/score/mw/com/impl/skeleton_base.h index 6c351d215..ac276053f 100644 --- a/score/mw/com/impl/skeleton_base.h +++ b/score/mw/com/impl/skeleton_base.h @@ -17,6 +17,7 @@ #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/instance_specifier.h" #include "score/mw/com/impl/methods/skeleton_method_base.h" +#include "score/mw/com/impl/reference_to_moveable.h" #include "score/mw/com/impl/skeleton_binding.h" #include "score/mw/com/impl/mocking/i_skeleton_base.h" @@ -26,7 +27,6 @@ #include #include -#include #include #include #include @@ -44,9 +44,9 @@ class SkeletonMethodBase; class SkeletonBase { public: - using SkeletonEvents = std::map>; - using SkeletonFields = std::map>; - using SkeletonMethods = std::map>; + using SkeletonEvents = std::map>>; + using SkeletonFields = std::map>>; + using SkeletonMethods = std::map>>; /// \brief Creation of service skeleton with provided Skeleton binding /// @@ -93,8 +93,8 @@ class SkeletonBase /// \brief A Skeleton shall be movable /// \requirement SWS_CM_00135 - SkeletonBase(SkeletonBase&& other) noexcept; - SkeletonBase& operator=(SkeletonBase&& other) noexcept; + SkeletonBase(SkeletonBase&& other) noexcept = default; + SkeletonBase& operator=(SkeletonBase&& other) noexcept = default; private: std::unique_ptr binding_; @@ -108,8 +108,6 @@ class SkeletonBase [[nodiscard]] score::Result OfferServiceEvents() const noexcept; [[nodiscard]] score::Result OfferServiceFields() const noexcept; - void UpdateAllServiceElementReferences() noexcept; - FlagOwner service_offered_flag_; }; @@ -122,17 +120,11 @@ class SkeletonBaseView [[nodiscard]] SkeletonBinding* GetBinding() const; - void RegisterEvent(std::string_view event_name, SkeletonEventBase& event); - - void RegisterField(std::string_view field_name, SkeletonFieldBase& field); - - void RegisterMethod(std::string_view method_name, SkeletonMethodBase& method); - - void UpdateEvent(std::string_view event_name, SkeletonEventBase& event) noexcept; + void RegisterEvent(std::string_view event_name, ReferenceToMoveable& event); - void UpdateField(std::string_view field_name, SkeletonFieldBase& field) noexcept; + void RegisterField(std::string_view field_name, ReferenceToMoveable& field); - void UpdateMethod(std::string_view method_name, SkeletonMethodBase& method) noexcept; + void RegisterMethod(std::string_view method_name, ReferenceToMoveable& method); [[nodiscard]] const SkeletonBase::SkeletonEvents& GetEvents() const& noexcept; diff --git a/score/mw/com/impl/skeleton_base_test.cpp b/score/mw/com/impl/skeleton_base_test.cpp index 377d63ee0..305ba933e 100644 --- a/score/mw/com/impl/skeleton_base_test.cpp +++ b/score/mw/com/impl/skeleton_base_test.cpp @@ -17,6 +17,7 @@ #include "score/mw/com/impl/com_error.h" #include "score/mw/com/impl/configuration/test/configuration_store.h" #include "score/mw/com/impl/methods/skeleton_method_base.h" +#include "score/mw/com/impl/reference_to_moveable_owner.h" #include "score/mw/com/impl/service_discovery_mock.h" #include "score/mw/com/impl/skeleton_event.h" #include "score/mw/com/impl/skeleton_event_base.h" @@ -660,8 +661,6 @@ class DummyField : public SkeletonFieldBase public: using SkeletonFieldBase::SkeletonFieldBase; - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {} - bool IsInitialValueSaved() const noexcept override { return false; @@ -706,6 +705,8 @@ class SkeletonBaseServiceElementReferencesFixture : public ::testing::Test SkeletonEventBase event_0_{skeleton_, event_name_0_, std::make_unique()}; SkeletonEventBase event_1_{skeleton_, event_name_1_, std::make_unique()}; + ReferenceToMoveableOwner event_0_reference_wrapper_{event_0_}; + ReferenceToMoveableOwner event_1_reference_wrapper_{event_1_}; std::unique_ptr field_event_dispatch_0_{ std::make_unique(skeleton_, @@ -718,9 +719,13 @@ class SkeletonBaseServiceElementReferencesFixture : public ::testing::Test DummyField field_0_{skeleton_, field_name_0_, std::move(field_event_dispatch_0_)}; DummyField field_1_{skeleton_, field_name_0_, std::move(field_event_dispatch_0_)}; + ReferenceToMoveableOwner field_0_reference_wrapper_{field_0_}; + ReferenceToMoveableOwner field_1_reference_wrapper_{field_1_}; SkeletonMethodBase method_0_{skeleton_, method_name_0_, std::make_unique()}; SkeletonMethodBase method_1_{skeleton_, method_name_1_, std::make_unique()}; + ReferenceToMoveableOwner method_0_reference_wrapper_{method_0_}; + ReferenceToMoveableOwner method_1_reference_wrapper_{method_1_}; }; TEST_F(SkeletonBaseServiceElementReferencesFixture, RegisteringServiceElementStoresReferenceInMap) @@ -728,39 +733,39 @@ TEST_F(SkeletonBaseServiceElementReferencesFixture, RegisteringServiceElementSto // Given a valid MySkeleton object // When registering 2 Events, Fields and Methods - SkeletonBaseView{skeleton_}.RegisterEvent(event_name_0_, event_0_); - SkeletonBaseView{skeleton_}.RegisterEvent(event_name_1_, event_1_); - SkeletonBaseView{skeleton_}.RegisterField(field_name_0_, field_0_); - SkeletonBaseView{skeleton_}.RegisterField(field_name_1_, field_1_); - SkeletonBaseView{skeleton_}.RegisterMethod(method_name_0_, method_0_); - SkeletonBaseView{skeleton_}.RegisterMethod(method_name_1_, method_1_); + SkeletonBaseView{skeleton_}.RegisterEvent(event_name_0_, event_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterEvent(event_name_1_, event_1_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterField(field_name_0_, field_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterField(field_name_1_, field_1_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterMethod(method_name_0_, method_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterMethod(method_name_1_, method_1_reference_wrapper_.Get()); // Then the skeleton's reference maps should contain references to the registered elements const auto& events = skeleton_.GetEvents(); EXPECT_EQ(events.size(), 2U); - EXPECT_EQ(&events.at(event_name_0_).get(), &event_0_); - EXPECT_EQ(&events.at(event_name_1_).get(), &event_1_); + EXPECT_EQ(&events.at(event_name_0_).get().Get(), &event_0_); + EXPECT_EQ(&events.at(event_name_1_).get().Get(), &event_1_); const auto& fields = skeleton_.GetFields(); EXPECT_EQ(fields.size(), 2U); - EXPECT_EQ(&fields.at(field_name_0_).get(), &field_0_); - EXPECT_EQ(&fields.at(field_name_1_).get(), &field_1_); + EXPECT_EQ(&fields.at(field_name_0_).get().Get(), &field_0_); + EXPECT_EQ(&fields.at(field_name_1_).get().Get(), &field_1_); const auto& methods = skeleton_.GetMethods(); EXPECT_EQ(methods.size(), 2U); - EXPECT_EQ(&methods.at(method_name_0_).get(), &method_0_); - EXPECT_EQ(&methods.at(method_name_1_).get(), &method_1_); + EXPECT_EQ(&methods.at(method_name_0_).get().Get(), &method_0_); + EXPECT_EQ(&methods.at(method_name_1_).get().Get(), &method_1_); } TEST_F(SkeletonBaseServiceElementReferencesFixture, MoveConstructingUpdatesReferencesToServiceElements) { // Given a valid MySkeleton object on which 2 Events, Fields and Methods were registered - SkeletonBaseView{skeleton_}.RegisterEvent(event_name_0_, event_0_); - SkeletonBaseView{skeleton_}.RegisterEvent(event_name_1_, event_1_); - SkeletonBaseView{skeleton_}.RegisterField(field_name_0_, field_0_); - SkeletonBaseView{skeleton_}.RegisterField(field_name_1_, field_1_); - SkeletonBaseView{skeleton_}.RegisterMethod(method_name_0_, method_0_); - SkeletonBaseView{skeleton_}.RegisterMethod(method_name_1_, method_1_); + SkeletonBaseView{skeleton_}.RegisterEvent(event_name_0_, event_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterEvent(event_name_1_, event_1_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterField(field_name_0_, field_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterField(field_name_1_, field_1_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterMethod(method_name_0_, method_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterMethod(method_name_1_, method_1_reference_wrapper_.Get()); // When move constructing a new MySkeleton object MySkeleton moved_to_skeleton{std::move(skeleton_)}; @@ -768,18 +773,18 @@ TEST_F(SkeletonBaseServiceElementReferencesFixture, MoveConstructingUpdatesRefer // Then the moved-to skeleton's reference maps should still contain references to the registered elements const auto& events = moved_to_skeleton.GetEvents(); ASSERT_EQ(events.size(), 2U); - EXPECT_EQ(&events.at(event_name_0_).get(), &event_0_); - EXPECT_EQ(&events.at(event_name_1_).get(), &event_1_); + EXPECT_EQ(&events.at(event_name_0_).get().Get(), &event_0_); + EXPECT_EQ(&events.at(event_name_1_).get().Get(), &event_1_); const auto& fields = moved_to_skeleton.GetFields(); ASSERT_EQ(fields.size(), 2U); - EXPECT_EQ(&fields.at(field_name_0_).get(), &field_0_); - EXPECT_EQ(&fields.at(field_name_1_).get(), &field_1_); + EXPECT_EQ(&fields.at(field_name_0_).get().Get(), &field_0_); + EXPECT_EQ(&fields.at(field_name_1_).get().Get(), &field_1_); const auto& methods = moved_to_skeleton.GetMethods(); EXPECT_EQ(methods.size(), 2U); - EXPECT_EQ(&methods.at(method_name_0_).get(), &method_0_); - EXPECT_EQ(&methods.at(method_name_1_).get(), &method_1_); + EXPECT_EQ(&methods.at(method_name_0_).get().Get(), &method_0_); + EXPECT_EQ(&methods.at(method_name_1_).get().Get(), &method_1_); } TEST_F(SkeletonBaseServiceElementReferencesFixture, MoveAssigningUpdatesReferencesToServiceElements) @@ -797,28 +802,31 @@ TEST_F(SkeletonBaseServiceElementReferencesFixture, MoveAssigningUpdatesReferenc mock_binding::Skeleton skeleton_binding_mock{}; // Given a valid MySkeleton object on which 2 Events, Fields and Methods were registered - SkeletonBaseView{skeleton_}.RegisterEvent(event_name_0_, event_0_); - SkeletonBaseView{skeleton_}.RegisterField(field_name_0_, field_0_); - SkeletonBaseView{skeleton_}.RegisterMethod(method_name_0_, method_0_); - SkeletonBaseView{skeleton_}.RegisterEvent(event_name_1_, event_1_); - SkeletonBaseView{skeleton_}.RegisterField(field_name_1_, field_1_); - SkeletonBaseView{skeleton_}.RegisterMethod(method_name_1_, method_1_); + SkeletonBaseView{skeleton_}.RegisterEvent(event_name_0_, event_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterEvent(event_name_1_, event_1_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterField(field_name_0_, field_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterField(field_name_1_, field_1_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterMethod(method_name_0_, method_0_reference_wrapper_.Get()); + SkeletonBaseView{skeleton_}.RegisterMethod(method_name_1_, method_1_reference_wrapper_.Get()); // and given a second valid MySkeleton object MySkeleton skeleton_2{std::make_unique(skeleton_binding_mock), instance_identifier_}; // and given that an Event, Field and Method were registered on the second skeleton SkeletonEventBase event{skeleton_2, other_event_name, std::make_unique()}; + ReferenceToMoveableOwner event_reference_wrapper{event}; auto field_event_dispatch = std::make_unique( skeleton_2, other_field_name, std::make_unique()); DummyField field{skeleton_2, other_field_name, std::move(field_event_dispatch)}; + ReferenceToMoveableOwner field_reference_wrapper{field}; SkeletonMethodBase method{skeleton_2, other_method_name, std::make_unique()}; - SkeletonBaseView{skeleton_2}.RegisterEvent(other_event_name, event); - SkeletonBaseView{skeleton_2}.RegisterField(other_field_name, field); - SkeletonBaseView{skeleton_2}.RegisterMethod(other_method_name, method); + ReferenceToMoveableOwner method_reference_wrapper{method}; + SkeletonBaseView{skeleton_2}.RegisterEvent(other_event_name, event_reference_wrapper.Get()); + SkeletonBaseView{skeleton_2}.RegisterField(other_field_name, field_reference_wrapper.Get()); + SkeletonBaseView{skeleton_2}.RegisterMethod(other_method_name, method_reference_wrapper.Get()); // When move assigning the first MySkeleton object to the second skeleton_2 = std::move(skeleton_); @@ -826,29 +834,31 @@ TEST_F(SkeletonBaseServiceElementReferencesFixture, MoveAssigningUpdatesReferenc // Then the second skeleton's reference maps should contain references to the first skeleton's registered elements const auto& events = skeleton_2.GetEvents(); ASSERT_EQ(events.size(), 2U); - EXPECT_EQ(&events.at(event_name_0_).get(), &event_0_); - EXPECT_EQ(&events.at(event_name_1_).get(), &event_1_); + EXPECT_EQ(&events.at(event_name_0_).get().Get(), &event_0_); + EXPECT_EQ(&events.at(event_name_1_).get().Get(), &event_1_); const auto& fields = skeleton_2.GetFields(); ASSERT_EQ(fields.size(), 2U); - EXPECT_EQ(&fields.at(field_name_0_).get(), &field_0_); - EXPECT_EQ(&fields.at(field_name_1_).get(), &field_1_); + EXPECT_EQ(&fields.at(field_name_0_).get().Get(), &field_0_); + EXPECT_EQ(&fields.at(field_name_1_).get().Get(), &field_1_); const auto& methods = skeleton_2.GetMethods(); EXPECT_EQ(methods.size(), 2U); - EXPECT_EQ(&methods.at(method_name_0_).get(), &method_0_); - EXPECT_EQ(&methods.at(method_name_1_).get(), &method_1_); + EXPECT_EQ(&methods.at(method_name_0_).get().Get(), &method_0_); + EXPECT_EQ(&methods.at(method_name_1_).get().Get(), &method_1_); } TEST_F(SkeletonBaseServiceElementReferencesFixture, MoveAssigningToItselfDoesNotDoAnything) { mock_binding::Skeleton skeleton_binding_mock{}; + // Given a valid MySkeleton object MySkeleton skeleton_2{std::make_unique(skeleton_binding_mock), instance_identifier_}; - // When move assigning the MySkeleton object to itself - auto other_name_same_skeleton_p = &skeleton_2; + // When move assigning the MySkeleton object to itself + auto* other_name_same_skeleton_p = &skeleton_2; skeleton_2 = std::move(*other_name_same_skeleton_p); + // Then nothing happens. // In case of self assignement we would want to know that actually nothing happens and no sideffects occur. // Abscence of sideeffects is not possible to test for. This test only validates that the self assignement branchcan diff --git a/score/mw/com/impl/skeleton_event.h b/score/mw/com/impl/skeleton_event.h index ac50d016a..db8c6af4a 100644 --- a/score/mw/com/impl/skeleton_event.h +++ b/score/mw/com/impl/skeleton_event.h @@ -89,8 +89,8 @@ class SkeletonEvent : public SkeletonEventBase SkeletonEvent(const SkeletonEvent&) = delete; SkeletonEvent& operator=(const SkeletonEvent&) & = delete; - SkeletonEvent(SkeletonEvent&& other) noexcept; - SkeletonEvent& operator=(SkeletonEvent&& other) & noexcept; + SkeletonEvent(SkeletonEvent&& other) noexcept = default; + SkeletonEvent& operator=(SkeletonEvent&& other) & noexcept = default; /** * \api @@ -141,8 +141,7 @@ SkeletonEvent::SkeletonEvent(SkeletonBase& skeleton_base, const event_name)}, skeleton_event_mock_{nullptr} { - SkeletonBaseView base_skeleton_view{skeleton_base}; - base_skeleton_view.RegisterEvent(event_name, *this); + SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, reference_to_moveable_owner_.Get()); if (binding_ != nullptr) { @@ -181,40 +180,6 @@ SkeletonEvent::SkeletonEvent(SkeletonBase& skeleton_base, { } -template -SkeletonEvent::SkeletonEvent(SkeletonEvent&& other) noexcept - : SkeletonEventBase(std::move(static_cast(other))), - skeleton_event_mock_{std::move(other.skeleton_event_mock_)} -{ - // Since the address of this event has changed, we need update the address stored in the parent skeleton. - SkeletonBaseView base_skeleton_view{skeleton_base_.get()}; - base_skeleton_view.UpdateEvent(event_name_, *this); - - other.skeleton_event_mock_ = nullptr; -} - -template -// Suppress "AUTOSAR C++14 A6-2-1" rule violation. The rule states "Move and copy assignment operators shall either move -// or respectively copy base classes and data members of a class, without any side effects." -// Rationale: The parent skeleton stores a reference to the SkeletonEvent. The address that is pointed to must be -// updated when the SkeletonEvent is moved. Therefore, side effects are required. -// coverity[autosar_cpp14_a6_2_1_violation] -auto SkeletonEvent::operator=(SkeletonEvent&& other) & noexcept -> SkeletonEvent& -{ - if (this != &other) - { - SkeletonEventBase::operator=(std::move(other)); - - // Since the address of this event has changed, we need update the address stored in the parent skeleton. - SkeletonBaseView base_skeleton_view{skeleton_base_.get()}; - base_skeleton_view.UpdateEvent(event_name_, *this); - - skeleton_event_mock_ = std::move(other.skeleton_event_mock_); - other.skeleton_event_mock_ = nullptr; - } - return *this; -} - template Result SkeletonEvent::Send(const EventType& sample_value) noexcept { diff --git a/score/mw/com/impl/skeleton_event_base.h b/score/mw/com/impl/skeleton_event_base.h index 128277e94..68b8d9bf2 100644 --- a/score/mw/com/impl/skeleton_event_base.h +++ b/score/mw/com/impl/skeleton_event_base.h @@ -14,6 +14,7 @@ #define SCORE_MW_COM_IMPL_SKELETON_EVENT_BASE_H #include "score/mw/com/impl/flag_owner.h" +#include "score/mw/com/impl/reference_to_moveable_owner.h" #include "score/mw/com/impl/skeleton_event_binding.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing.h" #include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h" @@ -46,20 +47,15 @@ class SkeletonEventBase const std::string_view event_name, std::unique_ptr binding) : binding_{std::move(binding)}, - skeleton_base_{skeleton_base}, event_name_{event_name}, tracing_data_{}, - service_offered_flag_{} + service_offered_flag_{}, + reference_to_moveable_owner_(*static_cast(this)) { } virtual ~SkeletonEventBase() = default; - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept - { - skeleton_base_ = skeleton_base; - } - /// \brief Used to indicate that the event shall be available to consumer /// Performs binding independent functionality and then dispatches to the binding score::Result PrepareOffer() noexcept @@ -87,27 +83,52 @@ class SkeletonEventBase SkeletonEventBase(const SkeletonEventBase&) = delete; SkeletonEventBase& operator=(const SkeletonEventBase&) & = delete; - SkeletonEventBase(SkeletonEventBase&&) noexcept = default; - SkeletonEventBase& operator=(SkeletonEventBase&& other) & noexcept = default; + SkeletonEventBase(SkeletonEventBase&& other) noexcept + : binding_{std::move(other.binding_)}, + event_name_{other.event_name_}, + tracing_data_{std::move(other.tracing_data_)}, + service_offered_flag_{std::move(other.service_offered_flag_)}, + reference_to_moveable_owner_{std::move(other.reference_to_moveable_owner_)} + { + reference_to_moveable_owner_.Update(*this); + } + + SkeletonEventBase& operator=(SkeletonEventBase&& other) & noexcept + { + if (this == &other) + { + return *this; + } + + binding_ = std::move(other.binding_); + event_name_ = other.event_name_; + tracing_data_ = std::move(other.tracing_data_); + service_offered_flag_ = std::move(other.service_offered_flag_); + reference_to_moveable_owner_ = std::move(other.reference_to_moveable_owner_); + + reference_to_moveable_owner_.Update(*this); + + return *this; + } // Suppress "AUTOSAR C++14 M11-0-1" rule findings. This rule states: "Member data in non-POD class types shall // be private.". We need these data elements to exchange this information between the SkeletonEventBase and the // SkeletonEvent. // coverity[autosar_cpp14_m11_0_1_violation] std::unique_ptr binding_; - - // The SkeletonEventBase must contain a reference to the SkeletonBase so that a SkeletonBase can call - // UpdateSkeletonReference whenever it is moved to a new address. A SkeletonBase only has a reference to a - // SkeletonEventBase, not a typed SkeletonEvent, which is why UpdateSkeletonReference has to be in this class - // despite skeleton_base_ being used in the derived class, SkeletonEvent. - // coverity[autosar_cpp14_m11_0_1_violation] - std::reference_wrapper skeleton_base_; // coverity[autosar_cpp14_m11_0_1_violation] std::string_view event_name_; // coverity[autosar_cpp14_m11_0_1_violation] tracing::SkeletonEventTracingData tracing_data_; // coverity[autosar_cpp14_m11_0_1_violation] FlagOwner service_offered_flag_; + + /// \brief Helper class for creating reference to this SkeletonEventBase which is provided to SkeletonBase when + /// registering this event. + /// + /// Contains a heap allocated reference to this SkeletonEventBase which is updated in the move constructor and move + /// assignment operator so that it's always valid, even after moving the SkeletonEventBase. + ReferenceToMoveableOwner reference_to_moveable_owner_; }; class SkeletonEventBaseView diff --git a/score/mw/com/impl/skeleton_event_test.cpp b/score/mw/com/impl/skeleton_event_test.cpp index 45aa2f81c..16a806eba 100644 --- a/score/mw/com/impl/skeleton_event_test.cpp +++ b/score/mw/com/impl/skeleton_event_test.cpp @@ -574,7 +574,7 @@ TEST(SkeletonEventTest, SkeletonEventsRegisterThemselvesWithSkeleton) EXPECT_EQ(event_name, kEventName); // and the event in the map corresponds to the correct skeleton event address - EXPECT_EQ(&event, &unit.my_dummy_event_); + EXPECT_EQ(&event.Get(), &unit.my_dummy_event_); } TEST(SkeletonEventTest, MovingConstructingSkeletonUpdatesEventMapReference) @@ -606,7 +606,7 @@ TEST(SkeletonEventTest, MovingConstructingSkeletonUpdatesEventMapReference) EXPECT_EQ(event_name, kEventName); // and the event in the map corresponds to the new skeleton event address - EXPECT_EQ(&event, &unit2.my_dummy_event_); + EXPECT_EQ(&event.Get(), &unit2.my_dummy_event_); } TEST(SkeletonEventTest, MovingAssigningSkeletonUpdatesEventMapReference) @@ -650,7 +650,7 @@ TEST(SkeletonEventTest, MovingAssigningSkeletonUpdatesEventMapReference) EXPECT_EQ(event_name, kEventName); // and the event in the map corresponds to the new skeleton event address - EXPECT_EQ(&event, &unit2.my_dummy_event_); + EXPECT_EQ(&event.Get(), &unit2.my_dummy_event_); } } // namespace diff --git a/score/mw/com/impl/skeleton_field.h b/score/mw/com/impl/skeleton_field.h index f9cd07006..bd4328380 100644 --- a/score/mw/com/impl/skeleton_field.h +++ b/score/mw/com/impl/skeleton_field.h @@ -72,8 +72,8 @@ class SkeletonField : public SkeletonFieldBase SkeletonField(const SkeletonField&) = delete; SkeletonField& operator=(const SkeletonField&) & = delete; - SkeletonField(SkeletonField&& other) noexcept; - SkeletonField& operator=(SkeletonField&& other) & noexcept; + SkeletonField(SkeletonField&& other) noexcept = default; + SkeletonField& operator=(SkeletonField&& other) & noexcept = default; /** * \api @@ -147,27 +147,6 @@ class SkeletonField : public SkeletonFieldBase return set_method_->RegisterHandler(std::move(wrapped_callback)); } - /// \brief Updates the reference to SkeletonBase held by this SkeletonField and also the owned methods. - /// - /// This is necessary when a Skeleton (which owns its events, fields and methods) is moved to a new address. When - /// this happens, the references to the SkeletonBase are pointing to the old address and must be updated. This must - /// be done also for the get and set method since they call a SkeletonMethod constructor which does not register - /// them with the SkeletonBase. Rather, they're considered as part of the SkeletonField and it's the field's - /// responsibility to update their SkeletonBase reference when it's moved. - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override - { - skeleton_base_ = skeleton_base; - - if (set_method_ != nullptr) - { - set_method_->UpdateSkeletonReference(skeleton_base); - } - if (get_method_ != nullptr) - { - get_method_->UpdateSkeletonReference(skeleton_base); - } - } - private: using SetMethodSignature = FieldType(FieldType); using GetMethodSignature = FieldType(); @@ -291,44 +270,7 @@ SkeletonField::SkeletonField( get_method_{std::move(skeleton_get_method_dispatch)} { SkeletonBaseView skeleton_base_view{parent}; - skeleton_base_view.RegisterField(field_name, *this); -} - -template -SkeletonField::SkeletonField(SkeletonField&& other) noexcept - : SkeletonFieldBase(static_cast(other)), - // known llvm bug (https://github.com/llvm/llvm-project/issues/63202) - // This usage is safe because the previous line only moves the base class portion via static_cast. - // The derived class member 'initial_field_value_' remains untouched by the base class move constructor, - // so it's still valid to access it here for moving into our own member. - // coverity[autosar_cpp14_a12_8_3_violation] This is a false-positive. - initial_field_value_{std::move(other.initial_field_value_)}, - skeleton_field_mock_{other.skeleton_field_mock_}, - is_set_handler_registered_{other.is_set_handler_registered_}, - set_method_{std::move(other.set_method_)}, - get_method_{std::move(other.get_method_)} -{ - SkeletonBaseView skeleton_base_view{skeleton_base_.get()}; - skeleton_base_view.UpdateField(field_name_, *this); -} - -template -auto SkeletonField::operator=(SkeletonField&& other) & noexcept - -> SkeletonField& -{ - if (this != &other) - { - SkeletonFieldBase::operator=(std::move(other)); - - initial_field_value_ = std::move(other.initial_field_value_); - skeleton_field_mock_ = std::move(other.skeleton_field_mock_); - is_set_handler_registered_ = other.is_set_handler_registered_; - set_method_ = std::move(other.set_method_); - get_method_ = std::move(other.get_method_); - SkeletonBaseView skeleton_base_view{skeleton_base_.get()}; - skeleton_base_view.UpdateField(field_name_, *this); - } - return *this; + skeleton_base_view.RegisterField(field_name, reference_to_moveable_owner_.Get()); } /// \brief FieldType is allocated by the user and provided to the middleware to send. Dispatches to diff --git a/score/mw/com/impl/skeleton_field_base.h b/score/mw/com/impl/skeleton_field_base.h index 55eb98d67..bf0a558f1 100644 --- a/score/mw/com/impl/skeleton_field_base.h +++ b/score/mw/com/impl/skeleton_field_base.h @@ -15,6 +15,7 @@ #include "score/mw/com/impl/com_error.h" #include "score/mw/com/impl/methods/skeleton_method_base.h" +#include "score/mw/com/impl/reference_to_moveable_owner.h" #include "score/mw/com/impl/skeleton_event_base.h" #include "score/mw/log/logging.h" @@ -23,6 +24,7 @@ #include #include #include +#include namespace score::mw::com::impl { @@ -43,8 +45,8 @@ class SkeletonFieldBase std::unique_ptr skeleton_event_base) : skeleton_event_dispatch_{std::move(skeleton_event_base)}, was_prepare_offer_called_{false}, - skeleton_base_{skeleton_base}, - field_name_{field_name} + field_name_{field_name}, + reference_to_moveable_owner_{*this} { } @@ -53,12 +55,6 @@ class SkeletonFieldBase SkeletonFieldBase(const SkeletonFieldBase&) = delete; SkeletonFieldBase& operator=(const SkeletonFieldBase&) & = delete; - /// \brief Updates the reference to SkeletonBase held by the SkeletonField and also the owned methods. - /// - /// This must happen in the derived class since the derived class owns the methods (this is required since they are - /// templated with the FieldType, which SkeletonFieldBase doesn't know). - virtual void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept = 0; - /// \brief Used to indicate that the field shall be available to consumer (e.g. binding specific preparation) Result PrepareOffer() noexcept { @@ -106,8 +102,29 @@ class SkeletonFieldBase } protected: - SkeletonFieldBase(SkeletonFieldBase&&) noexcept = default; - SkeletonFieldBase& operator=(SkeletonFieldBase&&) & noexcept = default; + SkeletonFieldBase(SkeletonFieldBase&& other) noexcept + : skeleton_event_dispatch_{std::move(other.skeleton_event_dispatch_)}, + was_prepare_offer_called_{other.was_prepare_offer_called_}, + field_name_{other.field_name_}, + reference_to_moveable_owner_{std::move(other.reference_to_moveable_owner_)} + { + reference_to_moveable_owner_.Update(*this); + } + + SkeletonFieldBase& operator=(SkeletonFieldBase&& other) & noexcept + { + if (this == &other) + { + return *this; + } + + skeleton_event_dispatch_ = std::move(other.skeleton_event_dispatch_); + was_prepare_offer_called_ = other.was_prepare_offer_called_; + field_name_ = other.field_name_; + reference_to_moveable_owner_ = std::move(other.reference_to_moveable_owner_); + reference_to_moveable_owner_.Update(*this); + return *this; + } // Suppress "AUTOSAR C++14 M11-0-1" rule findings. This rule states: "Member data in non-POD class types shall // be private.". We need these data elements to exchange this information between the SkeletonBase and the // SkeletonField. @@ -116,15 +133,16 @@ class SkeletonFieldBase // coverity[autosar_cpp14_m11_0_1_violation] bool was_prepare_offer_called_; - // The SkeletonFieldBase must contain a reference to the SkeletonBase so that a SkeletonBase can call - // UpdateSkeletonReference whenever it is moved to a new address. A SkeletonBase only has a reference to a - // SkeletonFieldBase, not a typed SkeletonField, which is why UpdateSkeletonReference has to be in this class - // despite skeleton_base_ being used in the derived class, SkeletonField. - // coverity[autosar_cpp14_m11_0_1_violation] - std::reference_wrapper skeleton_base_; // coverity[autosar_cpp14_m11_0_1_violation] std::string_view field_name_; + /// \brief Helper class for creating reference to this SkeletonFieldBase which is provided to SkeletonBase when + /// registering this event. + /// + /// Contains a heap allocated reference to this SkeletonFieldBase which is updated in the move constructor and move + /// assignment operator so that it's always valid, even after moving the SkeletonFieldBase. + ReferenceToMoveableOwner reference_to_moveable_owner_; + private: /// \brief Returns whether the initial value has been saved by the user to be used by DoDeferredUpdate [[nodiscard]] virtual bool IsInitialValueSaved() const noexcept = 0; diff --git a/score/mw/com/impl/skeleton_field_base_test.cpp b/score/mw/com/impl/skeleton_field_base_test.cpp index a141d7e36..7e0808d06 100644 --- a/score/mw/com/impl/skeleton_field_base_test.cpp +++ b/score/mw/com/impl/skeleton_field_base_test.cpp @@ -64,8 +64,6 @@ class MyDummyField : public SkeletonFieldBase { } - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {} - StrictMock* GetMockEventBinding() noexcept { auto* const skeleton_field_base_binding = SkeletonFieldBaseView{*this}.GetEventBinding(); @@ -97,8 +95,6 @@ class MyDummyField : public SkeletonFieldBase class MyDummyFieldFailingDeferredUpdate final : public MyDummyField { public: - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {} - Result DoDeferredUpdate() noexcept override { return MakeUnexpected(ComErrc::kCommunicationLinkError); diff --git a/score/mw/com/impl/skeleton_field_test.cpp b/score/mw/com/impl/skeleton_field_test.cpp index 1255f5e12..236dd5955 100644 --- a/score/mw/com/impl/skeleton_field_test.cpp +++ b/score/mw/com/impl/skeleton_field_test.cpp @@ -747,7 +747,7 @@ TEST_F(SkeletonFieldTestFixture, SkeletonFieldsRegisterThemselvesWithSkeleton) ASSERT_EQ(fields.size(), 1); const auto field_name = fields.begin()->first; - const auto& field = fields.begin()->second.get(); + const auto& field = fields.begin()->second.get().Get(); // the name corresponds to the correct field name EXPECT_EQ(field_name, kFieldName); @@ -770,7 +770,7 @@ TEST_F(SkeletonFieldTestFixture, MovingConstructingSkeletonUpdatesFieldMapRefere ASSERT_EQ(fields.size(), 1); const auto field_name = fields.begin()->first; - const auto& field = fields.begin()->second.get(); + const auto& field = fields.begin()->second.get().Get(); // the name corresponds to the correct field name EXPECT_EQ(field_name, kFieldName); @@ -805,7 +805,7 @@ TEST_F(SkeletonFieldTestFixture, MovingAssigningSkeletonUpdatesFieldMapReference ASSERT_EQ(fields.size(), 1); const auto field_name = fields.begin()->first; - const auto& field = fields.begin()->second.get(); + const auto& field = fields.begin()->second.get().Get(); // the name corresponds to the correct field name EXPECT_EQ(field_name, kFieldName); @@ -814,24 +814,6 @@ TEST_F(SkeletonFieldTestFixture, MovingAssigningSkeletonUpdatesFieldMapReference EXPECT_EQ(&field, &unit2.my_dummy_field_); } -using SkeletonFieldDeathTest = SkeletonFieldTestFixture; -TEST_F(SkeletonFieldDeathTest, UpdateWithInvalidFieldNameTriggersTermination) -{ - // Given a skeleton created based on a Lola binding - MyDummySkeleton unit{std::make_unique(), kInstanceIdWithLolaBinding}; - - // Expect that the field map stored in the skeleton - const auto& fields = SkeletonBaseView{unit}.GetFields(); - - // contains a single field - ASSERT_EQ(fields.size(), 1); - - auto& field = fields.begin()->second.get(); - - // Then the program terminates as expected due to the invalid field name - EXPECT_DEATH(SkeletonBaseView{unit}.UpdateField("non_existing_test_field_name", field);, ".*"); -} - // Helper skeleton that holds an EnableSet=true field (setter-capable field) class MySetterSkeleton : public SkeletonBase { @@ -1223,12 +1205,8 @@ using SkeletonFieldMoveConstructionFixture = SkeletonFieldTestFixture; TEST_F(SkeletonFieldMoveConstructionFixture, SecondRegisterSetHandlerReplacesHandler) { // Note. This test verifies that moving a skeleton does not break the getter / setter methods stored within a field. - // When moving, UpdateSkeletonReference must update the references to SkeletonBase in the field instance as well as - // the stored methods. However, the SkeletonBase reference in the methods are only used when moving the method (to - // update the reference to the method in SkeletonBase). Since the methods are stored in the field as unique_ptrs, - // they will never actually be moved. Therefore, with the current implementation, the SkeletonBase reference in the - // getter and setter are never used and so we have no way of ensuring that they are updated correctly. We can only - // verify that the method is still valid after move construction. + // The getter and setter methods are owned by the field via unique_ptr, so they remain valid across skeleton moves. + // This test verifies usability after move construction. // Given a skeleton containing a field with a setter enabled MySetterSkeleton unit{std::make_unique(), kInstanceIdWithLolaBinding}; diff --git a/score/mw/com/impl/tracing/BUILD b/score/mw/com/impl/tracing/BUILD index 4b55adcac..8840a6976 100644 --- a/score/mw/com/impl/tracing/BUILD +++ b/score/mw/com/impl/tracing/BUILD @@ -116,6 +116,7 @@ cc_library( deps = [ ":tracing_runtime", "//score/mw/com/impl:instance_identifier", + "//score/mw/com/impl:moveable_reference", "//score/mw/com/impl:runtime", "//score/mw/com/impl:skeleton_binding", "//score/mw/com/impl:skeleton_event_base", diff --git a/score/mw/com/impl/tracing/skeleton_tracing.cpp b/score/mw/com/impl/tracing/skeleton_tracing.cpp index ca61d2e4a..4e78e249f 100644 --- a/score/mw/com/impl/tracing/skeleton_tracing.cpp +++ b/score/mw/com/impl/tracing/skeleton_tracing.cpp @@ -16,10 +16,10 @@ /// #include "score/mw/com/impl/tracing/skeleton_tracing.h" +#include "score/mw/com/impl/reference_to_moveable.h" #include "score/mw/com/impl/runtime.h" #include "score/mw/com/impl/skeleton_event_base.h" #include "score/mw/com/impl/tracing/configuration/service_element_instance_identifier_view.h" -#include "score/mw/com/impl/tracing/tracing_runtime.h" #include @@ -35,11 +35,11 @@ bool IsTracingEnabledForInterfaceEvent(const SkeletonEventTracingData& skeleton_ } bool IsTracingEnabledForAnyEvent( - const std::map>& events) noexcept + const std::map>>& events) noexcept { for (const auto& event : events) { - auto& skeleton_event_base = event.second.get(); + auto& skeleton_event_base = event.second.get().Get(); auto skeleton_event_base_view = SkeletonEventBaseView(skeleton_event_base); const auto& skeleton_event_tracing = skeleton_event_base_view.GetSkeletonEventTracing(); const bool tracing_enabled = IsTracingEnabledForInterfaceEvent(skeleton_event_tracing); @@ -52,11 +52,11 @@ bool IsTracingEnabledForAnyEvent( } bool IsTracingEnabledForAnyField( - const std::map>& fields) noexcept + const std::map>>& fields) noexcept { for (const auto& field : fields) { - auto& skeleton_field_base = field.second.get(); + auto& skeleton_field_base = field.second.get().Get(); auto skeleton_field_base_view = SkeletonFieldBaseView(skeleton_field_base); auto skeleton_event_base_view = SkeletonEventBaseView(skeleton_field_base_view.GetEventBase()); const auto& skeleton_event_tracing = skeleton_event_base_view.GetSkeletonEventTracing(); @@ -71,8 +71,8 @@ bool IsTracingEnabledForAnyField( bool IsTracingEnabledForInstance( ITracingRuntime* const tracing_runtime, - const std::map>& events, - const std::map>& fields) noexcept + const std::map>>& events, + const std::map>>& fields) noexcept { if (tracing_runtime == nullptr) { @@ -92,8 +92,8 @@ bool IsTracingEnabledForInstance( std::optional CreateRegisterShmObjectCallback( const InstanceIdentifier& instance_id, - const std::map>& events, - const std::map>& fields, + const std::map>>& events, + const std::map>>& fields, const SkeletonBinding& skeleton_binding) noexcept { std::optional tracing_handler{}; @@ -125,8 +125,8 @@ std::optional CreateRegisterShm std::optional CreateUnregisterShmObjectCallback( const InstanceIdentifier& instance_id, - const std::map>& events, - const std::map>& fields, + const std::map>>& events, + const std::map>>& fields, const SkeletonBinding& skeleton_binding) noexcept { std::optional tracing_handler{}; diff --git a/score/mw/com/impl/tracing/skeleton_tracing.h b/score/mw/com/impl/tracing/skeleton_tracing.h index bb8456f1a..a674ce2eb 100644 --- a/score/mw/com/impl/tracing/skeleton_tracing.h +++ b/score/mw/com/impl/tracing/skeleton_tracing.h @@ -19,10 +19,12 @@ #define SCORE_MW_COM_IMPL_SKELETON_TRACING_H #include "score/mw/com/impl/instance_identifier.h" +#include "score/mw/com/impl/reference_to_moveable.h" #include "score/mw/com/impl/skeleton_binding.h" #include "score/mw/com/impl/skeleton_event_base.h" #include "score/mw/com/impl/skeleton_field_base.h" +#include #include #include @@ -31,13 +33,13 @@ namespace score::mw::com::impl::tracing std::optional CreateRegisterShmObjectCallback( const InstanceIdentifier& instance_id, - const std::map>& events, - const std::map>& fields, + const std::map>>& events, + const std::map>>& fields, const SkeletonBinding& skeleton_binding) noexcept; std::optional CreateUnregisterShmObjectCallback( const InstanceIdentifier& instance_id, - const std::map>& events, - const std::map>& fields, + const std::map>>& events, + const std::map>>& fields, const SkeletonBinding& skeleton_binding) noexcept; } // namespace score::mw::com::impl::tracing diff --git a/score/mw/com/impl/tracing/skeleton_tracing_test.cpp b/score/mw/com/impl/tracing/skeleton_tracing_test.cpp index e987fc35f..3d35bdc9c 100644 --- a/score/mw/com/impl/tracing/skeleton_tracing_test.cpp +++ b/score/mw/com/impl/tracing/skeleton_tracing_test.cpp @@ -16,6 +16,8 @@ #include "score/mw/com/impl/bindings/mock_binding/skeleton.h" #include "score/mw/com/impl/bindings/mock_binding/skeleton_event.h" #include "score/mw/com/impl/configuration/test/configuration_store.h" +#include "score/mw/com/impl/reference_to_moveable.h" +#include "score/mw/com/impl/reference_to_moveable_owner.h" #include "score/mw/com/impl/service_element_type.h" #include "score/mw/com/impl/skeleton_base.h" #include "score/mw/com/impl/skeleton_event_base.h" @@ -67,8 +69,6 @@ class MyDummyField : public SkeletonFieldBase { } - void UpdateSkeletonReference(SkeletonBase& skeleton_base) noexcept override {} - bool IsInitialValueSaved() const noexcept override { return true; @@ -98,8 +98,8 @@ class SkeletonTracingFixture : public ::testing::Test kEventName, std::make_unique>()}, skeleton_field_base_{empty_skeleton_, kFieldName, std::make_unique()}, - events_map_{{kEventName, skeleton_event_base_}}, - fields_map_{{kFieldName, skeleton_field_base_}} + events_map_{{kEventName, skeleton_event_base_ref_.Get()}}, + fields_map_{{kFieldName, skeleton_field_base_ref_.Get()}} { ON_CALL(mock_skeleton_binding_, GetBindingType).WillByDefault(Return(BindingType::kFake)); ON_CALL(runtime_mock_guard_.runtime_mock_, GetTracingRuntime()).WillByDefault(Return(&tracing_runtime_mock_)); @@ -128,10 +128,12 @@ class SkeletonTracingFixture : public ::testing::Test SkeletonBase empty_skeleton_; mock_binding::Skeleton& mock_skeleton_binding_; SkeletonEventBase skeleton_event_base_; + ReferenceToMoveableOwner skeleton_event_base_ref_{skeleton_event_base_}; MyDummyField skeleton_field_base_; + ReferenceToMoveableOwner skeleton_field_base_ref_{skeleton_field_base_}; - std::map> events_map_; - std::map> fields_map_; + std::map>> events_map_; + std::map>> fields_map_; }; using SkeletonTracingCreateRegisterShmCallbackFixture = SkeletonTracingFixture; From 4c777716747dbd468bfb5cb9ac25377f4e39297c Mon Sep 17 00:00:00 2001 From: Brendan Emery Date: Wed, 29 Apr 2026 12:01:21 +0200 Subject: [PATCH 4/7] mw/com: Add helpers for checking method handler signatures --- score/mw/com/impl/methods/BUILD | 55 ++ score/mw/com/impl/methods/callable_traits.cpp | 13 + score/mw/com/impl/methods/callable_traits.h | 79 +++ .../com/impl/methods/callable_traits_impl.h | 72 +++ .../com/impl/methods/callable_traits_test.cpp | 243 ++++++++ .../impl/methods/method_traits_checker.cpp | 13 + .../com/impl/methods/method_traits_checker.h | 206 +++++++ .../methods/method_traits_checker_test.cpp | 550 ++++++++++++++++++ score/mw/com/impl/methods/test/BUILD | 24 + .../test/callable_traits_resources.cpp | 13 + .../methods/test/callable_traits_resources.h | 214 +++++++ 11 files changed, 1482 insertions(+) create mode 100644 score/mw/com/impl/methods/callable_traits.cpp create mode 100644 score/mw/com/impl/methods/callable_traits.h create mode 100644 score/mw/com/impl/methods/callable_traits_impl.h create mode 100644 score/mw/com/impl/methods/callable_traits_test.cpp create mode 100644 score/mw/com/impl/methods/method_traits_checker.cpp create mode 100644 score/mw/com/impl/methods/method_traits_checker.h create mode 100644 score/mw/com/impl/methods/method_traits_checker_test.cpp create mode 100644 score/mw/com/impl/methods/test/BUILD create mode 100644 score/mw/com/impl/methods/test/callable_traits_resources.cpp create mode 100644 score/mw/com/impl/methods/test/callable_traits_resources.h diff --git a/score/mw/com/impl/methods/BUILD b/score/mw/com/impl/methods/BUILD index 5215e6ab7..da27dc61c 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"], @@ -187,6 +241,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..9235467ea --- /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 AssertCallableMatchesMethodSignature 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 AssertCallableMatchesMethodSignature() +{ + 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 AssertCallableMatchesMethodSignature 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 " + "AssertCallableMatchesMethodSignature 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..abf6f80cb --- /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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature 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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED((AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the call does not terminate + AssertCallableMatchesMethodSignature(); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertCallableMatchesMethodSignature())); +} + +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 AssertCallableMatchesMethodSignature + // Then the function terminates with a contract violation + SCORE_LANGUAGE_FUTURECPP_EXPECT_CONTRACT_VIOLATED( + (AssertCallableMatchesMethodSignature())); +} + +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/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..63f17b091 --- /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 constructs a default-initialised 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