diff --git a/score/mw/com/test/common_test_resources/BUILD b/score/mw/com/test/common_test_resources/BUILD index 36a4c82ff..c07585e4c 100644 --- a/score/mw/com/test/common_test_resources/BUILD +++ b/score/mw/com/test/common_test_resources/BUILD @@ -24,6 +24,8 @@ cc_library( deps = [ "//score/mw/com", "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:proxy_event_receiver", + "//score/mw/com/test/common_test_resources:proxy_event_state_change_notifier", ], ) @@ -314,6 +316,19 @@ cc_library( ], ) +cc_library( + name = "proxy_event_consumer", + srcs = ["proxy_event_consumer.cpp"], + hdrs = ["proxy_event_consumer.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/mw/com/test:__subpackages__"], + deps = [ + ":fail_test", + "//score/mw/com:types", + "@score_baselibs//score/language/futurecpp", + ], +) + cc_library( name = "generic_trace_api_test_resources", srcs = ["generic_trace_api_test_resources.cpp"], diff --git a/score/mw/com/test/common_test_resources/proxy_container.h b/score/mw/com/test/common_test_resources/proxy_container.h index 62665117c..f38e9e620 100644 --- a/score/mw/com/test/common_test_resources/proxy_container.h +++ b/score/mw/com/test/common_test_resources/proxy_container.h @@ -14,6 +14,8 @@ #define SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_PROXY_CONTAINER_H #include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/proxy_event_receiver.h" +#include "score/mw/com/test/common_test_resources/proxy_event_state_change_notifier.h" #include "score/mw/com/types.h" #include @@ -22,6 +24,7 @@ #include #include #include +#include namespace score::mw::com::test { @@ -39,6 +42,13 @@ class ProxyContainer return *proxy_; } + Proxy&& Extract() + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(proxy_ != nullptr, + "Proxy was not successfully created! Cannot extract it!"); + return std::move(*proxy_); + } + private: std::unique_ptr handle_{nullptr}; std::mutex proxy_creation_mutex_{}; diff --git a/score/mw/com/test/common_test_resources/proxy_event_consumer.cpp b/score/mw/com/test/common_test_resources/proxy_event_consumer.cpp new file mode 100644 index 000000000..f45d008ed --- /dev/null +++ b/score/mw/com/test/common_test_resources/proxy_event_consumer.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/common_test_resources/proxy_event_consumer.h" diff --git a/score/mw/com/test/common_test_resources/proxy_event_consumer.h b/score/mw/com/test/common_test_resources/proxy_event_consumer.h new file mode 100644 index 000000000..7ca454383 --- /dev/null +++ b/score/mw/com/test/common_test_resources/proxy_event_consumer.h @@ -0,0 +1,109 @@ +/******************************************************************************** + * 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_COMMON_TEST_RESOURCES_PROXY_EVENT_CONSUMER_H +#define SCORE_MW_COM_TEST_COMMON_TEST_RESOURCES_PROXY_EVENT_CONSUMER_H + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +inline auto MakeSampleSequenceCallback(std::optional& latest_value, const char* failure_message_prefix) +{ + return [&latest_value, failure_message_prefix](SamplePtr sample) { + if (sample == nullptr) + { + FailTest(failure_message_prefix, " received null sample"); + } + // After a reset latest_value is empty, so the next sample is accepted as the new baseline. + const std::uint32_t expected_value = latest_value.has_value() ? latest_value.value() + 1U : *sample; + if (*sample != expected_value) + { + FailTest( + failure_message_prefix, " received value ", *sample, " does not match expected value ", expected_value); + } + latest_value = *sample; + }; +} + +/// \brief Waits until subscribed, receives the expected number of samples, and notifies the provider once - repeated +/// for the given number of iterations. +template +void ReceiveAndNotify(ProxyEventReceiverType& proxy_event_receiver, + ProxyStateChangeNotifierType& proxy_state_change_notifier, + ProcessSynchronizerType& process_synchronizer, + const std::size_t num_samples_to_receive, + const std::size_t num_iterations, + const score::cpp::stop_token& stop_token) +{ + for (std::size_t iteration = 0U; iteration < num_iterations; ++iteration) + { + std::cout << "\nConsumer: Iteration " << (iteration + 1U) << " of " << num_iterations << std::endl; + const bool subscribed = + proxy_state_change_notifier.WaitForStateChange(stop_token, SubscriptionState::kSubscribed); + if (!subscribed) + { + FailTest("proxy_event_move_semantics consumer failed: WaitForStateChange was interrupted by stop_token"); + } + + const bool received = proxy_event_receiver.WaitForSamples(stop_token, num_samples_to_receive); + if (!received) + { + FailTest("proxy_event_move_semantics consumer failed: WaitForSamples was interrupted by stop_token"); + } + process_synchronizer.Notify(); + } +} + +/// \brief Coordinates a proxy re-subscription across a provider re-offer. Waits until the provider has withdrawn its +/// offer (kSubscriptionPending), unsubscribes and subscribes again while the service is withdrawn, then notifies the +/// provider so that it re-offers. This ordering guarantees that the samples received afterwards come from the fresh +/// offer instead of being stale buffered samples from the previous offer. +template +void ResubscribeAcrossReoffer(ProxyEventType& proxy_event, + ProxyStateChangeNotifierType& proxy_state_change_notifier, + ProcessSynchronizerType& process_synchronizer, + const std::size_t num_samples_to_receive, + const score::cpp::stop_token& stop_token) +{ + std::cout << "\nConsumer: Waiting for provider to withdraw its offer" << std::endl; + const bool withdrawn = + proxy_state_change_notifier.WaitForStateChange(stop_token, SubscriptionState::kSubscriptionPending); + if (!withdrawn) + { + FailTest("proxy_event_move_semantics consumer failed: WaitForStateChange was interrupted by stop_token"); + } + + std::cout << "Consumer: Unsubscribe and subscribe again" << std::endl; + proxy_event.Unsubscribe(); + const auto subscribe_result = proxy_event.Subscribe(num_samples_to_receive); + if (!subscribe_result.has_value()) + { + FailTest("proxy_event_move_semantics consumer failed: Re-subscribe failed: ", subscribe_result.error()); + } + + // Tell the provider we have re-subscribed so that it can re-offer the service. + process_synchronizer.Notify(); +} + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_COMMON_TEST_RESOURCES_PROXY_EVENT_CONSUMER_H diff --git a/score/mw/com/test/move_semantics/proxy_event/BUILD b/score/mw/com/test/move_semantics/proxy_event/BUILD new file mode 100644 index 000000000..33003cff6 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/BUILD @@ -0,0 +1,168 @@ +# ******************************************************************************* +# 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", + "//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/common_test_resources:skeleton_container", + "//score/mw/com/test/methods/methods_test_resources:process_synchronizer", + ], +) + +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/common_test_resources:proxy_container", + "//score/mw/com/test/common_test_resources:proxy_event_consumer", + "//score/mw/com/test/methods/methods_test_resources:process_synchronizer", + ], +) + +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/move_semantics/proxy_event:__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/move_semantics/proxy_event:__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/move_semantics/proxy_event:__subpackages__", + ], +) diff --git a/score/mw/com/test/move_semantics/proxy_event/config/logging.json b/score/mw/com/test/move_semantics/proxy_event/config/logging.json new file mode 100644 index 000000000..b60fbd04b --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/config/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "PRMS", + "appDesc": "proxy_event_move_semantics", + "logLevel": "kDebug", + "logLevelThresholdConsole": "kDebug", + "logMode": "kRemote|kConsole" +} diff --git a/score/mw/com/test/move_semantics/proxy_event/config/mw_com_config.json b/score/mw/com/test/move_semantics/proxy_event/config/mw_com_config.json new file mode 100644 index 000000000..3ed674989 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/config/mw_com_config.json @@ -0,0 +1,105 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/move_semantics/proxy_event/MoveEventInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 2110, + "events": [ + { + "eventName": "moved_event", + "eventId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "test/move_semantics/proxy_event/MoveEventInterfaceMovedTo", + "serviceTypeName": "/test/move_semantics/proxy_event/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/move_semantics/proxy_event/MoveEventInterfaceMovedFrom", + "serviceTypeName": "/test/move_semantics/proxy_event/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": 4011 + } +} diff --git a/score/mw/com/test/move_semantics/proxy_event/consumer.cpp b/score/mw/com/test/move_semantics/proxy_event/consumer.cpp new file mode 100644 index 000000000..a3cb05364 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/consumer.cpp @@ -0,0 +1,384 @@ +/******************************************************************************** + * 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/move_semantics/proxy_event/consumer.h" + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/proxy_container.h" +#include "score/mw/com/test/common_test_resources/proxy_event_consumer.h" +#include "score/mw/com/test/common_test_resources/proxy_event_state_change_notifier.h" +#include "score/mw/com/test/methods/methods_test_resources/process_synchronizer.h" +#include "score/mw/com/test/move_semantics/proxy_event/test_event_datatype.h" +#include "score/mw/com/types.h" + +#include +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::string kInterprocessNotificationShmPath{"/proxy_event_move_semantics_interprocess_notification"}; + +void RunConsumerMoveConstructProxyBeforeSubscribe(ProcessSynchronizer& process_synchronizer, + const std::size_t num_samples_to_receive, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token) +{ + 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(kInstanceSpecifierMovedTo, "proxy_event_move_semantics"); + + // Step 2. Move construct proxy before subscribe + std::cout << "\nConsumer: Step 2 - Move construct proxy before subscribe" << std::endl; + auto moved_proxy = proxy_container.Extract(); + + // Step 3. Register receive handler + std::cout << "\nConsumer: Step 3 - Register receive handler" << std::endl; + std::optional latest_value{0U}; + ProxyEventReceiver proxy_event_receiver{ + moved_proxy.moved_event_, + MakeSampleSequenceCallback(latest_value, "proxy_event_move_semantics consumer failed:")}; + + // Step 4. Register state change handler + std::cout << "\nConsumer: Step 4 - Register state change handler" << std::endl; + ProxyEventStateChangeNotifier proxy_state_change_notifier{moved_proxy.moved_event_}; + + // Step 5. Subscribe + std::cout << "\nConsumer: Step 5 - Subscribe" << std::endl; + auto subscribe_result = moved_proxy.moved_event_.Subscribe(num_samples_to_receive); + if (!subscribe_result.has_value()) + { + FailTest("proxy_event_move_semantics consumer failed: Subscribe failed: ", subscribe_result.error()); + } + + // Step 6. Wait for provider to send the first batch of values and notify + std::cout << "\nConsumer: Step 6 - Receive first batch of samples" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + num_send_iterations - 1U, + stop_token); + + // Step 7. Unsubscribe and subscribe again across the provider's re-offer + std::cout << "\nConsumer: Step 7 - Unsubscribe and subscribe again" << std::endl; + ResubscribeAcrossReoffer(moved_proxy.moved_event_, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + stop_token); + // A fresh subscription replays the buffered samples, so accept the next sample as the new baseline. + latest_value.reset(); + + // Step 8. Receive the remaining batch after re-subscribing + std::cout << "\nConsumer: Step 8 - Receive samples again after re-subscribing" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + 1U, + stop_token); + + std::cout << "Consumer: Done with all iterations, exiting" << std::endl; +} + +void RunConsumerMoveConstructProxyWhileSubscribed(ProcessSynchronizer& process_synchronizer, + const std::size_t num_samples_to_receive, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token) +{ + ProxyContainer proxy_container{}; + + // Step 1. Find service and create original proxy + std::cout << "\nConsumer: Step 1 - Find service and create original proxy" << std::endl; + proxy_container.CreateProxy(kInstanceSpecifierMovedTo, "proxy_event_move_semantics"); + auto& original_proxy = proxy_container.GetProxy(); + + // Step 2. Subscribe on original proxy + std::cout << "\nConsumer: Step 2 - Subscribe on original proxy" << std::endl; + auto subscribe_result = original_proxy.moved_event_.Subscribe(num_samples_to_receive); + if (!subscribe_result.has_value()) + { + FailTest("proxy_event_move_semantics consumer failed: Subscribe failed: ", subscribe_result.error()); + } + + // Step 3. Register handlers on original proxy and receive first batch + std::cout << "\nConsumer: Step 3 - Register handlers on original proxy" << std::endl; + { + std::optional latest_value{0U}; + ProxyEventReceiver proxy_event_receiver{ + original_proxy.moved_event_, + MakeSampleSequenceCallback(latest_value, "proxy_event_move_semantics consumer failed:")}; + ProxyEventStateChangeNotifier proxy_state_change_notifier{original_proxy.moved_event_}; + + std::cout << "\nConsumer: Step 4 - Receive first batch of samples" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + 1U, + stop_token); + } + + // Step 5. Move construct while subscribed + std::cout << "\nConsumer: Step 5 - Move construct while subscribed" << std::endl; + auto moved_proxy = proxy_container.Extract(); + + // Step 6. Register handlers on moved-to proxy and continue receiving + std::cout << "\nConsumer: Step 6 - Register handlers on moved-to proxy" << std::endl; + std::optional latest_value{}; + ProxyEventReceiver proxy_event_receiver{ + moved_proxy.moved_event_, + MakeSampleSequenceCallback(latest_value, "proxy_event_move_semantics consumer failed:")}; + ProxyEventStateChangeNotifier proxy_state_change_notifier{moved_proxy.moved_event_}; + + // Step 7. Continue receiving on the moved-to proxy while still offered + std::cout << "\nConsumer: Step 7 - Continue receiving on moved-to proxy" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + num_send_iterations - 2U, + stop_token); + + // Step 8. Unsubscribe and subscribe again across the provider's re-offer + std::cout << "\nConsumer: Step 8 - Unsubscribe and subscribe again" << std::endl; + ResubscribeAcrossReoffer(moved_proxy.moved_event_, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + stop_token); + // A fresh subscription replays the buffered samples, so accept the next sample as the new baseline. + latest_value.reset(); + + // Step 9. Receive the remaining batch after re-subscribing + std::cout << "\nConsumer: Step 9 - Receive samples again after re-subscribing" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + 1U, + stop_token); + + std::cout << "Consumer: Done with all iterations, exiting" << std::endl; +} + +void RunConsumerMoveAssignProxyBeforeSubscribe(ProcessSynchronizer& process_synchronizer, + const std::size_t num_samples_to_receive, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token) +{ + ProxyContainer moved_from_proxy_container{}; + ProxyContainer moved_to_proxy_container{}; + + // Step 1. Create original proxy and proxy + std::cout << "\nConsumer: Step 1 - Find service and create original proxy" << std::endl; + moved_from_proxy_container.CreateProxy(kInstanceSpecifierMovedTo, "proxy_event_move_semantics"); + std::cout << "\nConsumer: Step 2 - Find service and create proxy" << std::endl; + moved_to_proxy_container.CreateProxy(kInstanceSpecifierMovedFrom, "proxy_event_move_semantics"); + + auto moved_from_proxy = moved_from_proxy_container.Extract(); + auto moved_to_proxy = moved_to_proxy_container.Extract(); + + // Step 3. Move assign proxy = move(original proxy) before subscribe + std::cout << "\nConsumer: Step 3 - Move assign proxy = move(original proxy) before subscribe" << std::endl; + moved_to_proxy = std::move(moved_from_proxy); + + // Step 4. Register handlers and subscribe on proxy + std::cout << "\nConsumer: Step 4 - Register handlers on proxy" << std::endl; + std::optional latest_value{0U}; + ProxyEventReceiver proxy_event_receiver{ + moved_to_proxy.moved_event_, + MakeSampleSequenceCallback(latest_value, "proxy_event_move_semantics consumer failed:")}; + ProxyEventStateChangeNotifier proxy_state_change_notifier{moved_to_proxy.moved_event_}; + + std::cout << "\nConsumer: Step 5 - Subscribe on proxy" << std::endl; + auto subscribe_result = moved_to_proxy.moved_event_.Subscribe(num_samples_to_receive); + if (!subscribe_result.has_value()) + { + FailTest("proxy_event_move_semantics consumer failed: Subscribe failed: ", subscribe_result.error()); + } + + // Step 6. Wait for provider to send the first batch of values and notify + std::cout << "\nConsumer: Step 6 - Receive first batch of samples" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + num_send_iterations - 1U, + stop_token); + + // Step 7. Unsubscribe and subscribe again across the provider's re-offer + std::cout << "\nConsumer: Step 7 - Unsubscribe and subscribe again" << std::endl; + ResubscribeAcrossReoffer(moved_to_proxy.moved_event_, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + stop_token); + // A fresh subscription replays the buffered samples, so accept the next sample as the new baseline. + latest_value.reset(); + + // Step 8. Receive the remaining batch after re-subscribing + std::cout << "\nConsumer: Step 8 - Receive samples again after re-subscribing" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + 1U, + stop_token); + + std::cout << "Consumer: Done with all iterations, exiting" << std::endl; +} + +void RunConsumerMoveAssignProxyWhileSubscribed(ProcessSynchronizer& process_synchronizer, + const std::size_t num_samples_to_receive, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token) +{ + ProxyContainer active_proxy_container{}; + ProxyContainer passive_proxy_container{}; + + // Step 1. Create two proxies + std::cout << "\nConsumer: Step 1 - Find service and create active proxy" << std::endl; + active_proxy_container.CreateProxy(kInstanceSpecifierMovedTo, "proxy_event_move_semantics"); + std::cout << "\nConsumer: Step 2 - Find service and create passive proxy" << std::endl; + passive_proxy_container.CreateProxy(kInstanceSpecifierMovedFrom, "proxy_event_move_semantics"); + + auto active_proxy = active_proxy_container.Extract(); + auto passive_proxy = passive_proxy_container.Extract(); + + // Step 3. Subscribe active proxy and receive first batch + std::cout << "\nConsumer: Step 3 - Subscribe active proxy" << std::endl; + auto subscribe_result = active_proxy.moved_event_.Subscribe(num_samples_to_receive); + if (!subscribe_result.has_value()) + { + FailTest("proxy_event_move_semantics consumer failed: Subscribe failed: ", subscribe_result.error()); + } + + { + std::optional latest_value{0U}; + ProxyEventReceiver proxy_event_receiver{ + active_proxy.moved_event_, + MakeSampleSequenceCallback(latest_value, "proxy_event_move_semantics consumer failed:")}; + ProxyEventStateChangeNotifier proxy_state_change_notifier{active_proxy.moved_event_}; + + std::cout << "\nConsumer: Step 4 - Receive first batch on active proxy" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + 1U, + stop_token); + } + + // Step 5. Move assign while active + std::cout << "\nConsumer: Step 5 - Move assign while active" << std::endl; + passive_proxy = std::move(active_proxy); + + // Step 6. Continue receiving on the moved-to proxy while still offered + std::cout << "\nConsumer: Step 6 - Continue receiving on moved-to proxy" << std::endl; + std::optional latest_value{}; + ProxyEventReceiver proxy_event_receiver{ + passive_proxy.moved_event_, + MakeSampleSequenceCallback(latest_value, "proxy_event_move_semantics consumer failed:")}; + ProxyEventStateChangeNotifier proxy_state_change_notifier{passive_proxy.moved_event_}; + + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + num_send_iterations - 2U, + stop_token); + + // Step 7. Unsubscribe and subscribe again across the provider's re-offer + std::cout << "\nConsumer: Step 7 - Unsubscribe and subscribe again" << std::endl; + ResubscribeAcrossReoffer(passive_proxy.moved_event_, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + stop_token); + // A fresh subscription replays the buffered samples, so accept the next sample as the new baseline. + latest_value.reset(); + + // Step 8. Receive the remaining batch after re-subscribing + std::cout << "\nConsumer: Step 8 - Receive samples again after re-subscribing" << std::endl; + ReceiveAndNotify(proxy_event_receiver, + proxy_state_change_notifier, + process_synchronizer, + num_samples_to_receive, + 1U, + stop_token); + + std::cout << "Consumer: Done with all iterations, exiting" << std::endl; +} + +} // namespace + +void RunConsumer(const ProxyMoveScenario& scenario, + 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("proxy_event_move_semantics consumer failed: could not create ready synchronizer"); + } + + ExitFunctionGuard done_guard{[&process_synchronizer_result]() { + process_synchronizer_result->Notify(); + }}; + + auto& process_synchronizer = *process_synchronizer_result; + + switch (scenario) + { + case ProxyMoveScenario::kMoveConstructBeforeSubscribe: + { + RunConsumerMoveConstructProxyBeforeSubscribe( + process_synchronizer, num_samples_to_receive, num_send_iterations, stop_token); + break; + } + case ProxyMoveScenario::kMoveConstructWhileSubscribed: + { + RunConsumerMoveConstructProxyWhileSubscribed( + process_synchronizer, num_samples_to_receive, num_send_iterations, stop_token); + break; + } + case ProxyMoveScenario::kMoveAssignBeforeSubscribe: + { + RunConsumerMoveAssignProxyBeforeSubscribe( + process_synchronizer, num_samples_to_receive, num_send_iterations, stop_token); + break; + } + case ProxyMoveScenario::kMoveAssignWhileSubscribed: + { + RunConsumerMoveAssignProxyWhileSubscribed( + process_synchronizer, num_samples_to_receive, num_send_iterations, stop_token); + break; + } + case ProxyMoveScenario::kNumberOfScenarios: + [[fallthrough]]; + default: + FailTest("Unknown proxy move scenario in consumer"); + } +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/move_semantics/proxy_event/consumer.h b/score/mw/com/test/move_semantics/proxy_event/consumer.h new file mode 100644 index 000000000..367f98f54 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/consumer.h @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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_PROXY_EVENT_MOVE_SEMANTICS_CONSUMER_H +#define SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_CONSUMER_H + +#include "score/mw/com/test/move_semantics/proxy_event/test_parameters.h" +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +void RunConsumer(const ProxyMoveScenario& scenario, + 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_PROXY_EVENT_MOVE_SEMANTICS_CONSUMER_H diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/BUILD b/score/mw/com/test/move_semantics/proxy_event/integration_test/BUILD new file mode 100644 index 000000000..d72abe543 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/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_before_subscribe_proxy_same_process_test", + srcs = [ + "move_construct_before_subscribe_proxy_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/proxy_event:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_construct_after_subscribe_proxy_same_process_test", + srcs = [ + "move_construct_after_subscribe_proxy_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/proxy_event:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_assign_before_subscribe_proxy_same_process_test", + srcs = [ + "move_assign_before_subscribe_proxy_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/proxy_event:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_assign_while_subscribed_proxy_same_process_test", + srcs = [ + "move_assign_while_subscribed_proxy_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/proxy_event:main_consumer_and_provider-pkg", +) + +pkg_filegroup( + name = "different_processes_filesystem", + srcs = [ + "//score/mw/com/test/move_semantics/proxy_event:main_consumer-pkg", + "//score/mw/com/test/move_semantics/proxy_event:main_provider-pkg", + ], +) + +integration_test( + name = "move_construct_before_subscribe_proxy_different_process_test", + srcs = [ + "move_construct_before_subscribe_proxy_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_construct_after_subscribe_proxy_different_process_test", + srcs = [ + "move_construct_after_subscribe_proxy_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_assign_before_subscribe_proxy_different_process_test", + srcs = [ + "move_assign_before_subscribe_proxy_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_assign_while_subscribed_proxy_different_process_test", + srcs = [ + "move_assign_while_subscribed_proxy_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_before_subscribe_proxy_different_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_before_subscribe_proxy_different_process_test.py new file mode 100644 index 000000000..b3bb4574f --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_before_subscribe_proxy_different_process_test.py @@ -0,0 +1,19 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_assign_before_subscribe_proxy_different_process(target): + with consumer(target, ProxyMoveScenario.MOVE_ASSIGN_BEFORE_SUBSCRIBE): + with provider(target, ProxyMoveScenario.MOVE_ASSIGN_BEFORE_SUBSCRIBE): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_before_subscribe_proxy_same_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_before_subscribe_proxy_same_process_test.py new file mode 100644 index 000000000..c9b2450e8 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_before_subscribe_proxy_same_process_test.py @@ -0,0 +1,18 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_assign_before_subscribe_proxy_same_process(target): + with consumer_and_provider(target, ProxyMoveScenario.MOVE_ASSIGN_BEFORE_SUBSCRIBE): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_while_subscribed_proxy_different_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_while_subscribed_proxy_different_process_test.py new file mode 100644 index 000000000..9fc319d25 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_while_subscribed_proxy_different_process_test.py @@ -0,0 +1,19 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_assign_while_subscribed_proxy_different_process(target): + with consumer(target, ProxyMoveScenario.MOVE_ASSIGN_WHILE_SUBSCRIBED): + with provider(target, ProxyMoveScenario.MOVE_ASSIGN_WHILE_SUBSCRIBED): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_while_subscribed_proxy_same_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_while_subscribed_proxy_same_process_test.py new file mode 100644 index 000000000..83f21efd2 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_assign_while_subscribed_proxy_same_process_test.py @@ -0,0 +1,18 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_assign_while_subscribed_proxy_same_process(target): + with consumer_and_provider(target, ProxyMoveScenario.MOVE_ASSIGN_WHILE_SUBSCRIBED): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_after_subscribe_proxy_different_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_after_subscribe_proxy_different_process_test.py new file mode 100644 index 000000000..9dd0be370 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_after_subscribe_proxy_different_process_test.py @@ -0,0 +1,19 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_construct_after_subscribe_proxy_different_process(target): + with consumer(target, ProxyMoveScenario.MOVE_CONSTRUCT_WHILE_SUBSCRIBED): + with provider(target, ProxyMoveScenario.MOVE_CONSTRUCT_WHILE_SUBSCRIBED): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_after_subscribe_proxy_same_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_after_subscribe_proxy_same_process_test.py new file mode 100644 index 000000000..1838ac333 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_after_subscribe_proxy_same_process_test.py @@ -0,0 +1,18 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_construct_after_subscribe_proxy_same_process(target): + with consumer_and_provider(target, ProxyMoveScenario.MOVE_CONSTRUCT_WHILE_SUBSCRIBED): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_before_subscribe_proxy_different_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_before_subscribe_proxy_different_process_test.py new file mode 100644 index 000000000..3b2281d18 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_before_subscribe_proxy_different_process_test.py @@ -0,0 +1,19 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_construct_before_subscribe_proxy_different_process(target): + with consumer(target, ProxyMoveScenario.MOVE_CONSTRUCT_BEFORE_SUBSCRIBE): + with provider(target, ProxyMoveScenario.MOVE_CONSTRUCT_BEFORE_SUBSCRIBE): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_before_subscribe_proxy_same_process_test.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_before_subscribe_proxy_same_process_test.py new file mode 100644 index 000000000..127f30e69 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/move_construct_before_subscribe_proxy_same_process_test.py @@ -0,0 +1,18 @@ +# ******************************************************************************* +# 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, ProxyMoveScenario + + +def test_move_construct_before_subscribe_proxy_same_process(target): + with consumer_and_provider(target, ProxyMoveScenario.MOVE_CONSTRUCT_BEFORE_SUBSCRIBE): + pass diff --git a/score/mw/com/test/move_semantics/proxy_event/integration_test/test_fixture.py b/score/mw/com/test/move_semantics/proxy_event/integration_test/test_fixture.py new file mode 100644 index 000000000..522bb2b79 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/integration_test/test_fixture.py @@ -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 +# ******************************************************************************* +from enum import IntEnum + + +class ProxyMoveScenario(IntEnum): + MOVE_CONSTRUCT_BEFORE_SUBSCRIBE = 0 + MOVE_CONSTRUCT_WHILE_SUBSCRIBED = 1 + MOVE_ASSIGN_BEFORE_SUBSCRIBE = 2 + MOVE_ASSIGN_WHILE_SUBSCRIBED = 3 + + +def consumer_and_provider(target, scenario, **kwargs): + args = ["--scenario", str(int(scenario)), "--service-instance-manifest", "./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, **kwargs): + args = ["--scenario", str(int(scenario)), "--service-instance-manifest", "./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, **kwargs): + args = ["--scenario", str(int(scenario)), "--service-instance-manifest", "./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/move_semantics/proxy_event/main_consumer.cpp b/score/mw/com/test/move_semantics/proxy_event/main_consumer.cpp new file mode 100644 index 000000000..7b369ed4f --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/main_consumer.cpp @@ -0,0 +1,47 @@ +/******************************************************************************** + * 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/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" +#include "score/mw/com/test/move_semantics/proxy_event/consumer.h" +#include "score/mw/com/test/move_semantics/proxy_event/test_parameters.h" + +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 = score::mw::com::test::GetNumberOfSendIterations(test_configuration.scenario); + + std::cout << "Starting consumer with scenario " << static_cast(test_configuration.scenario) + << ", number of samples to receive per iteration " << score::mw::com::test::kNumberOfSamplesToSendPerOffer + << " and number of send iterations " << num_send_iterations << std::endl; + + score::mw::com::test::RunConsumer(test_configuration.scenario, + score::mw::com::test::kInstanceSpecifierMovedTo, + score::mw::com::test::kNumberOfSamplesToSendPerOffer, + num_send_iterations, + stop_source.get_token()); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/move_semantics/proxy_event/main_consumer_and_provider.cpp b/score/mw/com/test/move_semantics/proxy_event/main_consumer_and_provider.cpp new file mode 100644 index 000000000..070f47b0d --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/main_consumer_and_provider.cpp @@ -0,0 +1,57 @@ +/******************************************************************************** + * 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/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" +#include "score/mw/com/test/move_semantics/proxy_event/consumer.h" +#include "score/mw/com/test/move_semantics/proxy_event/provider.h" +#include "score/mw/com/test/move_semantics/proxy_event/test_parameters.h" + +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 = score::mw::com::test::GetNumberOfSendIterations(test_configuration.scenario); + + std::cout << "Starting provider and consumer with scenario " + << static_cast(test_configuration.scenario) << ", number of samples to send per offer " + << score::mw::com::test::kNumberOfSamplesToSendPerOffer << " and number of send iterations " + << num_send_iterations << std::endl; + + auto provider_future = std::async(score::mw::com::test::RunProvider, + score::mw::com::test::kNumberOfSamplesToSendPerOffer, + num_send_iterations, + stop_source.get_token()); + auto consumer_future = std::async(score::mw::com::test::RunConsumer, + test_configuration.scenario, + score::mw::com::test::kInstanceSpecifierMovedTo, + score::mw::com::test::kNumberOfSamplesToSendPerOffer, + num_send_iterations, + stop_source.get_token()); + + provider_future.get(); + consumer_future.get(); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/move_semantics/proxy_event/main_provider.cpp b/score/mw/com/test/move_semantics/proxy_event/main_provider.cpp new file mode 100644 index 000000000..079afc685 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/main_provider.cpp @@ -0,0 +1,44 @@ +/******************************************************************************** + * 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/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" +#include "score/mw/com/test/move_semantics/proxy_event/provider.h" +#include "score/mw/com/test/move_semantics/proxy_event/test_parameters.h" + +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 = score::mw::com::test::GetNumberOfSendIterations(test_configuration.scenario); + + std::cout << "Starting provider with scenario " << static_cast(test_configuration.scenario) + << ", number of samples to send per offer " << score::mw::com::test::kNumberOfSamplesToSendPerOffer + << " and number of send iterations " << num_send_iterations << std::endl; + + score::mw::com::test::RunProvider( + score::mw::com::test::kNumberOfSamplesToSendPerOffer, num_send_iterations, stop_source.get_token()); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/move_semantics/proxy_event/provider.cpp b/score/mw/com/test/move_semantics/proxy_event/provider.cpp new file mode 100644 index 000000000..689fe6d46 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/provider.cpp @@ -0,0 +1,101 @@ +/******************************************************************************** + * 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/move_semantics/proxy_event/provider.h" + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/skeleton_container.h" +#include "score/mw/com/test/methods/methods_test_resources/process_synchronizer.h" +#include "score/mw/com/test/move_semantics/proxy_event/test_event_datatype.h" + +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::string kInterprocessNotificationShmPath{"/proxy_event_move_semantics_interprocess_notification"}; + +void SendSamples(ProxyMoveSemanticsSkeleton& 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)); + } +} + +} // namespace + +void RunProvider(const std::size_t num_samples_to_send, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token) +{ + const auto moved_to_name = filesystem::Path{kInstanceSpecifierMovedTo.ToString()}.Filename().Native(); + auto process_synchronizer_result = + ProcessSynchronizer::CreateUniquePtr(kInterprocessNotificationShmPath + std::string{moved_to_name}); + + // Step 1. Create and offer the skeleton the consumer receives samples from. A second instance is also offered so + // that the move-assign consumer scenarios can discover and create their second proxy before the move; the + // move-construct scenarios simply ignore it. This keeps the provider generic and independent of the scenario. + std::cout << "\nProvider: Step 1 - Create and offer skeletons" << std::endl; + SkeletonContainer skeleton_container{}; + skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "proxy_event_move_semantics"); + skeleton_container.OfferService("proxy_event_move_semantics"); + + SkeletonContainer moved_from_skeleton_container{}; + moved_from_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedFrom, "proxy_event_move_semantics"); + moved_from_skeleton_container.OfferService("proxy_event_move_semantics"); + + // Step 2. Send one batch of samples per iteration and wait for the consumer to acknowledge each one. Before the + // final batch the offer is withdrawn and re-offered so the consumer can unsubscribe and re-subscribe on a + // withdrawn service (the consumer notifies once it has re-subscribed). + for (std::size_t iteration = 0U; iteration < num_send_iterations; ++iteration) + { + const bool is_final_iteration = (iteration + 1U == num_send_iterations); + if (is_final_iteration) + { + std::cout << "\nProvider: Stop offering so the consumer can unsubscribe and re-subscribe" << std::endl; + skeleton_container.GetSkeleton().StopOfferService(); + + if (!process_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("proxy_event_move_semantics provider failed: waiting for consumer re-subscribe was aborted"); + } + process_synchronizer_result->Reset(); + + std::cout << "\nProvider: Re-offer skeleton" << std::endl; + skeleton_container.OfferService("proxy_event_move_semantics"); + } + + const auto initial_value = static_cast(iteration * num_samples_to_send) + 1U; + std::cout << "\nProvider: Iteration " << (iteration + 1U) << " of " << num_send_iterations << " - Send " + << num_samples_to_send << " samples" << std::endl; + SendSamples(skeleton_container.GetSkeleton(), num_samples_to_send, initial_value); + + if (!process_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("proxy_event_move_semantics provider failed: waiting for consumer done was aborted"); + } + process_synchronizer_result->Reset(); + } +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/move_semantics/proxy_event/provider.h b/score/mw/com/test/move_semantics/proxy_event/provider.h new file mode 100644 index 000000000..90837decc --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/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_PROXY_EVENT_MOVE_SEMANTICS_PROVIDER_H +#define SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_PROVIDER_H + +#include "score/mw/com/test/move_semantics/proxy_event/test_parameters.h" + +#include + +namespace score::mw::com::test +{ + +void RunProvider(const std::size_t num_samples_to_send, + const std::size_t num_send_iterations, + const score::cpp::stop_token& stop_token); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_PROVIDER_H diff --git a/score/mw/com/test/move_semantics/proxy_event/test_event_datatype.cpp b/score/mw/com/test/move_semantics/proxy_event/test_event_datatype.cpp new file mode 100644 index 000000000..acb283fe4 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/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/move_semantics/proxy_event/test_event_datatype.h" diff --git a/score/mw/com/test/move_semantics/proxy_event/test_event_datatype.h b/score/mw/com/test/move_semantics/proxy_event/test_event_datatype.h new file mode 100644 index 000000000..45c2e1fdf --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/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_PROXY_EVENT_MOVE_SEMANTICS_TEST_EVENT_DATATYPE_H +#define SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_TEST_EVENT_DATATYPE_H + +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +template +class ProxyMoveSemanticsInterface : public T::Base +{ + public: + using T::Base::Base; + + typename T::template Event moved_event_{*this, "moved_event"}; +}; + +using ProxyMoveSemanticsProxy = score::mw::com::AsProxy; +using ProxyMoveSemanticsSkeleton = score::mw::com::AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_TEST_EVENT_DATATYPE_H diff --git a/score/mw/com/test/move_semantics/proxy_event/test_parameters.cpp b/score/mw/com/test/move_semantics/proxy_event/test_parameters.cpp new file mode 100644 index 000000000..0fb64a6dd --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/test_parameters.cpp @@ -0,0 +1,99 @@ +/******************************************************************************** + * 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/move_semantics/proxy_event/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, ""}, {kServiceInstanceManifest, ""}}); + + const auto scenario_index = GetValue(args, kScenario); + if (scenario_index >= static_cast(ProxyMoveScenario::kNumberOfScenarios)) + { + FailTest("Consumer: ", + kScenario, + " value ", + scenario_index, + " is out of range. Valid values are between 0 and ", + static_cast(ProxyMoveScenario::kNumberOfScenarios) - 1, + "."); + } + const auto scenario = static_cast(scenario_index); + + auto service_instance_manifest = GetValue(args, kServiceInstanceManifest); + + return {scenario, service_instance_manifest}; +} + +std::size_t GetNumberOfSendIterations(const ProxyMoveScenario scenario) +{ + if (scenario == ProxyMoveScenario::kMoveConstructBeforeSubscribe) + { + // In this scenario, the consumer will: + // - find service and create proxy + // - move construct proxy + // - subscribe on proxy + // - receive the samples + // - unsubscribe and subscribe again + // - receive the samples again + // The receiver therefore expects two receive iterations. + return 2U; + } + else if (scenario == ProxyMoveScenario::kMoveConstructWhileSubscribed) + { + // In this scenario, the consumer will: + // - find service and create proxy + // - create and subscribe proxy + // - receive the samples + // - move construct while subscribed + // - continue receiving the samples on proxy + // - unsubscribe and subscribe again + // - receive the samples again + // The receiver therefore expects three receive iterations. + return 3U; + } + else if (scenario == ProxyMoveScenario::kMoveAssignBeforeSubscribe) + { + // In this scenario, the consumer will: + // - find service and create proxy + // - move assign proxy = move(proxy) before subscribe + // - subscribe on proxy + // - receive the samples + // - unsubscribe and subscribe again + // - receive the samples again + // The receiver therefore expects two receive iterations. + return 2U; + } + else if (scenario == ProxyMoveScenario::kMoveAssignWhileSubscribed) + { + // In this scenario, the consumer will: + // - create two proxies + // - subscribe active proxy and receive samples + // - move assign while active + // - continue receiving the samples + // - unsubscribe and subscribe again + // - receive the samples again + // The receiver therefore expects three receive iterations. + return 3U; + } + + FailTest("Unknown scenario, cannot determine number of send iterations"); + return 0U; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/move_semantics/proxy_event/test_parameters.h b/score/mw/com/test/move_semantics/proxy_event/test_parameters.h new file mode 100644 index 000000000..0cefcd635 --- /dev/null +++ b/score/mw/com/test/move_semantics/proxy_event/test_parameters.h @@ -0,0 +1,54 @@ +/******************************************************************************** + * 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_PROXY_EVENT_MOVE_SEMANTICS_TEST_PARAMETERS_H +#define SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_TEST_PARAMETERS_H + +#include "score/mw/com/types.h" + +#include +#include + +namespace score::mw::com::test +{ + +const std::string kScenario{"scenario"}; +const std::string kServiceInstanceManifest{"service-instance-manifest"}; + +constexpr std::size_t kNumberOfSamplesToSendPerOffer{10U}; + +const InstanceSpecifier kInstanceSpecifierMovedTo = + InstanceSpecifier::Create(std::string{"test/move_semantics/proxy_event/MoveEventInterfaceMovedTo"}).value(); +const InstanceSpecifier kInstanceSpecifierMovedFrom = + InstanceSpecifier::Create(std::string{"test/move_semantics/proxy_event/MoveEventInterfaceMovedFrom"}).value(); + +enum class ProxyMoveScenario : std::uint8_t +{ + kMoveConstructBeforeSubscribe, + kMoveConstructWhileSubscribed, + kMoveAssignBeforeSubscribe, + kMoveAssignWhileSubscribed, + kNumberOfScenarios +}; + +struct CombinedTestConfiguration +{ + ProxyMoveScenario scenario; + std::string service_instance_manifest; +}; + +CombinedTestConfiguration ReadCommandLineArguments(int argc, const char** argv); +std::size_t GetNumberOfSendIterations(ProxyMoveScenario scenario); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PROXY_EVENT_MOVE_SEMANTICS_TEST_PARAMETERS_H