diff --git a/score/mw/com/test/move_semantics/skeleton_field/BUILD b/score/mw/com/test/move_semantics/skeleton_field/BUILD new file mode 100644 index 0000000000..6af6d3ae82 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/BUILD @@ -0,0 +1,172 @@ +# ******************************************************************************* +# 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_field_datatype", + srcs = ["test_field_datatype.cpp"], + hdrs = ["test_field_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_field_datatype", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:process_synchronizer", + "//score/mw/com/test/common_test_resources:skeleton_container", + ], +) + +cc_library( + name = "consumer", + srcs = ["consumer.cpp"], + hdrs = ["consumer.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":test_field_datatype", + ":test_parameters", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:process_synchronizer", + "//score/mw/com/test/common_test_resources:proxy_container", + "//score/mw/com/test/common_test_resources:proxy_event_receiver", + "//score/mw/com/test/common_test_resources:proxy_event_state_change_notifier", + ], +) + +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", + "@score_baselibs//score/string_manipulation/arguments", + ], +) + +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", + "@score_baselibs//score/string_manipulation/arguments", + ], +) + +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", + "@score_baselibs//score/string_manipulation/arguments", + ], +) + +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/skeleton_field:__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/skeleton_field:__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/skeleton_field:__subpackages__", + ], +) diff --git a/score/mw/com/test/move_semantics/skeleton_field/config/logging.json b/score/mw/com/test/move_semantics/skeleton_field/config/logging.json new file mode 100644 index 0000000000..c13cb3992c --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/config/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "SFSM", + "appDesc": "skeleton_field_move_semantics", + "logLevel": "kDebug", + "logLevelThresholdConsole": "kDebug", + "logMode": "kRemote|kConsole" +} diff --git a/score/mw/com/test/move_semantics/skeleton_field/config/mw_com_config.json b/score/mw/com/test/move_semantics/skeleton_field/config/mw_com_config.json new file mode 100644 index 0000000000..1b69654e5b --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/config/mw_com_config.json @@ -0,0 +1,105 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/skeleton_field_move_semantics/MoveFieldInterface", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 2101, + "fields": [ + { + "fieldName": "moved_field", + "fieldId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "test/skeleton_field_move_semantics/MoveFieldInterfaceMovedTo", + "serviceTypeName": "/test/skeleton_field_move_semantics/MoveFieldInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "fields": [ + { + "fieldName": "moved_field", + "numberOfSampleSlots": 4, + "maxSubscribers": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + }, + { + "instanceSpecifier": "test/skeleton_field_move_semantics/MoveFieldInterfaceMovedFrom", + "serviceTypeName": "/test/skeleton_field_move_semantics/MoveFieldInterface", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 2, + "asil-level": "QM", + "binding": "SHM", + "fields": [ + { + "fieldName": "moved_field", + "numberOfSampleSlots": 4, + "maxSubscribers": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM", + "applicationID": 4002 + } +} diff --git a/score/mw/com/test/move_semantics/skeleton_field/consumer.cpp b/score/mw/com/test/move_semantics/skeleton_field/consumer.cpp new file mode 100644 index 0000000000..efd4022429 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/consumer.cpp @@ -0,0 +1,140 @@ +/******************************************************************************** + * 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/skeleton_field/consumer.h" + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/process_synchronizer.h" +#include "score/mw/com/test/common_test_resources/proxy_container.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/test/move_semantics/skeleton_field/test_field_datatype.h" +#include "score/mw/com/test/move_semantics/skeleton_field/test_parameters.h" +#include "score/mw/com/types.h" + +#include +#include +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::string kInterprocessNotificationShmPath{"/skeleton_field_move_semantics_interprocess_notification"}; +const std::string kAboutToCallShmPath{"/skeleton_field_move_semantics_about_to_call"}; + +template +void CallGetAndCheckValue(ProxyFieldType& proxy_field, const std::int32_t expected_value) +{ + const auto get_result = proxy_field.Get(); + if (!get_result.has_value()) + { + FailTest("Consumer: Get() failed: ", get_result.error()); + } + if (*(get_result.value()) != expected_value) + { + FailTest("Consumer: Get() returned ", *(get_result.value()), " but expected ", expected_value); + } + std::cout << "\nConsumer: Get() returned expected value " << expected_value << std::endl; +} + +template +void CallSetAndCheckReturnValue(ProxyFieldType& proxy_field, + const std::int32_t set_request_value, + const std::int32_t expected_accepted_value) +{ + const auto set_result = proxy_field.Set(set_request_value); + if (!set_result.has_value()) + { + FailTest("Consumer: Set() failed: ", set_result.error()); + } + const std::int32_t accepted_value = *(set_result.value()); + if (accepted_value != expected_accepted_value) + { + FailTest("Consumer: Set() returned accepted value ", accepted_value, " but expected ", expected_accepted_value); + } + std::cout << "\nConsumer: Set() returned expected accepted value " << accepted_value << std::endl; +} + +} // namespace + +void RunConsumer(const score::cpp::stop_token& stop_token) +{ + auto done_synchronizer_result = ProcessSynchronizer::Create(kInterprocessNotificationShmPath); + if (!done_synchronizer_result.has_value()) + { + FailTest("Consumer: Could not create done ProcessSynchronizer"); + } + auto about_to_call_synchronizer_result = ProcessSynchronizer::Create(kAboutToCallShmPath); + if (!about_to_call_synchronizer_result.has_value()) + { + FailTest("Consumer: Could not create about-to-call ProcessSynchronizer"); + } + ExitFunctionGuard exit_guard{[&done_synchronizer_result]() { + done_synchronizer_result->Notify(); + }}; + + // Step 1. Find service and create proxy + std::cout << "\nConsumer: Step 1 - Find service and create proxy" << std::endl; + ProxyContainer proxy_container{}; + proxy_container.CreateProxy(kInstanceSpecifierMovedTo, "skeleton_field_move_semantics"); + auto& proxy = proxy_container.GetProxy(); + + // Step 2. Register receive handler and state change handler + std::cout << "\nConsumer: Step 2 - Register receive and state change handlers" << std::endl; + ProxyEventReceiver field_receiver{proxy.moved_field_}; + ProxyEventStateChangeNotifier subscription_notifier{proxy.moved_field_}; + + // Step 3. Subscribe + std::cout << "\nConsumer: Step 3 - Subscribe" << std::endl; + const auto subscribe_result = proxy.moved_field_.Subscribe(kTotalNumValuesToSend); + if (!subscribe_result.has_value()) + { + FailTest("Consumer: Subscribe failed: ", subscribe_result.error()); + } + + // Step 4. Wait for subscription + std::cout << "\nConsumer: Step 4 - Wait for subscription" << std::endl; + if (!subscription_notifier.WaitForStateChange(stop_token, SubscriptionState::kSubscribed)) + { + FailTest("Consumer: Subscription failed"); + } + + // Step 5. Notify provider that the live API call sequence is about to start. For "before offer" scenarios, the + // provider never waits on this notification, so this call is a harmless no-op there. + std::cout << "\nConsumer: Step 5 - Notify provider about to call" << std::endl; + about_to_call_synchronizer_result->Notify(); + + // Step 6. Live sequence: WaitForSamples -> Set -> Get. For "after offer" scenarios, the provider's move races + // this whole sequence with a single random delay, so it may land inside any of the 3 calls. + std::cout << "\nConsumer: Step 6 - Wait for all expected samples" << std::endl; + const std::vector values_to_receive = {kInitialValue, 20, 30, 35}; + if (!field_receiver.WaitForSamples(stop_token, values_to_receive)) + { + FailTest("Consumer: Did not receive all expected samples"); + } + + std::cout << "\nConsumer: Step 6 - Set field value and verify accepted value" << std::endl; + const auto expected_accepted_set_value = (kSetRequestValue * 2) + 1; + CallSetAndCheckReturnValue(proxy.moved_field_, kSetRequestValue, expected_accepted_set_value); + + std::cout << "\nConsumer: Step 6 - Get field value and verify it matches the accepted Set() value" << std::endl; + CallGetAndCheckValue(proxy.moved_field_, expected_accepted_set_value); + + // Step 7. Unsubscribe + std::cout << "\nConsumer: Step 7 - Unsubscribe" << std::endl; + proxy.moved_field_.Unsubscribe(); +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/move_semantics/skeleton_field/consumer.h b/score/mw/com/test/move_semantics/skeleton_field/consumer.h new file mode 100644 index 0000000000..ad8885d562 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/consumer.h @@ -0,0 +1,28 @@ +/******************************************************************************** + * 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_FIELD_MOVE_SEMANTICS_CONSUMER_H +#define SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_CONSUMER_H + +#include + +namespace score::mw::com::test +{ + +/// \brief Runs the (scenario-agnostic) consumer. It always connects to kInstanceSpecifierMovedTo, since regardless +/// of scenario (move-construct/move-assign, before/after offer), the object which ends up being offered under that +/// instance specifier is the one the consumer needs to talk to. +void RunConsumer(const score::cpp::stop_token& stop_token); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_CONSUMER_H diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/BUILD b/score/mw/com/test/move_semantics/skeleton_field/integration_test/BUILD new file mode 100644 index 0000000000..c57dd9b4fe --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/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_offer_same_process_test", + srcs = [ + "move_construct_before_offer_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/skeleton_field:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_construct_after_offer_same_process_test", + srcs = [ + "move_construct_after_offer_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/skeleton_field:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_assign_before_offer_same_process_test", + srcs = [ + "move_assign_before_offer_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/skeleton_field:main_consumer_and_provider-pkg", +) + +integration_test( + name = "move_assign_after_offer_same_process_test", + srcs = [ + "move_assign_after_offer_same_process_test.py", + "test_fixture.py", + ], + filesystem = "//score/mw/com/test/move_semantics/skeleton_field:main_consumer_and_provider-pkg", +) + +pkg_filegroup( + name = "different_processes_filesystem", + srcs = [ + "//score/mw/com/test/move_semantics/skeleton_field:main_consumer-pkg", + "//score/mw/com/test/move_semantics/skeleton_field:main_provider-pkg", + ], +) + +integration_test( + name = "move_construct_before_offer_different_process_test", + srcs = [ + "move_construct_before_offer_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_construct_after_offer_different_process_test", + srcs = [ + "move_construct_after_offer_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_assign_before_offer_different_process_test", + srcs = [ + "move_assign_before_offer_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) + +integration_test( + name = "move_assign_after_offer_different_process_test", + srcs = [ + "move_assign_after_offer_different_process_test.py", + "test_fixture.py", + ], + filesystem = ":different_processes_filesystem", +) diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_after_offer_different_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_after_offer_different_process_test.py new file mode 100644 index 0000000000..96ccb49fea --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_after_offer_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, SkeletonFieldMoveScenario + + +def test_move_assign_after_offer_different_process(target): + with provider(target, SkeletonFieldMoveScenario.MOVE_ASSIGN_AFTER_OFFER): + with consumer(target, SkeletonFieldMoveScenario.MOVE_ASSIGN_AFTER_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_after_offer_same_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_after_offer_same_process_test.py new file mode 100644 index 0000000000..1a30ff9c7f --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_after_offer_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, SkeletonFieldMoveScenario + + +def test_move_assign_after_offer_same_process(target): + with consumer_and_provider(target, SkeletonFieldMoveScenario.MOVE_ASSIGN_AFTER_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_before_offer_different_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_before_offer_different_process_test.py new file mode 100644 index 0000000000..412332f993 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_before_offer_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, SkeletonFieldMoveScenario + + +def test_move_assign_before_offer_different_process(target): + with provider(target, SkeletonFieldMoveScenario.MOVE_ASSIGN_BEFORE_OFFER): + with consumer(target, SkeletonFieldMoveScenario.MOVE_ASSIGN_BEFORE_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_before_offer_same_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_before_offer_same_process_test.py new file mode 100644 index 0000000000..a1c9bcad55 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_assign_before_offer_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, SkeletonFieldMoveScenario + + +def test_move_assign_before_offer_same_process(target): + with consumer_and_provider(target, SkeletonFieldMoveScenario.MOVE_ASSIGN_BEFORE_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_after_offer_different_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_after_offer_different_process_test.py new file mode 100644 index 0000000000..4236597944 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_after_offer_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, SkeletonFieldMoveScenario + + +def test_move_construct_after_offer_different_process(target): + with provider(target, SkeletonFieldMoveScenario.MOVE_CONSTRUCT_AFTER_OFFER): + with consumer(target, SkeletonFieldMoveScenario.MOVE_CONSTRUCT_AFTER_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_after_offer_same_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_after_offer_same_process_test.py new file mode 100644 index 0000000000..9b9fb33448 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_after_offer_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, SkeletonFieldMoveScenario + + +def test_move_construct_after_offer_same_process(target): + with consumer_and_provider(target, SkeletonFieldMoveScenario.MOVE_CONSTRUCT_AFTER_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_before_offer_different_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_before_offer_different_process_test.py new file mode 100644 index 0000000000..6df5f80371 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_before_offer_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, SkeletonFieldMoveScenario + + +def test_move_construct_before_offer_different_process(target): + with provider(target, SkeletonFieldMoveScenario.MOVE_CONSTRUCT_BEFORE_OFFER): + with consumer(target, SkeletonFieldMoveScenario.MOVE_CONSTRUCT_BEFORE_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_before_offer_same_process_test.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_before_offer_same_process_test.py new file mode 100644 index 0000000000..fb4299f724 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/integration_test/move_construct_before_offer_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, SkeletonFieldMoveScenario + + +def test_move_construct_before_offer_same_process(target): + with consumer_and_provider(target, SkeletonFieldMoveScenario.MOVE_CONSTRUCT_BEFORE_OFFER): + pass diff --git a/score/mw/com/test/move_semantics/skeleton_field/integration_test/test_fixture.py b/score/mw/com/test/move_semantics/skeleton_field/integration_test/test_fixture.py new file mode 100644 index 0000000000..49e80bea38 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/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 SkeletonFieldMoveScenario(IntEnum): + MOVE_CONSTRUCT_BEFORE_OFFER = 0 + MOVE_CONSTRUCT_AFTER_OFFER = 1 + MOVE_ASSIGN_BEFORE_OFFER = 2 + MOVE_ASSIGN_AFTER_OFFER = 3 + + +def consumer_and_provider(target, scenario, **kwargs): + args = ["--scenario", str(int(scenario)), "--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, **kwargs): + args = ["--scenario", str(int(scenario)), "--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, **kwargs): + args = ["--scenario", str(int(scenario)), "--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/move_semantics/skeleton_field/main_consumer.cpp b/score/mw/com/test/move_semantics/skeleton_field/main_consumer.cpp new file mode 100644 index 0000000000..c38fb0d0b0 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/main_consumer.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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/skeleton_field/consumer.h" +#include "score/mw/com/test/move_semantics/skeleton_field/test_parameters.h" +#include "score/string_manipulation/arguments/arguments.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(score::string_manipulation::GetArguments(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; + } + + std::cout << "Starting consumer with scenario " << static_cast(test_configuration.scenario) + << std::endl; + + score::mw::com::test::RunConsumer(stop_source.get_token()); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/move_semantics/skeleton_field/main_consumer_and_provider.cpp b/score/mw/com/test/move_semantics/skeleton_field/main_consumer_and_provider.cpp new file mode 100644 index 0000000000..2831e96cc2 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/main_consumer_and_provider.cpp @@ -0,0 +1,49 @@ +/******************************************************************************** + * 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/skeleton_field/consumer.h" +#include "score/mw/com/test/move_semantics/skeleton_field/provider.h" +#include "score/mw/com/test/move_semantics/skeleton_field/test_parameters.h" +#include "score/string_manipulation/arguments/arguments.h" + +#include + +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(score::string_manipulation::GetArguments(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; + } + + std::cout << "Starting provider and consumer with scenario " + << static_cast(test_configuration.scenario) << std::endl; + + auto provider_future = + std::async(score::mw::com::test::RunProvider, test_configuration.scenario, stop_source.get_token()); + auto consumer_future = std::async(score::mw::com::test::RunConsumer, stop_source.get_token()); + + provider_future.get(); + consumer_future.get(); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/move_semantics/skeleton_field/main_provider.cpp b/score/mw/com/test/move_semantics/skeleton_field/main_provider.cpp new file mode 100644 index 0000000000..dee4a893b2 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/main_provider.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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/skeleton_field/provider.h" +#include "score/mw/com/test/move_semantics/skeleton_field/test_parameters.h" +#include "score/string_manipulation/arguments/arguments.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(score::string_manipulation::GetArguments(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; + } + + std::cout << "Starting provider with scenario " << static_cast(test_configuration.scenario) + << std::endl; + + score::mw::com::test::RunProvider(test_configuration.scenario, stop_source.get_token()); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/move_semantics/skeleton_field/provider.cpp b/score/mw/com/test/move_semantics/skeleton_field/provider.cpp new file mode 100644 index 0000000000..380efa5fca --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/provider.cpp @@ -0,0 +1,318 @@ +/******************************************************************************** + * 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/skeleton_field/provider.h" + +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/common_test_resources/process_synchronizer.h" +#include "score/mw/com/test/common_test_resources/skeleton_container.h" +#include "score/mw/com/test/move_semantics/skeleton_field/test_field_datatype.h" + +#include +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::string kInterprocessNotificationShmPath{"/skeleton_field_move_semantics_interprocess_notification"}; +const std::string kAboutToCallShmPath{"/skeleton_field_move_semantics_about_to_call"}; + +void ValueTransformSetHandler(std::int32_t& value) noexcept +{ + value = (value * 2) + 1; +} + +void RegisterSetHandler(SkeletonFieldMoveSemanticsSkeleton& skeleton) +{ + const auto register_handler_result = skeleton.moved_field_.RegisterSetHandler([](std::int32_t& value) noexcept { + ValueTransformSetHandler(value); + }); + if (!register_handler_result.has_value()) + { + FailTest("Provider: Unable to register set handler: ", register_handler_result.error()); + } +} + +void UpdateInitialValue(SkeletonFieldMoveSemanticsSkeleton& skeleton) +{ + const auto update_result = skeleton.moved_field_.Update(kInitialValue); + if (!update_result.has_value()) + { + FailTest("Provider: Unable to update field with initial value: ", update_result.error()); + } +} + +void SendValues(SkeletonFieldMoveSemanticsSkeleton& skeleton) +{ + for (const auto value_to_send : kValuesToSend) + { + const auto update_result = skeleton.moved_field_.Update(value_to_send); + if (!update_result.has_value()) + { + FailTest("Provider: Unable to update field with updated value: ", update_result.error()); + } + } +} + +/// \brief Sleeps a random duration in [0, window] before returning, so that the caller's subsequent action (the +/// move) lands at an unpredictable point during the consumer's live API call sequence. +void SleepRandomRaceDelay(const std::chrono::microseconds& window) +{ + static thread_local std::mt19937 random_engine{std::random_device{}()}; + std::uniform_int_distribution distribution{0, window.count()}; + const auto delay = std::chrono::microseconds{distribution(random_engine)}; + std::this_thread::sleep_for(delay); +} + +void RunMoveConstructBeforeOfferProvider(const score::cpp::stop_token& stop_token) +{ + auto done_synchronizer_result = ProcessSynchronizer::Create(kInterprocessNotificationShmPath); + if (!done_synchronizer_result.has_value()) + { + FailTest("Provider: Could not create done ProcessSynchronizer"); + } + + // Step 1. Create skeleton + std::cout << "\nProvider: Step 1 - Create skeleton" << std::endl; + SkeletonContainer moved_to_skeleton_container{}; + moved_to_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_field_move_semantics"); + + RegisterSetHandler(moved_to_skeleton_container.GetSkeleton()); + UpdateInitialValue(moved_to_skeleton_container.GetSkeleton()); + + // Step 2. Move construct skeleton before offering + std::cout << "\nProvider: Step 2 - Move construct skeleton before offer" << std::endl; + auto moved_skeleton = std::move(moved_to_skeleton_container.GetSkeleton()); + + // Step 3. Offer skeleton + std::cout << "\nProvider: Step 3 - Offer skeleton" << std::endl; + const auto offer_service_result = moved_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 4. Send values + std::cout << "\nProvider: Step 4 - Send updated values" << std::endl; + SendValues(moved_skeleton); + + // Step 5. Wait for consumer to notify it is done with the verification sequence + std::cout << "\nProvider: Step 5 - Wait for consumer done notification" << std::endl; + if (!done_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("Provider: WaitWithAbort (done) was stopped by stop_token instead of notification"); + } + + // Step 6. Stop offering skeleton + std::cout << "\nProvider: Step 6 - Stop offering skeleton" << std::endl; + moved_skeleton.StopOfferService(); +} + +void RunMoveAssignBeforeOfferProvider(const score::cpp::stop_token& stop_token) +{ + auto done_synchronizer_result = ProcessSynchronizer::Create(kInterprocessNotificationShmPath); + if (!done_synchronizer_result.has_value()) + { + FailTest("Provider: Could not create done ProcessSynchronizer"); + } + + // Step 1. Create two skeletons + std::cout << "\nProvider: Step 1 - Create two skeletons" << std::endl; + SkeletonContainer moved_to_skeleton_container{}; + moved_to_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_field_move_semantics"); + + RegisterSetHandler(moved_to_skeleton_container.GetSkeleton()); + UpdateInitialValue(moved_to_skeleton_container.GetSkeleton()); + + SkeletonContainer moved_from_skeleton_container{}; + moved_from_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedFrom, "skeleton_field_move_semantics"); + RegisterSetHandler(moved_from_skeleton_container.GetSkeleton()); + UpdateInitialValue(moved_from_skeleton_container.GetSkeleton()); + auto moved_skeleton = moved_from_skeleton_container.Extract(); + + // Step 2. Move assign skeleton before offering + std::cout << "\nProvider: Step 2 - Move assign skeleton before offer" << std::endl; + moved_skeleton = std::move(moved_to_skeleton_container.GetSkeleton()); + + // Step 3. Offer skeleton + std::cout << "\nProvider: Step 3 - Offer skeleton" << std::endl; + const auto offer_service_result = moved_skeleton.OfferService(); + if (!offer_service_result.has_value()) + { + FailTest("Provider: OfferService failed: ", offer_service_result.error()); + } + + // Step 4. Send values + std::cout << "\nProvider: Step 4 - Send updated values" << std::endl; + SendValues(moved_skeleton); + + // Step 5. Wait for consumer to notify it is done with the verification sequence + std::cout << "\nProvider: Step 5 - Wait for consumer done notification" << std::endl; + if (!done_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("Provider: WaitWithAbort (done) was stopped by stop_token instead of notification"); + } + + // Step 6. Stop offering skeleton + std::cout << "\nProvider: Step 6 - Stop offering skeleton" << std::endl; + moved_skeleton.StopOfferService(); +} + +void RunMoveConstructAfterOfferProvider(const score::cpp::stop_token& stop_token) +{ + auto done_synchronizer_result = ProcessSynchronizer::Create(kInterprocessNotificationShmPath); + if (!done_synchronizer_result.has_value()) + { + FailTest("Provider: Could not create done ProcessSynchronizer"); + } + auto about_to_call_synchronizer_result = ProcessSynchronizer::Create(kAboutToCallShmPath); + if (!about_to_call_synchronizer_result.has_value()) + { + FailTest("Provider: Could not create about-to-call ProcessSynchronizer"); + } + + // Step 1. Create skeleton + std::cout << "\nProvider: Step 1 - Create skeleton" << std::endl; + SkeletonContainer moved_to_skeleton_container{}; + moved_to_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_field_move_semantics"); + + RegisterSetHandler(moved_to_skeleton_container.GetSkeleton()); + UpdateInitialValue(moved_to_skeleton_container.GetSkeleton()); + + // Step 2. Offer skeleton (no move yet) + std::cout << "\nProvider: Step 2 - Offer skeleton" << std::endl; + moved_to_skeleton_container.OfferService("skeleton_field_move_semantics"); + + // Step 3. Send values + std::cout << "\nProvider: Step 3 - Send updated values" << std::endl; + SendValues(moved_to_skeleton_container.GetSkeleton()); + + // Step 4. Wait for consumer to signal it is about to start the WaitForSamples->Set->Get sequence + std::cout << "\nProvider: Step 4 - Wait for consumer about-to-call notification" << std::endl; + if (!about_to_call_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("Provider: WaitWithAbort (about-to-call) was stopped by stop_token instead of notification"); + } + + // Step 5. Sleep a single random delay, then move construct at random point during the consumer's + // live WaitForSamples->Set->Get sequence + std::cout << "\nProvider: Step 5 - Sleep random race delay, then move construct skeleton while offered" + << std::endl; + SleepRandomRaceDelay(kSequenceRaceWindowUs); + auto moved_skeleton = std::move(moved_to_skeleton_container.GetSkeleton()); + + // Step 6. Wait for consumer to notify it is done with the verification sequence + std::cout << "\nProvider: Step 6 - Wait for consumer done notification" << std::endl; + if (!done_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("Provider: WaitWithAbort (done) was stopped by stop_token instead of notification"); + } + + // Step 7. Stop offering skeleton + std::cout << "\nProvider: Step 7 - Stop offering skeleton" << std::endl; + moved_skeleton.StopOfferService(); +} + +void RunMoveAssignAfterOfferProvider(const score::cpp::stop_token& stop_token) +{ + auto done_synchronizer_result = ProcessSynchronizer::Create(kInterprocessNotificationShmPath); + if (!done_synchronizer_result.has_value()) + { + FailTest("Provider: Could not create done ProcessSynchronizer"); + } + auto about_to_call_synchronizer_result = ProcessSynchronizer::Create(kAboutToCallShmPath); + if (!about_to_call_synchronizer_result.has_value()) + { + FailTest("Provider: Could not create about-to-call ProcessSynchronizer"); + } + + // Step 1. Create two skeletons + std::cout << "\nProvider: Step 1 - Create two skeletons" << std::endl; + SkeletonContainer moved_to_skeleton_container{}; + moved_to_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedTo, "skeleton_field_move_semantics"); + + RegisterSetHandler(moved_to_skeleton_container.GetSkeleton()); + UpdateInitialValue(moved_to_skeleton_container.GetSkeleton()); + + SkeletonContainer moved_from_skeleton_container{}; + moved_from_skeleton_container.CreateSkeleton(kInstanceSpecifierMovedFrom, "skeleton_field_move_semantics"); + RegisterSetHandler(moved_from_skeleton_container.GetSkeleton()); + UpdateInitialValue(moved_from_skeleton_container.GetSkeleton()); + + // Step 2. Offer both skeletons + std::cout << "\nProvider: Step 2 - Offer skeleton" << std::endl; + moved_to_skeleton_container.OfferService("skeleton_field_move_semantics"); + moved_from_skeleton_container.OfferService("skeleton_field_move_semantics"); + + auto moved_skeleton = moved_from_skeleton_container.Extract(); + + // Step 3. Send values + std::cout << "\nProvider: Step 3 - Send updated values" << std::endl; + SendValues(moved_to_skeleton_container.GetSkeleton()); + + // Step 4. Wait for consumer to signal it is about to start the WaitForSamples->Set->Get sequence + std::cout << "\nProvider: Step 4 - Wait for consumer about-to-call notification" << std::endl; + if (!about_to_call_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("Provider: WaitWithAbort (about-to-call) was stopped by stop_token instead of notification"); + } + + // Step 5. Sleep a single random delay, then move assign at random point during the consumer's + // live WaitForSamples->Set->Get sequence. + std::cout << "\nProvider: Step 5 - Sleep random race delay, then move assign skeleton while offered" << std::endl; + SleepRandomRaceDelay(kSequenceRaceWindowUs); + moved_skeleton = std::move(moved_to_skeleton_container.GetSkeleton()); + + // Step 6. Wait for consumer to notify it is done with the verification sequence + std::cout << "\nProvider: Step 6 - Wait for consumer done notification" << std::endl; + if (!done_synchronizer_result->WaitWithAbort(stop_token)) + { + FailTest("Provider: WaitWithAbort (done) was stopped by stop_token instead of notification"); + } + + // Step 7. Stop offering skeleton + std::cout << "\nProvider: Step 7 - Stop offering skeleton" << std::endl; + moved_skeleton.StopOfferService(); +} + +} // namespace + +void RunProvider(const SkeletonFieldMoveScenario& scenario, const score::cpp::stop_token& stop_token) +{ + switch (scenario) + { + case SkeletonFieldMoveScenario::kMoveConstructBeforeOffer: + RunMoveConstructBeforeOfferProvider(stop_token); + break; + case SkeletonFieldMoveScenario::kMoveConstructAfterOffer: + RunMoveConstructAfterOfferProvider(stop_token); + break; + case SkeletonFieldMoveScenario::kMoveAssignBeforeOffer: + RunMoveAssignBeforeOfferProvider(stop_token); + break; + case SkeletonFieldMoveScenario::kMoveAssignAfterOffer: + RunMoveAssignAfterOfferProvider(stop_token); + break; + case SkeletonFieldMoveScenario::kNumberOfScenarios: + default: + FailTest("Provider: Unknown scenario"); + break; + } +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/move_semantics/skeleton_field/provider.h b/score/mw/com/test/move_semantics/skeleton_field/provider.h new file mode 100644 index 0000000000..5028b99668 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/provider.h @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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_FIELD_MOVE_SEMANTICS_PROVIDER_H +#define SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_PROVIDER_H + +#include "score/mw/com/test/move_semantics/skeleton_field/test_parameters.h" + +#include + +namespace score::mw::com::test +{ + +void RunProvider(const SkeletonFieldMoveScenario& scenario, const score::cpp::stop_token& stop_token); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_PROVIDER_H diff --git a/score/mw/com/test/move_semantics/skeleton_field/test_field_datatype.cpp b/score/mw/com/test/move_semantics/skeleton_field/test_field_datatype.cpp new file mode 100644 index 0000000000..4a2826ebbd --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/test_field_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/skeleton_field/test_field_datatype.h" diff --git a/score/mw/com/test/move_semantics/skeleton_field/test_field_datatype.h b/score/mw/com/test/move_semantics/skeleton_field/test_field_datatype.h new file mode 100644 index 0000000000..7794ed4432 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/test_field_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_FIELD_MOVE_SEMANTICS_TEST_FIELD_DATATYPE_H +#define SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_TEST_FIELD_DATATYPE_H + +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +template +class SkeletonFieldMoveSemanticsInterface : public T::Base +{ + public: + using T::Base::Base; + + typename T::template Field moved_field_{*this, "moved_field"}; +}; + +using SkeletonFieldMoveSemanticsProxy = score::mw::com::AsProxy; +using SkeletonFieldMoveSemanticsSkeleton = score::mw::com::AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_TEST_FIELD_DATATYPE_H diff --git a/score/mw/com/test/move_semantics/skeleton_field/test_parameters.cpp b/score/mw/com/test/move_semantics/skeleton_field/test_parameters.cpp new file mode 100644 index 0000000000..c4615e5c71 --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/test_parameters.cpp @@ -0,0 +1,55 @@ +/******************************************************************************** + * 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/skeleton_field/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(SkeletonFieldMoveScenario::kNumberOfScenarios)) + { + FailTest("Consumer: ", + kScenario, + " value ", + scenario_index, + " is out of range. Valid values are between 0 and ", + static_cast(SkeletonFieldMoveScenario::kNumberOfScenarios) - 1, + "."); + } + const auto scenario = static_cast(scenario_index); + + auto service_instance_manifest = GetValue(args, kServiceInstanceManifest); + + return {scenario, service_instance_manifest}; +} + +bool IsMoveAssignScenario(const SkeletonFieldMoveScenario scenario) +{ + return scenario == SkeletonFieldMoveScenario::kMoveAssignBeforeOffer || + scenario == SkeletonFieldMoveScenario::kMoveAssignAfterOffer; +} + +bool IsAfterOfferScenario(const SkeletonFieldMoveScenario scenario) +{ + return scenario == SkeletonFieldMoveScenario::kMoveConstructAfterOffer || + scenario == SkeletonFieldMoveScenario::kMoveAssignAfterOffer; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/move_semantics/skeleton_field/test_parameters.h b/score/mw/com/test/move_semantics/skeleton_field/test_parameters.h new file mode 100644 index 0000000000..4e50df33aa --- /dev/null +++ b/score/mw/com/test/move_semantics/skeleton_field/test_parameters.h @@ -0,0 +1,76 @@ +/******************************************************************************** + * 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_FIELD_MOVE_SEMANTICS_TEST_PARAMETERS_H +#define SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_TEST_PARAMETERS_H + +#include "score/mw/com/types.h" + +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +const std::string kScenario{"scenario"}; +const std::string kServiceInstanceManifest{"service-instance-manifest"}; + +// The instance which ends up being offered and which the consumer always connects to, regardless of scenario. +// For move-construct scenarios, the skeleton created on this instance is (possibly) move-constructed but keeps this +// instance identity. For move-assign scenarios, this is the instance whose skeleton is the move-assign target +// (i.e. it survives, holding the moved-in state, and is then offered under this same instance identity). +const InstanceSpecifier kInstanceSpecifierMovedTo = + InstanceSpecifier::Create(std::string{"test/skeleton_field_move_semantics/MoveFieldInterfaceMovedTo"}).value(); + +// Only used in move-assign scenarios: the source instance whose skeleton is move-assigned away (never offered +// itself under this identity). +const InstanceSpecifier kInstanceSpecifierMovedFrom = + InstanceSpecifier::Create(std::string{"test/skeleton_field_move_semantics/MoveFieldInterfaceMovedFrom"}).value(); + +constexpr std::int32_t kInitialValue{18}; +constexpr std::int32_t kSetRequestValue{1234}; +const std::vector kValuesToSend{20, 30, 35}; +constexpr std::size_t kTotalNumValuesToSend{4U}; + +// Bounded random-delay window used by the "after offer" (fuzzy) scenarios to sleep a +// single random delay before performing the move operation. +constexpr std::chrono::microseconds kSequenceRaceWindowUs{100}; + +enum class SkeletonFieldMoveScenario : std::uint8_t +{ + kMoveConstructBeforeOffer, + kMoveConstructAfterOffer, + kMoveAssignBeforeOffer, + kMoveAssignAfterOffer, + kNumberOfScenarios +}; + +struct CombinedTestConfiguration +{ + SkeletonFieldMoveScenario scenario; + std::string service_instance_manifest; +}; + +CombinedTestConfiguration ReadCommandLineArguments(int argc, const char** argv); + +/// \brief Returns true if the scenario performs a move-assign (as opposed to a move-construct). +bool IsMoveAssignScenario(SkeletonFieldMoveScenario scenario); + +/// \brief Returns true if the scenario performs the move while the service is already offered (i.e. races the +/// consumer's live API calls), as opposed to moving before offering the service. +bool IsAfterOfferScenario(SkeletonFieldMoveScenario scenario); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_SKELETON_FIELD_MOVE_SEMANTICS_TEST_PARAMETERS_H