diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp index 8c96c2314..831f096f3 100644 --- a/score/mw/com/impl/bindings/lola/skeleton.cpp +++ b/score/mw/com/impl/bindings/lola/skeleton.cpp @@ -372,6 +372,10 @@ auto Skeleton::PrepareOffer(SkeletonEventBindings& events, auto& lola_message_passing = lola_runtime.GetLolaMessaging(); const SkeletonInstanceIdentifier skeleton_instance_identifier{lola_service_id_, lola_instance_id_}; + // Once StopOfferService is called these scopes are expired, thus we need to reinitialize them here + on_service_method_subscribed_handler_scope_ = score::safecpp::Scope<>(); + method_call_handler_scope_ = score::safecpp::Scope<>(); + // Register a handler with message passing which will open methods shared memory regions when the proxy notifies via // message passing that it has finished setting up the regions. We always register a handler for QM proxies and also // register a handler for ASIL-B proxies if this skeleton is ASIL-B. diff --git a/score/mw/com/test/common_test_resources/BUILD b/score/mw/com/test/common_test_resources/BUILD index 50279fd44..6271e39bc 100644 --- a/score/mw/com/test/common_test_resources/BUILD +++ b/score/mw/com/test/common_test_resources/BUILD @@ -51,6 +51,20 @@ cc_library( ], ) +cc_library( + name = "fail_test", + srcs = [ + "fail_test.cpp", + ], + hdrs = [ + "fail_test.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/mw/com/test:__subpackages__", + ], +) + cc_library( name = "shared_memory_object_guard", srcs = [ @@ -129,6 +143,25 @@ cc_library( ], ) +cc_library( + name = "command_line_parser", + srcs = [ + "command_line_parser.cpp", + ], + hdrs = [ + "command_line_parser.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/mw/com/test:__subpackages__", + ], + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:test_error_domain", + "@boost.program_options", + ], +) + cc_library( name = "sctf_test_runner", srcs = [ diff --git a/score/mw/com/test/common_test_resources/command_line_parser.cpp b/score/mw/com/test/common_test_resources/command_line_parser.cpp new file mode 100644 index 000000000..3420ae0b5 --- /dev/null +++ b/score/mw/com/test/common_test_resources/command_line_parser.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/common_test_resources/command_line_parser.h" +#include + +namespace score::mw::com::test +{ +auto ParseCommandLineArguments(int argc, + const char** argv, + const std::vector>& param_names) + -> CommandLineArgsMapType +{ + // uid is needed internally by sctftestrunner + + namespace po = boost::program_options; + po::options_description options; + options.add_options()("help,h", "display the help message"); + for (const auto& [param_name, param_doc] : param_names) + { + options.add_options()( + param_name.data(), po::value(), ("specify " + std::string{param_doc}).data()); + } + + po::variables_map args; + const auto parsed_args = + po::command_line_parser{argc, argv} + .options(options) + .allow_unregistered() + .style(po::command_line_style::unix_style | po::command_line_style::allow_long_disguise) + .run(); + po::store(parsed_args, args); + + if (args.count("help") > 0U) + { + std::cerr << options << '\n'; + return args; + } + + po::notify(args); + + return args; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/common_test_resources/command_line_parser.h b/score/mw/com/test/common_test_resources/command_line_parser.h new file mode 100644 index 000000000..f164fdc03 --- /dev/null +++ b/score/mw/com/test/common_test_resources/command_line_parser.h @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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_COMMAND_LINE_PARSER_H +#define SCORE_MW_COM_TEST_COMMON_TEST_RESOURCES_COMMAND_LINE_PARSER_H + +#include "score/mw/com/test/common_test_resources/test_error_domain.h" +#include + +#include + +#include "score/result/result.h" +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +using CommandLineArgsMapType = boost::program_options::variables_map; + +template +auto GetValueIfProvided(const boost::program_options::variables_map& args, const std::string& arg_string) + -> Result +{ + std::string error_msg = "could nod find the requested parameter: " + arg_string; + if constexpr (std::is_same_v) + { + return (args.count(arg_string) > 0U) ? args[arg_string].as() + : score::MakeUnexpected(MakeError( + TestErrorCode::kParsingCommandLineArgumentFailed, error_msg)); + } + if constexpr (std::is_integral_v) + { + return (args.count(arg_string) > 0U) ? static_cast(std::stoull(args[arg_string].as())) + : score::MakeUnexpected(MakeError( + TestErrorCode::kParsingCommandLineArgumentFailed, error_msg)); + } + if constexpr (std::is_floating_point_v) + { + return (args.count(arg_string) > 0U) ? static_cast(std::stold(args[arg_string].as())) + : score::MakeUnexpected(MakeError( + TestErrorCode::kParsingCommandLineArgumentFailed, error_msg)); + } + + return score::MakeUnexpected( + MakeError(TestErrorCode::kParsingCommandLineArgumentFailed, + "Only std::string, integral and floatin point types can be parsed as command line arguments!\n")); +} + +auto ParseCommandLineArguments(int argc, + const char** argv, + const std::vector>& param_names) + -> boost::program_options::variables_map; + +} // namespace score::mw::com::test +#endif // SCORE_MW_COM_TEST_COMMON_TEST_RESOURCES_COMMAND_LINE_PARSER_H diff --git a/score/mw/com/test/common_test_resources/fail_test.cpp b/score/mw/com/test/common_test_resources/fail_test.cpp new file mode 100644 index 000000000..93740ede7 --- /dev/null +++ b/score/mw/com/test/common_test_resources/fail_test.cpp @@ -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 + ********************************************************************************/ + +#include "score/mw/com/test/common_test_resources/fail_test.h" + +#include + +namespace score::mw::com::test::detail +{ + +void fail_test_(std::stringstream&& strstr) +{ + strstr << "\033[0m \033[1m\033[41mTEST FAILED\033[0m\n"; + std::cerr << std::move(strstr).str() << std::flush; + std::_Exit(EXIT_FAILURE); +} + +} // namespace score::mw::com::test::detail diff --git a/score/mw/com/test/common_test_resources/fail_test.h b/score/mw/com/test/common_test_resources/fail_test.h new file mode 100644 index 000000000..0c7db2aef --- /dev/null +++ b/score/mw/com/test/common_test_resources/fail_test.h @@ -0,0 +1,59 @@ +/******************************************************************************** + * 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_FAIL_TEST_H +#define SCORE_MW_COM_TEST_COMMON_TEST_RESOURCES_FAIL_TEST_H + +#include + +namespace score::mw::com::test +{ + +namespace detail +{ +void fail_test_(std::stringstream&& strstr); + +template +void fail_test_(std::stringstream&& strstr, Start&& start, Tail&&... tail) +{ + strstr << std::forward(start); + if constexpr (sizeof...(Tail) > 0U) + { + fail_test_(std::move(strstr), std::forward(tail)...); + } + else + { + fail_test_(std::move(strstr)); + } +} +} // namespace detail + +template +void fail_test(Args&&... args) +{ + std::stringstream strstr; + strstr << "\033[1m\033[31m"; + detail::fail_test_(std::move(strstr), std::forward(args)...); +} + +template +void fail_test_if(bool condition, Args&&... args) +{ + if (condition) + { + fail_test(std::forward(args)...); + } +} + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_COMMON_TEST_RESOURCES_FAIL_TEST_H diff --git a/score/mw/com/test/common_test_resources/test_error_domain.cpp b/score/mw/com/test/common_test_resources/test_error_domain.cpp index 2d7bafce8..8e4c62c13 100644 --- a/score/mw/com/test/common_test_resources/test_error_domain.cpp +++ b/score/mw/com/test/common_test_resources/test_error_domain.cpp @@ -21,6 +21,8 @@ std::string_view score::mw::com::test::TestErrorDomain::MessageFor(const score:: return "Failed to create instance specifier."; case score::mw::com::test::TestErrorCode::kCreateSkeletonFailed: return "Failed to create skeleton."; + case score::mw::com::test::TestErrorCode::kParsingCommandLineArgumentFailed: + return "Failed to parse command line argument."; default: return "Unknown Error!"; } diff --git a/score/mw/com/test/common_test_resources/test_error_domain.h b/score/mw/com/test/common_test_resources/test_error_domain.h index 3cbc44397..c631ea081 100644 --- a/score/mw/com/test/common_test_resources/test_error_domain.h +++ b/score/mw/com/test/common_test_resources/test_error_domain.h @@ -24,6 +24,7 @@ enum class TestErrorCode : score::result::ErrorCode { kCreateInstanceSpecifierFailed = 1, kCreateSkeletonFailed = 2, + kParsingCommandLineArgumentFailed = 3, }; class TestErrorDomain final : public score::result::ErrorDomain diff --git a/score/mw/com/test/methods/BUILD b/score/mw/com/test/methods/BUILD index 14a042885..91c55b94b 100644 --- a/score/mw/com/test/methods/BUILD +++ b/score/mw/com/test/methods/BUILD @@ -15,7 +15,11 @@ test_suite( name = "component_tests", tests = [ "//score/mw/com/test/methods/basic_acceptance_test:component_tests", + "//score/mw/com/test/methods/edge_cases_test:component_tests", + "//score/mw/com/test/methods/mixed_criticality:component_tests", + "//score/mw/com/test/methods/multiple_proxies:component_tests", "//score/mw/com/test/methods/signature_variations:component_tests", + "//score/mw/com/test/methods/stop_offer_during_call:component_tests", ], visibility = ["//score/mw/com/test:__pkg__"], ) diff --git a/score/mw/com/test/methods/edge_cases_test/BUILD b/score/mw/com/test/methods/edge_cases_test/BUILD new file mode 100644 index 000000000..a8565e104 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/BUILD @@ -0,0 +1,65 @@ +# ******************************************************************************* +# 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("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//score/mw/com/test:pkg_application.bzl", "pkg_application") + +cc_library( + name = "test_method_datatype", + srcs = ["test_method_datatype.cpp"], + hdrs = ["test_method_datatype.h"], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/mw/com/test/methods/edge_cases_test:__subpackages__", + ], + deps = [ + "//score/mw/com", + ], +) + +cc_binary( + name = "edge_cases_test", + srcs = ["edge_cases_test.cpp"], + data = ["config/mw_com_config.json"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:command_line_parser", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:assert_handler", + "//score/mw/com/test/methods/edge_cases_test:test_method_datatype", + "//score/mw/com/test/methods/methods_test_resources:proxy_container", + "@score_baselibs//score/language/futurecpp", + ], +) + +pkg_application( + name = "edge-case-test-pkg", + app_name = "EdgeCasesTestApp", + bin = [":edge_cases_test"], + etc = [ + "config/mw_com_config.json", + ], + visibility = [ + "//score/mw/com/test/methods/edge_cases_test/integration_test:__subpackages__", + ], +) + +test_suite( + name = "component_tests", + tests = [ + "//score/mw/com/test/methods/edge_cases_test/integration_test:component_tests", + ], +) diff --git a/score/mw/com/test/methods/edge_cases_test/config/mw_com_config.json b/score/mw/com/test/methods/edge_cases_test/config/mw_com_config.json new file mode 100644 index 000000000..cf0cde12c --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/config/mw_com_config.json @@ -0,0 +1,89 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/methods/edge_cases/", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 1200, + "methods": [ + { + "methodName": "method_with_args_and_return", + "methodId": 1 + }, + { + "methodName": "method_with_args_only", + "methodId": 2 + }, + { + "methodName": "method_with_return_only", + "methodId": 3 + }, + { + "methodName": "method_without_args_or_return", + "methodId": 4 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "test/methods/edge_cases/EdgeCasesMethods", + "serviceTypeName": "/test/methods/edge_cases/", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "methods": [ + { + "methodName": "method_with_args_and_return", + "queueSize": 1 + }, + { + "methodName": "method_with_args_only", + "queueSize": 1 + }, + { + "methodName": "method_with_return_only", + "queueSize": 1 + }, + { + "methodName": "method_without_args_or_return", + "queueSize": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM" + } +} diff --git a/score/mw/com/test/methods/edge_cases_test/edge_cases_test.cpp b/score/mw/com/test/methods/edge_cases_test/edge_cases_test.cpp new file mode 100644 index 000000000..4610d1be1 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/edge_cases_test.cpp @@ -0,0 +1,460 @@ +/******************************************************************************** + * 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/assert_handler.h" +#include "score/mw/com/test/common_test_resources/command_line_parser.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/methods/edge_cases_test/test_method_datatype.h" +#include "score/mw/com/test/methods/methods_test_resources/proxy_container.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +const InstanceSpecifier kInstanceSpecifier = + InstanceSpecifier::Create(std::string{"test/methods/edge_cases/EdgeCasesMethods"}).value(); + +constexpr std::int32_t kTestValueA = 10; +constexpr std::int32_t kTestValueB = 20; +constexpr std::int32_t kExpectedSum = kTestValueA + kTestValueB; + +const std::vector kAllMethodNames = {"method_with_args_and_return", + "method_with_args_only", + "method_with_return_only", + "method_without_args_or_return"}; + +// Test 1: Disabled methods should return error (not crash), enabled methods should work +// +// Creates a proxy with 3 of 4 methods enabled and verifies: +// - All 3 enabled methods (args+return, args-only, return-only) work correctly +// - The disabled void() method returns an error instead of crashing +// +// NOTE: Framework limitation — only the void() signature gracefully returns an error when +// the method is disabled. For other signatures the impl layer (proxy_method.h) wraps binding +// calls with SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD assertions that abort the process: +// - void(Args...): AllocateInArgs → SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD → abort +// - ReturnType(): AllocateReturnType → SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD → abort +// - ReturnType(Args...): AllocateInArgs → SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD → abort +// The LoLa binding correctly returns kBindingFailure for unsubscribed methods, but the error +// is never propagated. When the impl layer is fixed to propagate errors instead of asserting, +// extend this test to cover all four signatures as disabled. +void TestDisabledMethodReturnsError() +{ + std::cout << "\n=== Test 1: Disabled method returns error, enabled methods work ===" << std::endl; + + // Create skeleton and register all handlers + auto skeleton_result = EdgeCasesMethodSkeleton::Create(kInstanceSpecifier); + if (!skeleton_result.has_value()) + { + fail_test("Failed to create skeleton: ", skeleton_result.error()); + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + + auto handler1 = [](std::int32_t val1, std::int32_t val2) -> std::int32_t { + return val1 + val2; + }; + skeleton->method_with_args_and_return.RegisterHandler(std::move(handler1)); + + auto handler2 = [](std::int32_t) {}; + skeleton->method_with_args_only.RegisterHandler(std::move(handler2)); + + auto handler3 = []() -> std::int32_t { + return 42; + }; + skeleton->method_with_return_only.RegisterHandler(std::move(handler3)); + + auto handler4 = []() {}; + skeleton->method_without_args_or_return.RegisterHandler(std::move(handler4)); + + auto offer_result = skeleton->OfferService(); + if (!offer_result.has_value()) + { + fail_test("Failed to offer service: ", offer_result.error()); + } + + // Create proxy with 3 methods enabled, leaving method_without_args_or_return DISABLED + ProxyContainer proxy_container; + const std::vector enabled_methods = { + "method_with_args_and_return", "method_with_args_only", "method_with_return_only"}; + if (!proxy_container.CreateProxy(kInstanceSpecifier, enabled_methods)) + { + fail_test("Failed to create proxy"); + } + + // 1a: Verify enabled method_with_args_and_return works (int32_t(int32_t, int32_t)) + std::cout << " Testing enabled method_with_args_and_return..." << std::endl; + auto result_args_return = proxy_container.GetProxy().method_with_args_and_return(kTestValueA, kTestValueB); + if (!result_args_return.has_value()) + { + fail_test("Enabled method_with_args_and_return failed: ", result_args_return.error()); + } + if (*result_args_return.value() != kExpectedSum) + { + fail_test( + "method_with_args_and_return wrong value: ", *result_args_return.value(), " expected: ", kExpectedSum); + } + std::cout << " PASSED - method_with_args_and_return works" << std::endl; + + // 1b: Verify enabled method_with_args_only works (void(int32_t)) + std::cout << " Testing enabled method_with_args_only..." << std::endl; + auto result_args_only = proxy_container.GetProxy().method_with_args_only(kTestValueA); + if (!result_args_only.has_value()) + { + fail_test("Enabled method_with_args_only failed: ", result_args_only.error()); + } + std::cout << " PASSED - method_with_args_only works" << std::endl; + + // 1c: Verify enabled method_with_return_only works (int32_t()) + std::cout << " Testing enabled method_with_return_only..." << std::endl; + auto result_return_only = proxy_container.GetProxy().method_with_return_only(); + if (!result_return_only.has_value()) + { + fail_test("Enabled method_with_return_only failed: ", result_return_only.error()); + } + constexpr std::int32_t kExpectedReturnOnly = 42; + if (*result_return_only.value() != kExpectedReturnOnly) + { + fail_test( + "method_with_return_only wrong value: ", *result_return_only.value(), " expected: ", kExpectedReturnOnly); + } + std::cout << " PASSED - method_with_return_only works" << std::endl; + + // 1d: Call DISABLED method_without_args_or_return → should return error (not crash) + // This is the void() signature — the only one that currently returns an error gracefully. + std::cout << " Testing disabled method_without_args_or_return..." << std::endl; + auto disabled_result = proxy_container.GetProxy().method_without_args_or_return(); + if (disabled_result.has_value()) + { + fail_test("ERROR: Disabled method succeeded when it should have returned error!", disabled_result.error()); + } + std::cout << " PASSED - Disabled method returned error: " << disabled_result.error() << std::endl; + + skeleton->StopOfferService(); + + std::cout << "=== Test 1: PASSED ===" << std::endl; +} + +// Test 2: Skeleton without all handlers should fail to offer service +void TestIncompleteHandlers() +{ + std::cout << "\n=== Test 2: Incomplete handlers fail OfferService ===" << std::endl; + + auto skeleton_result = EdgeCasesMethodSkeleton::Create(kInstanceSpecifier); + if (!skeleton_result.has_value()) + { + fail_test("Failed to create skeleton: ", skeleton_result.error()); + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + + // Register only 2 of 4 handlers + auto handler1 = [](std::int32_t val1, std::int32_t val2) -> std::int32_t { + return val1 + val2; + }; + skeleton->method_with_args_and_return.RegisterHandler(std::move(handler1)); + std::cout << " Registered handler 1" << std::endl; + + auto handler2 = [](std::int32_t) {}; + skeleton->method_with_args_only.RegisterHandler(std::move(handler2)); + std::cout << " Registered handler 2 (only 2 of 4 registered)" << std::endl; + + // Try to offer service - should fail + auto offer_result = skeleton->OfferService(); + if (offer_result.has_value()) + { + fail_test("ERROR: OfferService succeeded when it should have failed!"); + skeleton->StopOfferService(); + } + + std::cout << " OfferService failed as expected: " << offer_result.error() << std::endl; + std::cout << "=== Test 2: PASSED ===" << std::endl; +} + +// Test 3: Proxy can be recreated after first one is destroyed +void TestProxyRecreation() +{ + std::cout << "\n=== Test 3: Proxy recreation ===" << std::endl; + + // Create skeleton with all handlers + auto skeleton_result = EdgeCasesMethodSkeleton::Create(kInstanceSpecifier); + if (!skeleton_result.has_value()) + { + fail_test("Failed to create skeleton: ", skeleton_result.error()); + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + + auto handler1 = [](std::int32_t val1, std::int32_t val2) -> std::int32_t { + return val1 + val2; + }; + skeleton->method_with_args_and_return.RegisterHandler(std::move(handler1)); + + auto handler2 = [](std::int32_t) {}; + skeleton->method_with_args_only.RegisterHandler(std::move(handler2)); + + auto handler3 = []() -> std::int32_t { + return 42; + }; + skeleton->method_with_return_only.RegisterHandler(std::move(handler3)); + + auto handler4 = []() {}; + skeleton->method_without_args_or_return.RegisterHandler(std::move(handler4)); + + auto offer_result = skeleton->OfferService(); + if (!offer_result.has_value()) + { + fail_test("Failed to offer service: ", offer_result.error()); + } + + // Create first proxy, use it, then destroy it + { + std::cout << " Creating first proxy..." << std::endl; + ProxyContainer proxy_container1; + fail_test_if(!proxy_container1.CreateProxy(kInstanceSpecifier, kAllMethodNames), + "Failed to create first proxy"); + + auto result1 = proxy_container1.GetProxy().method_with_args_and_return(kTestValueA, kTestValueB); + if (!result1.has_value()) + { + fail_test("First proxy method call failed: ", result1.error()); + } + fail_test_if(*result1.value() != kExpectedSum, "First proxy returned wrong value"); + + std::cout << " First proxy works correctly" << std::endl; + } // First proxy destroyed here + + // Create second proxy after first is destroyed + { + std::cout << " Creating second proxy after first is destroyed..." << std::endl; + ProxyContainer proxy_container2; + fail_test_if(!proxy_container2.CreateProxy(kInstanceSpecifier, kAllMethodNames), + "Failed to create second proxy"); + + auto result2 = proxy_container2.GetProxy().method_with_args_and_return(kTestValueA, kTestValueB); + if (!result2.has_value()) + { + fail_test("Second proxy method call failed: ", result2.error()); + } + fail_test_if(*result2.value() != kExpectedSum, "Second proxy returned wrong value"); + + std::cout << " Second proxy works correctly" << std::endl; + } + + skeleton->StopOfferService(); + + std::cout << "=== Test 3: PASSED ===" << std::endl; +} + +// Test 4: Skeleton can be recreated after first one is destroyed +// +// Currently blocked by Ticket-243577: StopOfferService does not unregister the +// OnServiceMethodSubscribedHandler, so the leaked handler causes the second OfferService +// to fail with kBindingFailure. Enable this test by setting RUN_SKELETON_RECREATION_TEST=1 +// once the fix lands. +void TestSkeletonRecreation() +{ + std::cout << "\n=== Test 4: Skeleton recreation ===" << std::endl; + + // --- First skeleton lifecycle --- + { + std::cout << " Creating first skeleton..." << std::endl; + auto skeleton_result = EdgeCasesMethodSkeleton::Create(kInstanceSpecifier); + if (!skeleton_result.has_value()) + { + fail_test("Failed to create first skeleton: ", skeleton_result.error()); + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + + auto handler1 = [](std::int32_t val1, std::int32_t val2) -> std::int32_t { + return val1 + val2; + }; + skeleton->method_with_args_and_return.RegisterHandler(std::move(handler1)); + + auto handler2 = [](std::int32_t) {}; + skeleton->method_with_args_only.RegisterHandler(std::move(handler2)); + + auto handler3 = []() -> std::int32_t { + return 42; + }; + skeleton->method_with_return_only.RegisterHandler(std::move(handler3)); + + auto handler4 = []() {}; + skeleton->method_without_args_or_return.RegisterHandler(std::move(handler4)); + + auto offer_result = skeleton->OfferService(); + if (!offer_result.has_value()) + { + fail_test("First OfferService failed: ", offer_result.error()); + } + + // Create proxy and verify methods work with first skeleton + { + ProxyContainer proxy_container; + fail_test_if(!proxy_container.CreateProxy(kInstanceSpecifier, kAllMethodNames), + "Failed to create proxy for the first skeleton"); + + auto result = proxy_container.GetProxy().method_with_args_and_return(kTestValueA, kTestValueB); + if (!result.has_value()) + { + fail_test("First skeleton method call failed: ", result.error()); + } + fail_test_if(*result.value() != kExpectedSum, "First skeleton returned wrong value"); + std::cout << " First skeleton works correctly" << std::endl; + } // Proxy destroyed here + + skeleton->StopOfferService(); + } // First skeleton destroyed here + + // --- Second skeleton lifecycle (recreation) --- + { + std::cout << " Creating second skeleton after first is destroyed..." << std::endl; + auto skeleton_result = EdgeCasesMethodSkeleton::Create(kInstanceSpecifier); + if (!skeleton_result.has_value()) + { + fail_test("Failed to create second skeleton: ", skeleton_result.error()); + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + + auto handler1 = [](std::int32_t val1, std::int32_t val2) -> std::int32_t { + return val1 + val2; + }; + skeleton->method_with_args_and_return.RegisterHandler(std::move(handler1)); + + auto handler2 = [](std::int32_t) {}; + skeleton->method_with_args_only.RegisterHandler(std::move(handler2)); + + auto handler3 = []() -> std::int32_t { + return 42; + }; + skeleton->method_with_return_only.RegisterHandler(std::move(handler3)); + + auto handler4 = []() {}; + skeleton->method_without_args_or_return.RegisterHandler(std::move(handler4)); + + auto offer_result = skeleton->OfferService(); + if (!offer_result.has_value()) + { + fail_test("Second OfferService failed: ", offer_result.error()); + } + + // Create proxy and verify methods work with second skeleton + { + ProxyContainer proxy_container; + fail_test_if(!proxy_container.CreateProxy(kInstanceSpecifier, kAllMethodNames), + "Failed to create proxy for the second skeleton"); + + auto result = proxy_container.GetProxy().method_with_args_and_return(kTestValueA, kTestValueB); + if (!result.has_value()) + { + fail_test("Second skeleton method call failed: ", result.error()); + } + fail_test_if(*result.value() != kExpectedSum, "Second skeleton returned wrong value"); + + std::cout << " Second skeleton works correctly" << std::endl; + } // Proxy destroyed here + + skeleton->StopOfferService(); + } // Second skeleton destroyed here + + std::cout << "=== Test 4: PASSED ===" << std::endl; +} + +enum struct TestType : std::uint8_t +{ + DISABLED_METHOD = 0, + INCOMPLETE_HANDLERS = 1, + PROXY_RECREATION = 2, + SKELETON_RECREATION = 3, +}; + +auto GetTestType(int argc, const char** argv) -> TestType +{ + auto args = ParseCommandLineArguments(argc, argv, {{"test", ""}}); + auto test_type_str = GetValueIfProvided(args, "test"); + + fail_test_if(!test_type_str.has_value(), "No test type specified"); + + if (test_type_str.value() == "disabled_method_test") + { + return TestType::DISABLED_METHOD; + } + if (test_type_str.value() == "incomplete_handlers_test") + { + return TestType::INCOMPLETE_HANDLERS; + } + if (test_type_str.value() == "proxy_recreation_test") + { + return TestType::PROXY_RECREATION; + } + if (test_type_str.value() == "skeleton_recreation_test") + { + return TestType::SKELETON_RECREATION; + } + + fail_test("Unknown test type"); + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); +} + +} // namespace +} // namespace score::mw::com::test + +int main(int argc, const char** argv) +{ + using namespace score::mw::com; + using namespace score::mw::com::test; + + // Initialize runtime + SetupAssertHandler(); + score::mw::com::runtime::InitializeRuntime(argc, argv); + + auto test_type = GetTestType(argc, argv); + + switch (test_type) + { + case TestType::DISABLED_METHOD: + { + TestDisabledMethodReturnsError(); + break; + } + case TestType::INCOMPLETE_HANDLERS: + { + TestIncompleteHandlers(); + break; + } + case TestType::PROXY_RECREATION: + { + TestProxyRecreation(); + break; + } + case TestType::SKELETON_RECREATION: + { + TestSkeletonRecreation(); + break; + } + default: + std::cerr << "Unknown test type" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "\n=== ALL EDGE CASES TESTS PASSED ===" << std::endl; + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/edge_cases_test/integration_test/BUILD b/score/mw/com/test/methods/edge_cases_test/integration_test/BUILD new file mode 100644 index 000000000..4a4c4f9a5 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/integration_test/BUILD @@ -0,0 +1,72 @@ +# ******************************************************************************* +# 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:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +pkg_tar( + name = "filesystem", + deps = [ + "//score/mw/com/test/methods/edge_cases_test:edge-case-test-pkg", + ], +) + +py_library( + name = "test_fixture", + srcs = ["test_fixture.py"], +) + +integration_test( + name = "disabled_method_test", + srcs = [ + "disabled_method_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "incomplete_handlers_test", + srcs = [ + "incomplete_handlers_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "proxy_recreation_test", + srcs = [ + "proxy_recreation_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "skeleton_recreation_test", + srcs = [ + "skeleton_recreation_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +test_suite( + name = "component_tests", + tests = [ + "disabled_method_test", + "incomplete_handlers_test", + "proxy_recreation_test", + "skeleton_recreation_test", + ], +) diff --git a/score/mw/com/test/methods/edge_cases_test/integration_test/disabled_method_test.py b/score/mw/com/test/methods/edge_cases_test/integration_test/disabled_method_test.py new file mode 100644 index 000000000..2fecdf49e --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/integration_test/disabled_method_test.py @@ -0,0 +1,17 @@ +# ******************************************************************************* +# 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 TestType, edge_cases_test + + +def test_edge_cases(sut): + edge_cases_test(sut, TestType.DISABLED_METHOD) diff --git a/score/mw/com/test/methods/edge_cases_test/integration_test/incomplete_handlers_test.py b/score/mw/com/test/methods/edge_cases_test/integration_test/incomplete_handlers_test.py new file mode 100644 index 000000000..52d840aa0 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/integration_test/incomplete_handlers_test.py @@ -0,0 +1,17 @@ +# ******************************************************************************* +# 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 TestType, edge_cases_test + + +def test_edge_cases(sut): + edge_cases_test(sut, TestType.INCOMPLETE_HANDLERS) diff --git a/score/mw/com/test/methods/edge_cases_test/integration_test/proxy_recreation_test.py b/score/mw/com/test/methods/edge_cases_test/integration_test/proxy_recreation_test.py new file mode 100644 index 000000000..65240a617 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/integration_test/proxy_recreation_test.py @@ -0,0 +1,17 @@ +# ******************************************************************************* +# 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 TestType, edge_cases_test + + +def test_edge_cases(sut): + edge_cases_test(sut, TestType.PROXY_RECREATION) diff --git a/score/mw/com/test/methods/edge_cases_test/integration_test/skeleton_recreation_test.py b/score/mw/com/test/methods/edge_cases_test/integration_test/skeleton_recreation_test.py new file mode 100644 index 000000000..fa3a92d66 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/integration_test/skeleton_recreation_test.py @@ -0,0 +1,17 @@ +# ******************************************************************************* +# 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 TestType, edge_cases_test + + +def test_edge_cases(sut): + edge_cases_test(sut, TestType.SKELETON_RECREATION) diff --git a/score/mw/com/test/methods/edge_cases_test/integration_test/test_fixture.py b/score/mw/com/test/methods/edge_cases_test/integration_test/test_fixture.py new file mode 100644 index 000000000..a50be48c1 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/integration_test/test_fixture.py @@ -0,0 +1,26 @@ +# ******************************************************************************* +# 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 Enum + + +class TestType(Enum): + DISABLED_METHOD = "disabled_method_test" + INCOMPLETE_HANDLERS = "incomplete_handlers_test" + PROXY_RECREATION = "proxy_recreation_test" + SKELETON_RECREATION = "skeleton_recreation_test" + + +def edge_cases_test(sut, test_type: TestType, timeout_sec=60): + with sut.start_process(f"bin/edge_cases_test --test {test_type.value}", + cwd="/opt/EdgeCasesTestApp") as provider: + assert provider.wait_for_exit(timeout_sec) == 0 diff --git a/score/mw/com/test/methods/edge_cases_test/test_method_datatype.cpp b/score/mw/com/test/methods/edge_cases_test/test_method_datatype.cpp new file mode 100644 index 000000000..e631939c0 --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/test_method_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/methods/edge_cases_test/test_method_datatype.h" diff --git a/score/mw/com/test/methods/edge_cases_test/test_method_datatype.h b/score/mw/com/test/methods/edge_cases_test/test_method_datatype.h new file mode 100644 index 000000000..b4beee36d --- /dev/null +++ b/score/mw/com/test/methods/edge_cases_test/test_method_datatype.h @@ -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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_METHODS_EDGE_CASES_TEST_TEST_METHOD_DATATYPE_H +#define SCORE_MW_COM_TEST_METHODS_EDGE_CASES_TEST_TEST_METHOD_DATATYPE_H + +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +template +class EdgeCasesMethodInterface : public T::Base +{ + public: + using T::Base::Base; + + /// \brief Method with both InArgs and Result + typename T::template Method method_with_args_and_return{ + *this, + "method_with_args_and_return"}; + + /// \brief Method with only InArgs, no Result (void return) + typename T::template Method method_with_args_only{*this, "method_with_args_only"}; + + /// \brief Method with only Result, no InArgs + typename T::template Method method_with_return_only{*this, "method_with_return_only"}; + + /// \brief Method without InArgs or Result + typename T::template Method method_without_args_or_return{*this, "method_without_args_or_return"}; +}; + +using EdgeCasesMethodProxy = score::mw::com::AsProxy; +using EdgeCasesMethodSkeleton = score::mw::com::AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_METHODS_EDGE_CASES_TEST_TEST_METHOD_DATATYPE_H diff --git a/score/mw/com/test/methods/methods_test_resources/BUILD b/score/mw/com/test/methods/methods_test_resources/BUILD index ba8105cdc..ec412d076 100644 --- a/score/mw/com/test/methods/methods_test_resources/BUILD +++ b/score/mw/com/test/methods/methods_test_resources/BUILD @@ -12,6 +12,7 @@ # ******************************************************************************* load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") cc_library( name = "proxy_container", @@ -77,3 +78,81 @@ cc_library( "@score_logging//score/mw/log", ], ) + +cc_library( + name = "common_resources", + srcs = ["common_resources.cpp"], + hdrs = ["common_resources.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/mw/com/test/methods:__subpackages__"], + deps = ["//score/mw/com"], +) + +cc_library( + name = "test_method_datatype", + srcs = ["test_method_datatype.cpp"], + hdrs = ["test_method_datatype.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/mw/com/test/methods:__subpackages__"], + deps = [ + "//score/mw/com", + ], +) + +cc_library( + name = "provider_runner", + srcs = ["provider_runner.cpp"], + hdrs = ["provider_runner.h"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + visibility = ["//score/mw/com/test/methods:__subpackages__"], + deps = [ + ":common_resources", + ":test_method_datatype", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:command_line_parser", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/methods/methods_test_resources:process_synchronizer", + "@score_baselibs//score/result", + ], +) + +cc_library( + name = "consumer_runner", + srcs = ["consumer_runner.cpp"], + hdrs = ["consumer_runner.h"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + visibility = ["//score/mw/com/test/methods:__subpackages__"], + deps = [ + ":common_resources", + ":test_method_datatype", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:command_line_parser", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/methods/methods_test_resources:process_synchronizer", + "//score/mw/com/test/methods/methods_test_resources:proxy_container", + ], +) + +filegroup( + name = "base_mw_com_config_json", + srcs = ["base_mw_com_config.json"], + visibility = ["//score/mw/com/test/methods:__subpackages__"], +) + +py_binary( + name = "config_generator", + srcs = ["config_generator.py"], + data = ["base_mw_com_config_json"], + visibility = ["//score/mw/com/test/methods:__subpackages__"], +) + +validate_json_schema_test( + name = "validate_config_schema_provider", + json = "mw_com_config.json", + schema = "//score/mw/com:config_schema", + tags = ["lint"], +) diff --git a/score/mw/com/test/methods/methods_test_resources/base_mw_com_config.json b/score/mw/com/test/methods/methods_test_resources/base_mw_com_config.json new file mode 100644 index 000000000..364059d69 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/base_mw_com_config.json @@ -0,0 +1,97 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/methods/methods_test_resources/MultiMethodProvider", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 3000, + "methods": [ + { + "methodName": "method0", + "methodId": 1 + }, + { + "methodName": "method1", + "methodId": 2 + }, + { + "methodName": "method2", + "methodId": 3 + }, + { + "methodName": "method3", + "methodId": 4 + }, + { + "methodName": "method4", + "methodId": 5 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "methods_test_resources/MultiMethodProvider", + "serviceTypeName": "/test/methods/methods_test_resources/MultiMethodProvider", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "methods": [ + { + "methodName": "method0", + "queueSize": 5 + }, + { + "methodName": "method1", + "queueSize": 5 + }, + { + "methodName": "method2", + "queueSize": 5 + }, + { + "methodName": "method3", + "queueSize": 5 + }, + { + "methodName": "method4", + "queueSize": 5 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM" + } +} diff --git a/score/mw/com/test/methods/methods_test_resources/common_resources.cpp b/score/mw/com/test/methods/methods_test_resources/common_resources.cpp new file mode 100644 index 000000000..28a0f64a6 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/common_resources.cpp @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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/methods/methods_test_resources/common_resources.h" + +#include "score/mw/com/runtime.h" + +namespace score::mw::com::test +{ + +auto CreateInterprocessNotificationShmPath(size_t path_id) -> std::string +{ + std::string path{"test_methods_interprocess_shm_notification_path"}; + path.append(std::to_string(path_id)); + path.append("_notification"); + return path; +} + +void InitializeRuntime(const std::string& path) +{ + auto rtc = path.empty() ? score::mw::com::runtime::RuntimeConfiguration() + : score::mw::com::runtime::RuntimeConfiguration(path); + score::mw::com::runtime::InitializeRuntime(rtc); + score::mw::log::LogInfo() << "LoLa Runtime initialized!"; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/methods/methods_test_resources/common_resources.h b/score/mw/com/test/methods/methods_test_resources/common_resources.h new file mode 100644 index 000000000..572b885d1 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/common_resources.h @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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_METHODS_METHODS_TEST_RESOURCES_COMMON_RESOURCES_H +#define SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_COMMON_RESOURCES_H +/// This file contains information shared between the provider and consumer. +#include +#include +#include + +namespace score::mw::com::test +{ + +enum class CopyMode : std::uint8_t +{ + ZERO_COPY, + WITH_COPY +}; + +inline constexpr std::size_t kMaxRegisteredMethos{5U}; +inline constexpr std::int32_t kMethodResultMultiplierBase = 13; + +inline constexpr std::string_view kInstanceSpecifierSV = "methods_test_resources/MultiMethodProvider"; + +auto CreateInterprocessNotificationShmPath(std::size_t path_id) -> std::string; + +void InitializeRuntime(const std::string& path); + +} // namespace score::mw::com::test +#endif // SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_COMMON_RESOURCES_H diff --git a/score/mw/com/test/methods/methods_test_resources/config_generator.py b/score/mw/com/test/methods/methods_test_resources/config_generator.py new file mode 100644 index 000000000..fccea51a2 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/config_generator.py @@ -0,0 +1,75 @@ +import json +import sys +import os +from typing import Dict + + +def load_json(json_path: str) -> Dict: + with open(json_path, 'r') as json_file: + loaded_json = json.load(json_file) + return loaded_json + + +def save_json(json_path: str, json_data: Dict): + os.makedirs(os.path.dirname(json_path), exist_ok=True) + with open(json_path, "w") as json_file: + json.dump(json_data, json_file, indent=4, sort_keys=True) + + +def create_mw_com_config(base_config_json: dict, + asil_level_app: str, + asil_level_communication_partner: str, + app_id: int | None): + result = dict(base_config_json) + result["global"]["asil-level"] = asil_level_app + result["serviceInstances"][0]["instances"][0]["asil-level"] = asil_level_communication_partner + if app_id is not None: + result["global"]["applicationID"] = app_id + return result + + +def parse_key(arg_dict, key): + try: + return arg_dict[key] + except KeyError as e: + print(f"json string must contain '{key}' key.") + print(f"Error: {e}. Exiting.") + sys.exit(1) + + +def main(): + base_mw_com_config_path = str(sys.argv[1]) + out_mw_com_config_path = str(sys.argv[2]) + try: + arg_dict = json.loads(sys.argv[3]) + except json.decoder.JSONDecodeError as e: + print("Third command line argument must be a valid json string.") + print(f"Error: {e}. Exiting.") + sys.exit(1) + + asil_level_app = parse_key(arg_dict, "asil-level-app") + asil_level_communication_partner = parse_key( + arg_dict, "asil-level-communication-partner") + + app_id_str = parse_key(arg_dict, "applicationID") + if app_id_str == "": + app_id = None + else: + try: + app_id = int(app_id_str) + except ValueError as e: + print( + "The value of 'applicationID' key must be either empty or a string that can be converted to an integer.") + print(f"Error: {e}. Exiting.") + sys.exit(1) + + base_config_json = load_json(base_mw_com_config_path) + + config_json = create_mw_com_config( + base_config_json, asil_level_app, asil_level_communication_partner, app_id) + save_json(out_mw_com_config_path, config_json) + print("config saved") + + +if __name__ == "__main__": + main() diff --git a/score/mw/com/test/methods/methods_test_resources/config_generator_rule.bzl b/score/mw/com/test/methods/methods_test_resources/config_generator_rule.bzl new file mode 100644 index 000000000..d08612327 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/config_generator_rule.bzl @@ -0,0 +1,70 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +def _make_configs(ctx): + config_json = ctx.file.base_config_json_path + generated_configs_out = ctx.actions.declare_file(ctx.attr.out_config_name) + further_args = str(ctx.attr.further_args) + + ctx.actions.run( + executable = ctx.executable.tool, + inputs = [config_json], + outputs = [generated_configs_out], + arguments = [config_json.path, generated_configs_out.path, further_args], + ) + + return [DefaultInfo(files = depset([generated_configs_out]))] + +make_config_json = rule( + implementation = _make_configs, + attrs = { + "base_config_json_path": attr.label(allow_single_file = True), + "out_config_name": attr.string(mandatory = True), + "further_args": attr.string_dict( + default = {}, + doc = "Any list of additional arguments that can be provider to the tool specified in the 'tool' attribute.", + ), + "tool": attr.label( + default = "//score/mw/com/test/multiple_proxies/config_generator:config_generator", + executable = True, + cfg = "exec", + allow_files = True, + ), + }, +) + +def _make_default_configs_impl(name, visibility, applicationID, asil_level_app, asil_level_communication_partner, **kwargs): + out_config_name = "{}.json".format(name) + + make_config_json( + name = name, + base_config_json_path = "//score/mw/com/test/methods/methods_test_resources:base_mw_com_config_json", + out_config_name = out_config_name, + further_args = { + "asil-level-app": asil_level_app, + "asil-level-communication-partner": asil_level_communication_partner, + "applicationID": applicationID, + }, + tool = "//score/mw/com/test/methods/methods_test_resources:config_generator", + visibility = visibility, + **kwargs + ) + +make_default_configs = macro( + implementation = _make_default_configs_impl, + attrs = { + "applicationID": attr.string(configurable = False), + "asil_level_app": attr.string(configurable = False, default = "QM"), + "asil_level_communication_partner": attr.string(configurable = False, default = "QM"), + }, +) diff --git a/score/mw/com/test/methods/methods_test_resources/consumer_runner.cpp b/score/mw/com/test/methods/methods_test_resources/consumer_runner.cpp new file mode 100644 index 000000000..b34ad4587 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/consumer_runner.cpp @@ -0,0 +1,251 @@ +/******************************************************************************** + * 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/methods/methods_test_resources/consumer_runner.h" + +#include "score/mw/com/test/common_test_resources/command_line_parser.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/methods/methods_test_resources/common_resources.h" +#include "score/mw/com/test/methods/methods_test_resources/process_synchronizer.h" +#include "score/mw/com/test/methods/methods_test_resources/proxy_container.h" +#include "score/mw/com/test/methods/methods_test_resources/test_method_datatype.h" + +#include "score/mw/com/types.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ + +/// \brief create method names in the format "method0", "method1", ..., "methodN" and store them in the provided vector +auto MakeAllRegistrableMethodNames() -> std::vector +{ + std::vector method_names; + method_names.reserve(kMaxRegisteredMethos); + for (std::size_t method_id = 0U; method_id < kMaxRegisteredMethos; ++method_id) + { + method_names.emplace_back(std::string{"method"} + std::to_string(method_id)); + } + return method_names; +} + +/// \brief create a static vector of all possible method names and return method names corresponding to the method IDs +auto GetEnabledMethods(const std::vector& method_names, const std::vector& enabled_methods) + -> std::vector +{ + + std::vector enabled_method_names; + for (const auto method_id : enabled_methods) + { + // clang-format off + fail_test_if(method_id > method_names.size(), "Trying to create more methods than allowed", method_id, " is larger than ", method_names.size()); + // clang-format on + enabled_method_names.emplace_back(method_names.at(method_id)); + } + return enabled_method_names; +} + +void ValidateMethodCallResult(score::Result>& result, + std::size_t consumer_id, + std::size_t method_id, + std::int32_t input_value) +{ + if (!result.has_value()) + { + fail_test("Consumer", consumer_id, ": method", method_id, " call failed: ", result.error().Message()); + } + + const auto actual = *(result.value()); + const auto expected = input_value * (kMethodResultMultiplierBase + static_cast(method_id)); + + // clang-format off + fail_test_if(actual != expected, + "Consumer" , consumer_id , ": method" , method_id , " returned " , actual , " but expected " , expected); + // clang-format on + + std::cout << "Consumer" << consumer_id << ": method" << method_id << " returned correct value: " << actual << '\n'; +} + +// template +template +void CallMethod(Proxy& proxy, CopyMode copy_mode, std::size_t method_id, std::size_t consumer_id, std::int32_t input) +{ + std::cout << "Consumer" << consumer_id << ": Calling a method" << method_id << " with the value " << input << '\n'; + + auto& method = [&proxy, consumer_id, method_id]() -> auto& { + if (method_id == 0U) + { + return proxy.method0; + } + if (method_id == 1U) + { + return proxy.method1; + } + if (method_id == 2U) + { + return proxy.method2; + } + if (method_id == 3U) + { + return proxy.method3; + } + if (method_id == 4U) + { + return proxy.method4; + } + // clang-format off + fail_test("Consumer", consumer_id, ": Invalid method ID ", method_id, " can not be larger than ", kMaxRegisteredMethos); + // clang-format on + + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); + }(); + + auto result = [&method, copy_mode, input]() -> score::Result> { + if (copy_mode == CopyMode::ZERO_COPY) + { + auto allocated_args_result = method.Allocate(); + if (!allocated_args_result.has_value()) + { + std::cerr << "Conumer: Failiure during zero copy call. " << allocated_args_result.error() << '\n'; + return score::Unexpected(allocated_args_result.error()); + } + + auto& [arg_ptr] = allocated_args_result.value(); + *arg_ptr = input; + + return method(std::move(arg_ptr)); + } + + return method(input); + }(); + + ValidateMethodCallResult(result, consumer_id, method_id, input); +} + +} // namespace + +auto ReadCommandLineArguments(int argc, const char** argv) -> ConsumerTestConfiguration +{ + + auto args = score::mw::com::test::ParseCommandLineArguments( + argc, argv, {{"consumer_id", ""}, {"num-proxies-per-consumer", ""}, {"service-instance-manifest", ""}}); + + ConsumerTestConfiguration test_configuration{}; + + const auto consumer_id_opt = GetValueIfProvided(args, "consumer_id"); + if (!consumer_id_opt.has_value()) + { + fail_test("Consumer: --consumer_id parameter is not optional.\n"); + } + test_configuration.consumer_id = consumer_id_opt.value(); + if (test_configuration.consumer_id > 3) + { + fail_test("Consumer: consumer_id value ", test_configuration.consumer_id, " is invalid (must be between 0-2)"); + } + + const auto num_proxies_per_process_opt = GetValueIfProvided(args, "num-proxies-per-consumer"); + if (num_proxies_per_process_opt.has_value()) + { + test_configuration.num_proxies_per_process = num_proxies_per_process_opt.value(); + if (!(test_configuration.num_proxies_per_process > 0)) + { + fail_test("Consumer: num-proxies-per-consumer value ", + test_configuration.num_proxies_per_process, + " must be greater than 0."); + } + } + + std::string defuault_pat{"./etc/mw_com_config.json"}; + test_configuration.service_instance_manifest = + GetValueIfProvided(args, "service-instance-manifest").value_or(defuault_pat); + + return test_configuration; +} + +void run_consumer(const ConsumerTestConfiguration& test_configuration, + const std::vector& enabled_method_ids) +{ + std::cout << "Starting consumer with ID: " << test_configuration.consumer_id << "\n"; + + const auto notification_path = CreateInterprocessNotificationShmPath(test_configuration.consumer_id); + + auto process_synchronizer = ProcessSynchronizer::Create(notification_path); + if (!process_synchronizer.has_value()) + { + fail_test("Consumer", test_configuration.consumer_id, ": Could not create the ProcessSynchronizer.\n"); + } + + // Precreation of this vector of strings is necessary because proxy container expects a vector of string views. Thus + // the GetEnabledMethods function needs to return string views to valid strings. Thus this names need to be created + // in the outer scope of proxy_runner and must outlive the proxy, created by proxy container. + auto method_names = MakeAllRegistrableMethodNames(); + + auto proxy_runner = [&enabled_method_ids, &method_names](const ConsumerTestConfiguration& test_configuration, + std::size_t proxy_id) -> void { + auto instance_specifier_result = InstanceSpecifier::Create(std::string{kInstanceSpecifierSV}); + // clang-format off + fail_test_if(!instance_specifier_result.has_value(), + "Consumer", test_configuration.consumer_id, ": Proxy", proxy_id, " Got Invalid instance specifier"); + // clang-format on + + ProxyContainer proxy_container; + + if (bool proxy_creation_succeeded = proxy_container.CreateProxy( + instance_specifier_result.value(), GetEnabledMethods(method_names, enabled_method_ids)); + !proxy_creation_succeeded) + { + fail_test("Consumer", test_configuration.consumer_id, ": Could not create the proxy with ID ", proxy_id); + } + + std::cout << "Consumer" << test_configuration.consumer_id << ": Proxy " << proxy_id + << " created successfully. Calling Methods\n"; + + auto& proxy = proxy_container.GetProxy(); + for (const auto& method_id : enabled_method_ids) + { + const auto some_random_input_value = static_cast(proxy_id + method_id); + CopyMode copy_mode = (method_id % 2 == 0) ? CopyMode::ZERO_COPY : CopyMode::WITH_COPY; + CallMethod(proxy, copy_mode, method_id, test_configuration.consumer_id, some_random_input_value); + } + }; + + { + std::vector threads; + threads.resize(test_configuration.num_proxies_per_process); + for (std::size_t proxy_id = 0U; proxy_id < test_configuration.num_proxies_per_process; ++proxy_id) + { + threads.emplace_back(proxy_runner, test_configuration, proxy_id); + } + } + + std::cout << "Consumer" << test_configuration.consumer_id << ": Exiting with result.\n"; + + process_synchronizer->Notify(); + std::cout << "Consumer" << test_configuration.consumer_id << ": Notified provider of completion.\n"; + + std::cout << "Consumer" << test_configuration.consumer_id << ": All threads have completed. Exiting.\n"; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/methods/methods_test_resources/consumer_runner.h b/score/mw/com/test/methods/methods_test_resources/consumer_runner.h new file mode 100644 index 000000000..98702f2ed --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/consumer_runner.h @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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_METHODS_METHODS_TEST_RESOURCES_CONSUMER_RUNNER_H +#define SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_CONSUMER_RUNNER_H +#include +#include +#include + +namespace score::mw::com::test +{ + +struct ConsumerTestConfiguration +{ + std::size_t consumer_id{0}; + std::size_t num_proxies_per_process{1}; + std::string service_instance_manifest; +}; + +auto ReadCommandLineArguments(int argc, const char** argv) -> ConsumerTestConfiguration; +void run_consumer(const ConsumerTestConfiguration& test_configuration, + const std::vector& enabled_method_ids); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_CONSUMER_RUNNER_H diff --git a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp index 814be84db..2be6ba52f 100644 --- a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp +++ b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.cpp @@ -22,12 +22,14 @@ #include #include #include +#include namespace score::mw::com::test { std::optional ProcessSynchronizer::Create(const std::string& interprocess_notification_shm_path) { + std::thread bla; auto interprocess_notification_result = SharedMemoryObjectCreator::CreateOrOpenObject(interprocess_notification_shm_path); if (!interprocess_notification_result.has_value()) @@ -42,6 +44,23 @@ std::optional ProcessSynchronizer::Create(const std::string return std::optional{std::in_place_t{}, std::move(interprocess_notification_result).value()}; } +auto ProcessSynchronizer::CreateUniquePtr(const std::string& interprocess_notification_shm_path) + -> std::unique_ptr +{ + auto interprocess_notification_result = + SharedMemoryObjectCreator::CreateOrOpenObject(interprocess_notification_shm_path); + if (!interprocess_notification_result.has_value()) + { + std::stringstream ss; + ss << "Consumer: Creating or opening interprocess notification object failed:" + << interprocess_notification_result.error(); + std::cerr << ss.str() << std::endl; + return nullptr; + } + + return std::make_unique(std::move(interprocess_notification_result).value()); +} + ProcessSynchronizer::ProcessSynchronizer( SharedMemoryObjectCreator interprocess_notifier_creator) : interprocess_notifier_creator_{std::move(interprocess_notifier_creator)}, diff --git a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h index 8a30c2897..0afdb0008 100644 --- a/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h +++ b/score/mw/com/test/methods/methods_test_resources/process_synchronizer.h @@ -31,6 +31,8 @@ class ProcessSynchronizer { public: static std::optional Create(const std::string& interprocess_notification_shm_path); + static auto CreateUniquePtr(const std::string& interprocess_notification_shm_path) + -> std::unique_ptr; ProcessSynchronizer(SharedMemoryObjectCreator interprocess_notifier_creator); diff --git a/score/mw/com/test/methods/methods_test_resources/provider_runner.cpp b/score/mw/com/test/methods/methods_test_resources/provider_runner.cpp new file mode 100644 index 000000000..5f889bd62 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/provider_runner.cpp @@ -0,0 +1,201 @@ +/******************************************************************************** + * 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/methods/methods_test_resources/provider_runner.h" + +#include "score/mw/com/test/common_test_resources/command_line_parser.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/methods/methods_test_resources/common_resources.h" +#include "score/mw/com/test/methods/methods_test_resources/process_synchronizer.h" +#include "score/mw/com/test/methods/methods_test_resources/test_method_datatype.h" + +#include "score/mw/com/types.h" +#include "score/result/result.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ +std::array, kMaxRegisteredMethos> gMethodCallCounts{0U, 0U, 0U, 0U, 0U}; +std::array, kMaxRegisteredMethos> gConcurrentCalls{0U, 0U, 0U, 0U, 0U}; +std::array, kMaxRegisteredMethos> gOccuranceOfConcurrentCalls{0U, 0U, 0U, 0U, 0U}; + +using InpArgType = MultiMethodSkeleton::InpArgType; +using ReturnType = MultiMethodSkeleton::ReturnType; + +auto handler_maker(InpArgType idx) +{ + return [idx](InpArgType val) -> ReturnType { + const auto count = gMethodCallCounts.at(idx).fetch_add(1) + 1; + auto current_call_numner = gConcurrentCalls.at(idx).fetch_add(1); + if (current_call_numner > 0) + { + gOccuranceOfConcurrentCalls.at(idx).fetch_add(1); + } + + // Simulate some processing work to increase the time window for concurrent execution + // This makes it more likely that multiple handlers will be executing at the same time + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + std::cout << "Provider: method" << idx << " called (count=" << count << ") with val=" << val << '\n'; + gConcurrentCalls.at(idx).fetch_add(-1); + return val * (kMethodResultMultiplierBase + idx); + }; +} + +void RegisterMethodHandlers(MultiMethodSkeleton& skeleton) +{ + skeleton.method0.RegisterHandler(handler_maker(0)); + skeleton.method1.RegisterHandler(handler_maker(1)); + skeleton.method2.RegisterHandler(handler_maker(2)); + skeleton.method3.RegisterHandler(handler_maker(3)); + skeleton.method4.RegisterHandler(handler_maker(4)); + std::cout << "Provider: All 5 method handlers registered successfully.\n"; +} + +auto CreateAndOfferSkeleton() -> score::Result +{ + auto instance_specifier_result = InstanceSpecifier::Create(std::string{kInstanceSpecifierSV}); + if (!instance_specifier_result.has_value()) + { + std::cerr << "Provider: Invalid instance specifier\n"; + return score::MakeUnexpected(instance_specifier_result.error()); + } + + auto skeleton_result = MultiMethodSkeleton::Create(instance_specifier_result.value()); + if (!skeleton_result.has_value()) + { + std::cerr << "Provider: Could not create skeleton: " << skeleton_result.error().Message() << '\n'; + return skeleton_result; + } + + auto& skeleton = skeleton_result.value(); + RegisterMethodHandlers(skeleton); + + auto offer_result = skeleton.OfferService(); + if (!offer_result.has_value()) + { + std::cerr << "Provider: Could not offer service: " << offer_result.error().Message() << '\n'; + return score::MakeUnexpected(offer_result.error()); + } + + std::cout << "Provider: Service offered successfully\n"; + return skeleton_result; +} +} // namespace + +void run_provider(const score::cpp::stop_token& stop_token, + const std::vector& expected_method_call_count, + std::size_t num_consumer) +{ + auto skeleton_result = CreateAndOfferSkeleton(); + fail_test_if(!skeleton_result.has_value(), "Provider: Failed to create and offer skeleton."); + + std::vector> process_synchronizers; + for (std::size_t i{0}; i < num_consumer; ++i) + { + process_synchronizers.emplace_back( + ProcessSynchronizer::CreateUniquePtr(CreateInterprocessNotificationShmPath(i))); + + fail_test_if(!process_synchronizers.at(i), "Provider: Could not create ProcessSynchronizer ", i); + } + + std::cout << "Provider: Ready for method calls from multiple proxies\n"; + + for (std::size_t i{0}; i < process_synchronizers.size(); ++i) + { + std::cout << "Provider: Waiting for Consumer" << i << " to finish...\n"; + if (!process_synchronizers.at(i)->WaitWithAbort(stop_token)) + { + fail_test("Provider: WaitWithAbort for Consumer", i, " was stopped by stop_token\n"); + } + } + std::cout << "Provider: All consumers have finished...\n\n"; + + std::size_t idx{0}; + for (const auto& method_call_count : gMethodCallCounts) + { + std::cout << "idx: " << idx << " count: " << method_call_count.load(); + std::cout << " Recorded number of concurrent calls: " << gOccuranceOfConcurrentCalls.at(idx) << "\n"; + if (method_call_count.load() != expected_method_call_count.at(idx)) + { + std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; + std::cout << "Provider: method" << idx << " called unexpected amount of times.\n"; + std::cout << "idx: " << idx << " count: " << method_call_count.load() << "\n"; + std::cout << "Recorded number of concurrent calls: " << gOccuranceOfConcurrentCalls.at(idx) << "\n"; + std::cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; + fail_test(); + } + idx++; + } + std::cout << "Provider: Shutting down.\n"; +} + +auto ParseConsumerRepetitionFromArgs(int argc, const char** argv, std::size_t num_consumers) + -> std::pair, CommandLineArgsMapType> +{ + std::vector consumer_repetition{}; + + std::vector> arg_names_and_descriptions{}; + for (std::size_t consumer_id = 0; consumer_id < num_consumers; ++consumer_id) + { + const auto arg_name = std::string{"num-proxies-per-consumer"} + std::to_string(consumer_id); + const auto arg_description = + std::string{"number of times consumer"} + std::to_string(consumer_id) + std::string{" calls its methods"}; + arg_names_and_descriptions.emplace_back(arg_name, arg_description); + } + + auto args = score::mw::com::test::ParseCommandLineArguments(argc, argv, arg_names_and_descriptions); + + // for (std::size_t consumer_id{0}; consumer_id < num_consumer; ++consumer_id) + for (auto& [arg_name, _] : arg_names_and_descriptions) + { + auto arg_value_opt = GetValueIfProvided(args, arg_name); + if (!arg_value_opt.has_value()) + { + std::stringstream strstr; + strstr << "Provider: --" << arg_name << " parameter is not optional.\n"; + fail_test(strstr.str()); + } + consumer_repetition.emplace_back(arg_value_opt.value()); + } + return {consumer_repetition, args}; +} + +auto CalculateExpectedMethodCallCounts(const std::vector& consumer_repetition, + std::vector> enabled_method_ids) + -> std::vector +{ + std::vector expected_method_call_count(kMaxRegisteredMethos, 0U); + + for (std::size_t consumer_id{0}; consumer_id < enabled_method_ids.size(); ++consumer_id) + { + for (const auto method_id : enabled_method_ids.at(consumer_id)) + { + expected_method_call_count.at(method_id) += consumer_repetition.at(consumer_id); + } + } + return expected_method_call_count; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/methods/methods_test_resources/provider_runner.h b/score/mw/com/test/methods/methods_test_resources/provider_runner.h new file mode 100644 index 000000000..c1f419a2e --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/provider_runner.h @@ -0,0 +1,38 @@ +/******************************************************************************** + * 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_METHODS_METHODS_TEST_RESOURCES_PROVIDER_RUNNER_H +#define SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_PROVIDER_RUNNER_H +#include "score/mw/com/test/common_test_resources/command_line_parser.h" + +#include + +#include +#include + +namespace score::mw::com::test +{ + +void run_provider(const score::cpp::stop_token& stop_token, + const std::vector& expected_method_call_count, + std::size_t num_consumers); + +auto ParseConsumerRepetitionFromArgs(int argc, const char** argv, std::size_t num_consumers) + -> std::pair, CommandLineArgsMapType>; + +auto CalculateExpectedMethodCallCounts(const std::vector& consumer_repetition, + std::vector> enabled_method_ids) + -> std::vector; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_PROVIDER_RUNNER_H diff --git a/score/mw/com/test/methods/methods_test_resources/test_method_datatype.cpp b/score/mw/com/test/methods/methods_test_resources/test_method_datatype.cpp new file mode 100644 index 000000000..f7ecb0d62 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/test_method_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/methods/methods_test_resources/test_method_datatype.h" diff --git a/score/mw/com/test/methods/methods_test_resources/test_method_datatype.h b/score/mw/com/test/methods/methods_test_resources/test_method_datatype.h new file mode 100644 index 000000000..e47309c93 --- /dev/null +++ b/score/mw/com/test/methods/methods_test_resources/test_method_datatype.h @@ -0,0 +1,48 @@ +/******************************************************************************** + * 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_METHODS_METHODS_TEST_RESOURCES_TEST_METHOD_DATATYPE_H +#define SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_TEST_METHOD_DATATYPE_H + +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +/// \brief Test interface with multiple methods for selective enabling by different proxies +template +class MultiMethodInterface : public T::Base +{ + public: + using T::Base::Base; + using ReturnType = std::int32_t; + using InpArgType = std::int32_t; + using MethodType = ReturnType(InpArgType); + + typename T::template Method method0{*this, "method0"}; + typename T::template Method method1{*this, "method1"}; + typename T::template Method method2{*this, "method2"}; + typename T::template Method method3{*this, "method3"}; + typename T::template Method method4{*this, "method4"}; +}; + +/// \brief Proxy side of the test service +using MultiMethodProxy = score::mw::com::AsProxy; + +/// \brief Skeleton side of the test service +using MultiMethodSkeleton = score::mw::com::AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_METHODS_METHODS_TEST_RESOURCES_TEST_METHOD_DATATYPE_H diff --git a/score/mw/com/test/methods/mixed_criticality/BUILD b/score/mw/com/test/methods/mixed_criticality/BUILD new file mode 100644 index 000000000..a7699b5eb --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/BUILD @@ -0,0 +1,117 @@ +# ******************************************************************************* +# 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("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//score/mw/com/test:pkg_application.bzl", "pkg_application") +load("//score/mw/com/test/methods/methods_test_resources:config_generator_rule.bzl", "make_default_configs") + +make_default_configs( + name = "qm_qm_mw_com_config", + asil_level_app = "QM", + asil_level_communication_partner = "QM", +) + +make_default_configs( + name = "qm_asil_b_mw_com_config", + asil_level_app = "QM", + asil_level_communication_partner = "B", +) + +make_default_configs( + name = "asil_b_qm_mw_com_config", + asil_level_app = "B", + asil_level_communication_partner = "QM", +) + +make_default_configs( + name = "asil_b_asil_b_mw_com_config", + asil_level_app = "B", + asil_level_communication_partner = "B", +) + +cc_library( + name = "common_resources", + srcs = ["common_resources.cpp"], + hdrs = ["common_resources.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/mw/com/test/methods:__subpackages__"], + deps = [ + "//score/mw/com/test/common_test_resources:command_line_parser", + "//score/mw/com/test/common_test_resources:fail_test", + ], +) + +cc_binary( + name = "provider_main", + srcs = ["provider_main.cpp"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":common_resources", + "//score/mw/com/test/common_test_resources:assert_handler", + "//score/mw/com/test/methods/methods_test_resources:provider_runner", + ], +) + +cc_binary( + name = "consumer_main", + srcs = ["consumer_main.cpp"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":common_resources", + "//score/mw/com/test/common_test_resources:assert_handler", + "//score/mw/com/test/methods/methods_test_resources:consumer_runner", + ], +) + +# --- Packages --- + +pkg_application( + name = "provider-pkg", + app_name = "ProviderApp", + bin = [":provider_main"], + etc = [ + ":qm_qm_mw_com_config", + ":qm_asil_b_mw_com_config", + ":asil_b_qm_mw_com_config", + ":asil_b_asil_b_mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/mixed_criticality/integration_test:__pkg__", + ], +) + +pkg_application( + name = "consumer-pkg", + app_name = "ConsumerApp", + bin = [":consumer_main"], + etc = [ + ":qm_qm_mw_com_config", + ":qm_asil_b_mw_com_config", + ":asil_b_qm_mw_com_config", + ":asil_b_asil_b_mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/mixed_criticality/integration_test:__pkg__", + ], +) + +test_suite( + name = "component_tests", + tests = [ + "//score/mw/com/test/methods/mixed_criticality/integration_test:component_tests", + ], +) diff --git a/score/mw/com/test/methods/mixed_criticality/common_resources.cpp b/score/mw/com/test/methods/mixed_criticality/common_resources.cpp new file mode 100644 index 000000000..2a8d9e0ca --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/common_resources.cpp @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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/methods/mixed_criticality/common_resources.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 +{ + +auto GetServiceInstanceManifestPath(int argc, const char** argv) -> std::string +{ + std::string service_instance_manifest_name = "service-instance-manifest"; + + auto args = ParseCommandLineArguments(argc, argv, {{service_instance_manifest_name, ""}}); + + auto get_value_or_crash = [&args](const std::string& name) -> std::string { + return GetValueIfProvided(args, name) + .or_else([&name](auto&&) -> score::Result { + fail_test("Parser: could not parse ", name, " from command line arguments!"); + return score::Result{}; + }) + .value(); + }; + + return get_value_or_crash(service_instance_manifest_name); +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/methods/mixed_criticality/common_resources.h b/score/mw/com/test/methods/mixed_criticality/common_resources.h new file mode 100644 index 000000000..eeca5e30a --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/common_resources.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_METHODS_MIXED_CRITICALITY_COMMON_RESOURCES_H +#define SCORE_MW_COM_TEST_METHODS_MIXED_CRITICALITY_COMMON_RESOURCES_H +/// This file contains information shared between the provider and consumer. + +#include +#include +namespace score::mw::com::test +{ + +const std::vector> kEnabledMethodIDs{std::vector{0U, 1U, 2U, 3U, 4U}}; + +const std::size_t kConfiguredNumberOfConsumers{kEnabledMethodIDs.size()}; +auto GetServiceInstanceManifestPath(int argc, const char** argv) -> std::string; + +} // namespace score::mw::com::test +#endif // SCORE_MW_COM_TEST_METHODS_MIXED_CRITICALITY_COMMON_RESOURCES_H diff --git a/score/mw/com/test/methods/mixed_criticality/consumer_main.cpp b/score/mw/com/test/methods/mixed_criticality/consumer_main.cpp new file mode 100644 index 000000000..0586f11db --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/consumer_main.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 + ********************************************************************************/ + +/// \brief QM consumer main entry point for mixed criticality method tests. +/// This consumer runs at QM ASIL level and calls methods on a QM provider. + +#include "score/mw/com/runtime.h" +#include "score/mw/com/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/methods/methods_test_resources/common_resources.h" +#include "score/mw/com/test/methods/methods_test_resources/consumer_runner.h" +#include "score/mw/com/test/methods/mixed_criticality/common_resources.h" + +#include +#include + +namespace +{ +} // namespace + +int main(int argc, const char** argv) +{ + auto service_instance_manifest = score::mw::com::test::GetServiceInstanceManifestPath(argc, argv); + score::mw::com::test::InitializeRuntime(service_instance_manifest); + + score::mw::com::test::SetupAssertHandler(); + score::cpp::stop_source stop_source{}; + + auto consumer_id{0U}; + auto num_proxies_per_process{1U}; + score::mw::com::test::ConsumerTestConfiguration test_config{ + consumer_id, num_proxies_per_process, service_instance_manifest}; + score::mw::com::test::run_consumer(test_config, score::mw::com::test::kEnabledMethodIDs[0]); + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/mixed_criticality/integration_test/BUILD b/score/mw/com/test/methods/mixed_criticality/integration_test/BUILD new file mode 100644 index 000000000..9a14afc9f --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/integration_test/BUILD @@ -0,0 +1,74 @@ +# ******************************************************************************* +# 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:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +pkg_tar( + name = "filesystem", + deps = [ + "//score/mw/com/test/methods/mixed_criticality:consumer-pkg", + "//score/mw/com/test/methods/mixed_criticality:provider-pkg", + ], +) + +py_library( + name = "test_fixture", + srcs = ["test_fixture.py"], +) + +integration_test( + name = "qm_consumer_qm_provider_test", + srcs = [ + "qm_consumer_qm_provider_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "qm_consumer_asilb_provider_test", + srcs = [ + "qm_consumer_asilb_provider_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "asilb_consumer_qm_provider_test", + srcs = [ + "asilb_consumer_qm_provider_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "asilb_consumer_asilb_provider_test", + srcs = [ + "asilb_consumer_asilb_provider_test.py", + ":test_fixture", + ], + filesystem = ":filesystem", +) + + +test_suite( + name = "component_tests", + tests = [ + ":asilb_consumer_asilb_provider_test", + ":asilb_consumer_qm_provider_test", + ":qm_consumer_asilb_provider_test", + ":qm_consumer_qm_provider_test", + ], +) diff --git a/score/mw/com/test/methods/mixed_criticality/integration_test/asilb_consumer_asilb_provider_test.py b/score/mw/com/test/methods/mixed_criticality/integration_test/asilb_consumer_asilb_provider_test.py new file mode 100644 index 000000000..893964509 --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/integration_test/asilb_consumer_asilb_provider_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 Criticality, call_consumer_and_provider + + +def test_mixed_criticality_consumer_provider(sut): + call_consumer_and_provider( + sut, Criticality.ASILB, Criticality.ASILB, Criticality.ASILB) diff --git a/score/mw/com/test/methods/mixed_criticality/integration_test/asilb_consumer_qm_provider_test.py b/score/mw/com/test/methods/mixed_criticality/integration_test/asilb_consumer_qm_provider_test.py new file mode 100644 index 000000000..16147e203 --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/integration_test/asilb_consumer_qm_provider_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 Criticality, call_consumer_and_provider + + +def test_mixed_criticality_consumer_provider(sut): + print("Starting asilb consumer and qm provider...") + call_consumer_and_provider( + sut, Criticality.ASILB, Criticality.QM, Criticality.QM, timeout_sec=120) diff --git a/score/mw/com/test/methods/mixed_criticality/integration_test/qm_consumer_asilb_provider_test.py b/score/mw/com/test/methods/mixed_criticality/integration_test/qm_consumer_asilb_provider_test.py new file mode 100644 index 000000000..6d6f9c6c9 --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/integration_test/qm_consumer_asilb_provider_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 Criticality, call_consumer_and_provider + + +def test_mixed_criticality_consumer_provider(sut): + call_consumer_and_provider( + sut, Criticality.QM, Criticality.QM, Criticality.ASILB) diff --git a/score/mw/com/test/methods/mixed_criticality/integration_test/qm_consumer_qm_provider_test.py b/score/mw/com/test/methods/mixed_criticality/integration_test/qm_consumer_qm_provider_test.py new file mode 100644 index 000000000..8593ce47f --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/integration_test/qm_consumer_qm_provider_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 Criticality, call_consumer_and_provider + + +def test_mixed_criticality_consumer_provider(sut): + call_consumer_and_provider( + sut, Criticality.QM, Criticality.QM, Criticality.QM) diff --git a/score/mw/com/test/methods/mixed_criticality/integration_test/test_fixture.py b/score/mw/com/test/methods/mixed_criticality/integration_test/test_fixture.py new file mode 100644 index 000000000..905279d55 --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/integration_test/test_fixture.py @@ -0,0 +1,56 @@ +# ******************************************************************************* +# 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 Enum + + +class Criticality(Enum): + ASILB = "asil_b" + QM = "qm" + + +def call_consumer_and_provider(sut, + consumer_criticality: Criticality, + consumer_expects_criticality: Criticality, + provider_criticality: Criticality, + timeout_sec=60): + """ + Validates: + - Method registration on a skeleton with a given provider_criticality + - Method invocation from a proxy with a given consumer_criticality + - Correct parameter passing and result return + - Both copy and zero-copy call semantics are tested + Note: every even numbered method call is zero-copy, thus more + than two methods need to be registered, in the used mw_com_config.json + """ + # consumer app may be asil b but consume asil qm provider thus two separate + # Criticality values need to be specified here + consumer_config_name = ( + f"{consumer_criticality.value}_{consumer_expects_criticality.value}_mw_com_config.json") + + # this is not a typo! For a provider the global app criticality and the + # offered criticality is always the same + provider_config_name = ( + f"{provider_criticality.value}_{provider_criticality.value}_mw_com_config.json") + + print(f"Starting consumer and provider with criticality { + consumer_criticality.value} and {provider_criticality.value}") + consumer_args = f"--service-instance-manifest ./etc/{consumer_config_name}" + provider_args = f"--service-instance-manifest ./etc/{provider_config_name}" + + with sut.start_process(f"bin/consumer_main {consumer_args}", + cwd="/opt/ConsumerApp") as consumer: + with sut.start_process(f"bin/provider_main {provider_args}", + cwd="/opt/ProviderApp") as provider: + assert provider.wait_for_exit(timeout_sec) == 0 + assert consumer.wait_for_exit(timeout_sec) == 0 diff --git a/score/mw/com/test/methods/mixed_criticality/provider_main.cpp b/score/mw/com/test/methods/mixed_criticality/provider_main.cpp new file mode 100644 index 000000000..7ef8fbfaa --- /dev/null +++ b/score/mw/com/test/methods/mixed_criticality/provider_main.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 + ********************************************************************************/ + +/// \brief ASIL-B provider main entry point for mixed criticality method tests. +/// This provider runs at ASIL-B level and offers a service with add_numbers method. +/// An ASIL-B provider automatically serves both QM and ASIL-B consumers. + +#include "score/mw/com/runtime.h" + +#include "score/mw/com/test/common_test_resources/assert_handler.h" +#include "score/mw/com/test/methods/methods_test_resources/common_resources.h" +#include "score/mw/com/test/methods/methods_test_resources/provider_runner.h" +#include "score/mw/com/test/methods/mixed_criticality/common_resources.h" + +#include +#include + +int main(int argc, const char** argv) +{ + std::cout << "Provider: Starting provider process...\n"; + auto service_instance_manifest_path = score::mw::com::test::GetServiceInstanceManifestPath(argc, argv); + + score::mw::com::test::InitializeRuntime(service_instance_manifest_path); + + // we have one consumer which spawns one proxy thus the consumer repetition is a vector of lenght 1 with element + // value 1. This information is required by the provider to calculate how many times a proxy will call all of it's + // registered methods. + constexpr std::size_t num_consumer{1U}; + constexpr std::size_t num_proxy_per_consumer{1U}; + std::vector consumer_repetition(num_consumer, num_proxy_per_consumer); + auto expected_method_call_count = score::mw::com::test::CalculateExpectedMethodCallCounts( + consumer_repetition, score::mw::com::test::kEnabledMethodIDs); + + score::mw::com::test::SetupAssertHandler(); + score::cpp::stop_source stop_source{}; + score::mw::com::test::run_provider(stop_source.get_token(), expected_method_call_count, num_consumer); +} diff --git a/score/mw/com/test/methods/multiple_proxies/BUILD b/score/mw/com/test/methods/multiple_proxies/BUILD new file mode 100644 index 000000000..81a9d6afd --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/BUILD @@ -0,0 +1,171 @@ +# ******************************************************************************* +# 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("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//score/mw/com/test:pkg_application.bzl", "pkg_application") +load("//score/mw/com/test/methods/methods_test_resources:config_generator_rule.bzl", "make_default_configs") + +make_default_configs( + name = "mw_com_config", + applicationID = "12", +) + +make_default_configs( + name = "consumer0_mw_com_config", + applicationID = "0", +) + +make_default_configs( + name = "consumer1_mw_com_config", + applicationID = "1", +) + +make_default_configs( + name = "consumer2_mw_com_config", + applicationID = "2", +) + +make_default_configs( + name = "consumer3_mw_com_config", + applicationID = "3", +) + +cc_library( + name = "common_resources", + srcs = ["common_resources.cpp"], + hdrs = ["common_resources.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/mw/com/test/methods:__subpackages__"], + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/methods/methods_test_resources:common_resources", + ], +) + +cc_binary( + name = "provider_main", + srcs = ["provider_main.cpp"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":common_resources", + "//score/mw/com/test/methods/methods_test_resources:provider_runner", + ], +) + +cc_binary( + name = "consumer_main", + srcs = ["consumer_main.cpp"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":common_resources", + "//score/mw/com/test/methods/methods_test_resources:consumer_runner", + ], +) + +cc_binary( + name = "combined_consumer_provider_main", + srcs = ["combined_consumer_provider_main.cpp"], + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + deps = [ + ":common_resources", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/methods/methods_test_resources:consumer_runner", + "//score/mw/com/test/methods/methods_test_resources:provider_runner", + ], +) + +pkg_application( + name = "provider-pkg", + app_name = "ProviderApp", + bin = [":provider_main"], + etc = [ + ":mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:__pkg__", + ], +) + +pkg_application( + name = "consumer0-pkg", + app_name = "Consumer0App", + bin = [":consumer_main"], + etc = [ + ":consumer0_mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:__pkg__", + ], +) + +pkg_application( + name = "consumer1-pkg", + app_name = "Consumer1App", + bin = [":consumer_main"], + etc = [ + ":consumer1_mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:__pkg__", + ], +) + +pkg_application( + name = "consumer2-pkg", + app_name = "Consumer2App", + bin = [":consumer_main"], + etc = [ + ":consumer2_mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:__pkg__", + ], +) + +pkg_application( + name = "consumer3-pkg", + app_name = "Consumer3App", + bin = [":consumer_main"], + etc = [ + ":consumer3_mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:__pkg__", + ], +) + +pkg_application( + name = "combined-provider-consumer-pkg", + app_name = "CombinedConsumerProviderApp", + bin = [":combined_consumer_provider_main"], + etc = [ + ":mw_com_config", + ], + visibility = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:__pkg__", + ], +) + +test_suite( + name = "component_tests", + tests = [ + "//score/mw/com/test/methods/multiple_proxies/integration_test:component_tests", + ], +) diff --git a/score/mw/com/test/methods/multiple_proxies/combined_consumer_provider_main.cpp b/score/mw/com/test/methods/multiple_proxies/combined_consumer_provider_main.cpp new file mode 100644 index 000000000..4fb48daad --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/combined_consumer_provider_main.cpp @@ -0,0 +1,82 @@ +/******************************************************************************** + * 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/command_line_parser.h" +#include "score/mw/com/test/methods/methods_test_resources/common_resources.h" +#include "score/mw/com/test/methods/methods_test_resources/consumer_runner.h" +#include "score/mw/com/test/methods/methods_test_resources/provider_runner.h" + +#include + +#include "score/mw/com/test/methods/multiple_proxies/common_resources.h" +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +struct CombinedTestConfiguration +{ + std::vector consumer_repetition; + std::string service_instance_manifest; +}; +auto ReadCombinedCommandLineArguments(int argc, const char** argv, std::size_t num_consumers) + -> CombinedTestConfiguration +{ + auto [consumer_repetition, args] = ParseConsumerRepetitionFromArgs(argc, argv, num_consumers); + + std::string defuault_pat{"./etc/mw_com_config.json"}; + return CombinedTestConfiguration{ + consumer_repetition, GetValueIfProvided(args, "service-instance-manifest").value_or(defuault_pat)}; +} + +} // namespace +} // namespace score::mw::com::test + +int main(int argc, const char** argv) +{ + auto num_consumers = score::mw::com::test::kConfiguredNumberOfConsumers; + + score::mw::com::test::CombinedTestConfiguration combined_test_configuration{ + score::mw::com::test::ReadCombinedCommandLineArguments(argc, argv, num_consumers)}; + auto expected_method_call_count = score::mw::com::test::CalculateExpectedMethodCallCounts( + combined_test_configuration.consumer_repetition, score::mw::com::test::kEnabledMethodIDs); + + std::cout << "service-instance-manifest: " << combined_test_configuration.service_instance_manifest << "\n"; + + score::mw::com::test::InitializeRuntime(combined_test_configuration.service_instance_manifest); + + // ------ Start the Provider Threads + std::vector consumer_threads; + + for (std::size_t consumer_id{0}; consumer_id < num_consumers; ++consumer_id) + { + score::mw::com::test::ConsumerTestConfiguration consumer_test_configuration{}; + consumer_test_configuration.consumer_id = consumer_id; + consumer_test_configuration.num_proxies_per_process = + combined_test_configuration.consumer_repetition.at(consumer_id); + consumer_test_configuration.service_instance_manifest = combined_test_configuration.service_instance_manifest; + auto enabled_method_ids = score::mw::com::test::kEnabledMethodIDs.at(consumer_id); + consumer_threads.emplace_back( + score::mw::com::test::run_consumer, consumer_test_configuration, enabled_method_ids); + } + + // ------ Start the consumer + + score::cpp::stop_source stop_source_{}; + score::mw::com::test::run_provider(stop_source_.get_token(), expected_method_call_count, num_consumers); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/multiple_proxies/common_resources.cpp b/score/mw/com/test/methods/multiple_proxies/common_resources.cpp new file mode 100644 index 000000000..94fa7ff75 --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/common_resources.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/test/methods/multiple_proxies/common_resources.h" diff --git a/score/mw/com/test/methods/multiple_proxies/common_resources.h b/score/mw/com/test/methods/multiple_proxies/common_resources.h new file mode 100644 index 000000000..b84af93e4 --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/common_resources.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_METHODS_MULTIPLE_PROXIES_COMMON_RESOURCES_H +#define SCORE_MW_COM_TEST_METHODS_MULTIPLE_PROXIES_COMMON_RESOURCES_H +#include + +namespace score::mw::com::test +{ + +const std::vector> kEnabledMethodIDs{std::vector{0U, 1U}, + {1U, 2U}, + {3U, 4U}, + {0U, 2U, 3U}}; + +const std::size_t kConfiguredNumberOfConsumers{kEnabledMethodIDs.size()}; + +} // namespace score::mw::com::test +#endif // SCORE_MW_COM_TEST_METHODS_MULTIPLE_PROXIES_COMMON_RESOURCES_H diff --git a/score/mw/com/test/methods/multiple_proxies/consumer_main.cpp b/score/mw/com/test/methods/multiple_proxies/consumer_main.cpp new file mode 100644 index 000000000..4ee64a02f --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/consumer_main.cpp @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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/methods/methods_test_resources/common_resources.h" +#include "score/mw/com/test/methods/methods_test_resources/consumer_runner.h" +#include "score/mw/com/test/methods/multiple_proxies/common_resources.h" +#include + +int main(int argc, const char** argv) +{ + const auto test_configuration = score::mw::com::test::ReadCommandLineArguments(argc, argv); + + const auto& enabled_method_ids = score::mw::com::test::kEnabledMethodIDs.at(test_configuration.consumer_id); + score::mw::com::test::InitializeRuntime(test_configuration.service_instance_manifest); + score::mw::com::test::run_consumer(test_configuration, enabled_method_ids); + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/multiple_proxies/integration_test/BUILD b/score/mw/com/test/methods/multiple_proxies/integration_test/BUILD new file mode 100644 index 000000000..572667099 --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/integration_test/BUILD @@ -0,0 +1,61 @@ +# ******************************************************************************* +# 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:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +pkg_tar( + name = "filesystem_different_process", + deps = [ + "//score/mw/com/test/methods/multiple_proxies:consumer0-pkg", + "//score/mw/com/test/methods/multiple_proxies:consumer1-pkg", + "//score/mw/com/test/methods/multiple_proxies:consumer2-pkg", + "//score/mw/com/test/methods/multiple_proxies:consumer3-pkg", + "//score/mw/com/test/methods/multiple_proxies:provider-pkg", + ], +) + +pkg_tar( + name = "filesystem_same_process", + deps = [ + "//score/mw/com/test/methods/multiple_proxies:combined-provider-consumer-pkg", + ], +) + +integration_test( + name = "different_process_provider_and_consumers", + timeout = "moderate", + srcs = [ + "test_different_process_provider_and_consumers.py", + ], + filesystem = ":filesystem_different_process", +) + +integration_test( + name = "same_process_provider_and_consumers", + timeout = "moderate", + srcs = [ + "test_same_process_provider_and_consumers.py", + ], + filesystem = ":filesystem_same_process", +) + + + +test_suite( + name = "component_tests", + tests = [ + ":different_process_provider_and_consumers", + ":same_process_provider_and_consumers", + ], +) diff --git a/score/mw/com/test/methods/multiple_proxies/integration_test/test_different_process_provider_and_consumers.py b/score/mw/com/test/methods/multiple_proxies/integration_test/test_different_process_provider_and_consumers.py new file mode 100644 index 000000000..d0b7e81c5 --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/integration_test/test_different_process_provider_and_consumers.py @@ -0,0 +1,48 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + + +TIMEOUT_SEC = 120 +NUM_PROXIES_PER_CONSUMER = [7, 3, 3, 10] +NUM_CONSUMERS = len(NUM_PROXIES_PER_CONSUMER) + + +def start_consumer_and_wait(sut, i: int): + if i > NUM_CONSUMERS - 1: + return + print(f"Starting consumer {i}...") + config_name = f"consumer{i}_mw_com_config.json" + num_proxies = NUM_PROXIES_PER_CONSUMER[i] + args = f"--consumer_id {i} --num-proxies-per-consumer {num_proxies} " + args += f"--service-instance-manifest ./etc/{config_name}" + + with sut.start_process( + f"./bin/consumer_main {args}", + cwd=f"/opt/Consumer{i}App/") as consumer_process: + start_consumer_and_wait(sut, i+1) + print(f"Waiting for consumer {i} to exit...") + assert consumer_process.wait_for_exit(timeout=TIMEOUT_SEC) == 0 + + +def test_multiple_proxies(sut): + + args = "" + + for i in range(NUM_CONSUMERS): + args += f"--num-proxies-per-consumer{i} {NUM_PROXIES_PER_CONSUMER[i]} " + print(f"Starting provider with args: {args}") + + with sut.start_process(f"./bin/provider_main {args}", + cwd="/opt/ProviderApp/") as provider_process: + start_consumer_and_wait(sut, 0) + assert provider_process.wait_for_exit(timeout=TIMEOUT_SEC) == 0 diff --git a/score/mw/com/test/methods/multiple_proxies/integration_test/test_same_process_provider_and_consumers.py b/score/mw/com/test/methods/multiple_proxies/integration_test/test_same_process_provider_and_consumers.py new file mode 100644 index 000000000..ca0dfc7ab --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/integration_test/test_same_process_provider_and_consumers.py @@ -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 +# ******************************************************************************* + + +TIMEOUT_SEC = 120 +NUM_PROXIES_PER_CONSUMER = [7, 3, 3, 1] +NUM_CONSUMERS = len(NUM_PROXIES_PER_CONSUMER) + + +def test_multiple_proxies(sut): + args = "" + for i in range(NUM_CONSUMERS): + num_proxies = NUM_PROXIES_PER_CONSUMER[i] + args += f"--num-proxies-per-consumer{i} {num_proxies} " + + config_name = "mw_com_config.json" + args += f"--service-instance-manifest ./etc/{config_name}" + + with sut.start_process( + f"./bin/combined_consumer_provider_main {args}", + cwd="/opt/CombinedConsumerProviderApp/") as combinded_process: + assert combinded_process.wait_for_exit(timeout=TIMEOUT_SEC) == 0 diff --git a/score/mw/com/test/methods/multiple_proxies/provider_main.cpp b/score/mw/com/test/methods/multiple_proxies/provider_main.cpp new file mode 100644 index 000000000..bd19f6167 --- /dev/null +++ b/score/mw/com/test/methods/multiple_proxies/provider_main.cpp @@ -0,0 +1,30 @@ +/******************************************************************************** + * 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/methods/methods_test_resources/provider_runner.h" +#include "score/mw/com/test/methods/multiple_proxies/common_resources.h" + +#include +#include +#include + +int main(int argc, const char** argv) +{ + const std::size_t num_consumers = score::mw::com::test::kConfiguredNumberOfConsumers; + auto [consumer_repetition, args] = score::mw::com::test::ParseConsumerRepetitionFromArgs(argc, argv, num_consumers); + auto expected_method_call_count = score::mw::com::test::CalculateExpectedMethodCallCounts( + consumer_repetition, score::mw::com::test::kEnabledMethodIDs); + + score::cpp::stop_source stop_source_{}; + score::mw::com::test::run_provider(stop_source_.get_token(), expected_method_call_count, num_consumers); + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/stop_offer_during_call/BUILD b/score/mw/com/test/methods/stop_offer_during_call/BUILD new file mode 100644 index 000000000..5f202416d --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/BUILD @@ -0,0 +1,69 @@ +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") + +# ******************************************************************************* +# 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("//score/mw/com/test:pkg_application.bzl", "pkg_application") + +validate_json_schema_test( + name = "validate_mw_com_config_schema", + json = "config/mw_com_config.json", + schema = "//score/mw/com:config_schema", + tags = ["lint"], +) + +cc_library( + name = "test_method_datatype", + srcs = ["test_method_datatype.cpp"], + hdrs = ["test_method_datatype.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//visibility:private"], + deps = [ + "//score/mw/com", + ], +) + +cc_binary( + name = "stop_offer_during_call", + srcs = ["stop_offer_during_call.cpp"], + data = [ + "config/logging.json", + "config/mw_com_config.json", + ], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":test_method_datatype", + "//score/mw/com", + "//score/mw/com:runtime", + "//score/mw/com/test/common_test_resources:fail_test", + ], +) + +pkg_application( + name = "stop-offer-during-call-pkg", + app_name = "StopOfferDuringCallApp", + bin = [":stop_offer_during_call"], + etc = [ + "config/mw_com_config.json", + ], + visibility = [ + "//score/mw/com/test/methods/stop_offer_during_call/integration_test:__subpackages__", + ], +) + +test_suite( + name = "component_tests", + tests = [ + "//score/mw/com/test/methods/stop_offer_during_call/integration_test:component_tests", + ], +) diff --git a/score/mw/com/test/methods/stop_offer_during_call/config/mw_com_config.json b/score/mw/com/test/methods/stop_offer_during_call/config/mw_com_config.json new file mode 100644 index 000000000..5d63c3fd5 --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/config/mw_com_config.json @@ -0,0 +1,65 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/methods/stop_offer_during_call/StopOfferTest", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 5000, + "methods": [ + { + "methodName": "long_running_method", + "methodId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "test/methods/StopOfferTest", + "serviceTypeName": "/test/methods/stop_offer_during_call/StopOfferTest", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "methods": [ + { + "methodName": "long_running_method", + "queueSize": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM" + } +} diff --git a/score/mw/com/test/methods/stop_offer_during_call/integration_test/BUILD b/score/mw/com/test/methods/stop_offer_during_call/integration_test/BUILD new file mode 100644 index 000000000..1bc6ee067 --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/integration_test/BUILD @@ -0,0 +1,36 @@ +# ******************************************************************************* +# 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:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +pkg_tar( + name = "filesystem", + deps = [ + "//score/mw/com/test/methods/stop_offer_during_call:stop-offer-during-call-pkg", + ], +) +integration_test( + name = "stop_offer_during_call", + srcs = [ + "stop_offer_during_call_test.py", + ], + filesystem = ":filesystem", +) + +test_suite( + name = "component_tests", + tests = [ + ":stop_offer_during_call", + ], +) diff --git a/score/mw/com/test/methods/stop_offer_during_call/integration_test/stop_offer_during_call_test.py b/score/mw/com/test/methods/stop_offer_during_call/integration_test/stop_offer_during_call_test.py new file mode 100644 index 000000000..e91c95367 --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/integration_test/stop_offer_during_call_test.py @@ -0,0 +1,17 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +def test_multiple_proxies(sut): + with sut.start_process("./bin/stop_offer_during_call", + cwd="/opt/StopOfferDuringCallApp/") as process: + assert process.wait_for_exit() == 0 diff --git a/score/mw/com/test/methods/stop_offer_during_call/main.cpp b/score/mw/com/test/methods/stop_offer_during_call/main.cpp new file mode 100644 index 000000000..f06fb7a2e --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/main.cpp @@ -0,0 +1,202 @@ +/******************************************************************************** + * 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/fail_test.h" +#include "score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +using namespace score::mw::com; +using namespace score::mw::com::test; + +const std::string_view kInstanceSpecifierSV = "test/methods/StopOfferTest"; +void check_result(std::future>>&& result_future, + std::int32_t expected_value) +{ + std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl; + auto result = std::move(result_future).get(); + std::cout << "Async call completed, checking result...\n"; + // fail_test_if(!result.has_value(), "FAIL: Method call failed with error unexpectedly: ", result.error()); + if (!result.has_value()) + { + fail_test("FAIL: Method call failed with error unexpectedly: ", result.error()); + } + + auto& return_value_ptr = result.value(); + + fail_test_if(return_value_ptr.get() == nullptr, + " FAIL: Method call returned null pointer, instead of a valid ptr to shm."); + + // clang-format off + fail_test_if(*return_value_ptr != expected_value, + "FAIL: Method call returned wrong value. Expected: ", expected_value, ", Actual: ", *return_value_ptr); + // clang-format on + + std::cout << "Method call succeeded with expected value: " << *return_value_ptr << std::endl; +} +struct ExecutionMarkers +{ + std::atomic method_started{false}; + std::atomic green_light_for_completion{false}; + std::atomic method_completed{false}; +}; + +void ResetExecutionMarkers(ExecutionMarkers& markers) +{ + markers.method_started = false; + markers.green_light_for_completion = false; + markers.method_completed = false; +} + +} // namespace + +int main(int argc, const char** argv) +{ + score::mw::com::runtime::InitializeRuntime(argc, argv); + + ExecutionMarkers execution_markers{}; + + std::cout << "Step 1: Create skeleton with long-running handler." << std::endl; + + auto instance_specifier_result = InstanceSpecifier::Create(std::string{kInstanceSpecifierSV}); + if (!instance_specifier_result.has_value()) + { + std::cout << "Could not create Instance specifier!.\n" + << "Here is why: " << instance_specifier_result.error() << "\n\n" + << std::flush; + } + auto instance_specifier = std::move(instance_specifier_result).value(); + + auto skeleton_result = StopOfferDuringCallSkeleton::Create(instance_specifier); + + if (!skeleton_result.has_value()) + { + std::cerr << "Failed to create skeleton: " << skeleton_result.error() << std::endl; + return EXIT_FAILURE; + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + std::cout << "Step 1: skeleton created." << std::endl; + + auto handler = [&execution_markers](std::int32_t input) -> std::int32_t { + execution_markers.method_started = true; + constexpr auto idling_time = std::chrono::milliseconds(1); + while (!execution_markers.green_light_for_completion) + { + std::this_thread::sleep_for(idling_time); + } + std::cout << "Consumer: green light was given... Continuing to completion...\n"; + execution_markers.method_completed = true; + return input * 2; + }; + + std::cout << "Step 1.1: Registering handler." << std::endl; + auto register_result = skeleton->long_running_method.RegisterHandler(std::move(handler)); + if (!register_result.has_value()) + { + std::cerr << "Failed to register handler: " << register_result.error() << std::endl; + return EXIT_FAILURE; + } + + auto offer_result = skeleton->OfferService(); + if (!offer_result.has_value()) + { + std::cerr << "Failed to offer service: " << offer_result.error() << std::endl; + return EXIT_FAILURE; + } + std::cout << "Step 1.2: Service offered" << std::endl; + + std::cout << "Step 2: Creating Proxy" << std::endl; + auto find_result = StopOfferDuringCallProxy::FindService(instance_specifier); + if (!find_result.has_value() || find_result->size() != 1) + { + std::cerr << "FindService failed or returned wrong count" << std::endl; + return EXIT_FAILURE; + } + + auto proxy_result = StopOfferDuringCallProxy::Create((*find_result)[0], {"long_running_method"}); + if (!proxy_result.has_value()) + { + std::cerr << "Failed to create proxy: " << proxy_result.error() << std::endl; + return EXIT_FAILURE; + } + auto proxy = std::make_unique(std::move(proxy_result).value()); + + std::cout << "Step 2.1: Call the long running method asynchronously\n"; + constexpr std::int32_t test_input = 12; + constexpr std::int32_t expected_output = 24; + + constexpr std::size_t max_repetition_count{5}; + std::cout + << "Step 3: Starting test loop. call mathod, wait for it to start, call StopOfferService, check result. Repeat " + << max_repetition_count << " times.\n"; + std::size_t repetiction_count{0}; + while (repetiction_count < max_repetition_count) + { + + auto async_result = + std::async(std::launch::async, [&proxy, test_input]() -> score::Result> { + auto result = proxy->long_running_method(test_input); + return result; + }); + + std::cout << "Step 3.1 : Wait for handler to start, then call StopOfferService\n" << std::flush; + while (!execution_markers.method_started) + { + constexpr auto idling_time = std::chrono::microseconds(50); + std::this_thread::sleep_for(idling_time); + } + + if (execution_markers.method_completed) + { + std::cout << " Step 3.2: StopOfferService was not calld since the method was already completed\n" + << std::flush; + continue; + } + + // green_light_for_completion needs to be set before calling StopOfferService, otherwise we will get a + // deadlock, since StopOfferService allways waits for the completion of the method calls before it + // completes. + execution_markers.green_light_for_completion = true; + skeleton->StopOfferService(); + std::cout << " Step 3.2: StopOfferService was called while the method was still running\n" << std::flush; + + ++repetiction_count; + check_result(std::move(async_result), expected_output); + ResetExecutionMarkers(execution_markers); + } + + // Test passes if: + // - No crash occurred + // - StopOfferService didn't hang indefinitely + // - Method call either succeeded or returned an error (not hung) + + if (repetiction_count == 0) + { + std::cerr << "FAIL: StopOfferService was never called during method execution" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/stop_offer_during_call/stop_offer_during_call.cpp b/score/mw/com/test/methods/stop_offer_during_call/stop_offer_during_call.cpp new file mode 100644 index 000000000..8d35bc6d0 --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/stop_offer_during_call.cpp @@ -0,0 +1,227 @@ +/******************************************************************************** + * 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/fail_test.h" +#include "score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +using namespace score::mw::com; +using namespace score::mw::com::test; + +const std::string_view kInstanceSpecifierSV = "test/methods/StopOfferTest"; +void check_result(std::future>>&& result_future, + std::int32_t expected_value) +{ + std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl; + auto result = std::move(result_future).get(); + std::cout << "Async call completed, checking result...\n"; + // fail_test_if(!result.has_value(), "FAIL: Method call failed with error unexpectedly: ", result.error()); + if (!result.has_value()) + { + fail_test("FAIL: Method call failed with error unexpectedly: ", result.error()); + } + + auto& return_value_ptr = result.value(); + + fail_test_if(return_value_ptr.get() == nullptr, + " FAIL: Method call returned null pointer, instead of a valid ptr to shm."); + + // clang-format off + fail_test_if(*return_value_ptr != expected_value, + "FAIL: Method call returned wrong value. Expected: ", expected_value, ", Actual: ", *return_value_ptr); + // clang-format on + + std::cout << "Method call succeeded with expected value: " << *return_value_ptr << std::endl; +} + +void offer_service(StopOfferDuringCallSkeleton& skeleton) +{ + auto offer_result = skeleton.OfferService(); + if (!offer_result.has_value()) + { + fail_test("Could not offer service: ", offer_result.error().Message()); + } + std::cout << "Service offered successfully\n"; +} + +auto find_service(InstanceSpecifier& instance_specifier) -> StopOfferDuringCallProxy +{ + + auto find_result = StopOfferDuringCallProxy::FindService(instance_specifier); + if (!find_result.has_value() || find_result->size() != 1) + { + fail_test("FindService failed or returned wrong count. Error: ", find_result.error()); + } + + auto proxy_result = StopOfferDuringCallProxy::Create((*find_result)[0], {"long_running_method"}); + if (!proxy_result.has_value()) + { + fail_test("Failed to create proxy: ", proxy_result.error()); + } + auto proxy = std::move(proxy_result).value(); + return proxy; +} + +struct ExecutionMarkers +{ + std::atomic method_started{false}; + std::atomic green_light_for_completion{false}; + std::atomic method_completed{false}; +}; + +void ResetExecutionMarkers(ExecutionMarkers& markers) +{ + markers.method_started = false; + markers.green_light_for_completion = false; + markers.method_completed = false; +} + +} // namespace + +/// Test passes if: +/// - No crash occurred +/// - StopOfferService didn't hang indefinitely +/// - Method call succeeded +int main(int argc, const char** argv) +{ + score::mw::com::runtime::InitializeRuntime(argc, argv); + + ExecutionMarkers execution_markers{}; + + std::cout << "Step 1: Create skeleton with long-running handler." << std::endl; + + auto instance_specifier_result = InstanceSpecifier::Create(std::string{kInstanceSpecifierSV}); + if (!instance_specifier_result.has_value()) + { + std::cout << "Could not create Instance specifier!.\n" + << "Here is why: " << instance_specifier_result.error() << "\n\n" + << std::flush; + } + auto instance_specifier = std::move(instance_specifier_result).value(); + + auto skeleton_result = StopOfferDuringCallSkeleton::Create(instance_specifier); + + if (!skeleton_result.has_value()) + { + std::cerr << "Failed to create skeleton: " << skeleton_result.error() << std::endl; + return EXIT_FAILURE; + } + + // auto skeleton = std::make_unique(std::move(skeleton_result).value()); + auto skeleton = std::move(skeleton_result).value(); + std::cout << "Step 1: skeleton created." << std::endl; + + auto handler = [&execution_markers](std::int32_t input) -> std::int32_t { + execution_markers.method_started = true; + constexpr auto idling_time = std::chrono::milliseconds(1); + while (!execution_markers.green_light_for_completion) + { + std::this_thread::sleep_for(idling_time); + } + std::cout << "Consumer: green light was given... Continuing to completion...\n"; + execution_markers.method_completed = true; + return input * 2; + }; + + std::cout << "Step 1.1: Registering handler." << std::endl; + auto register_result = skeleton.long_running_method.RegisterHandler(std::move(handler)); + if (!register_result.has_value()) + { + std::cerr << "Failed to register handler: " << register_result.error() << std::endl; + return EXIT_FAILURE; + } + + offer_service(skeleton); + + std::cout << "Step 1.2: Creating a Proxy and finding reoffered service" << std::endl; + auto proxy = find_service(instance_specifier); + + std::cout << "Step 2.1: Call the long running method asynchronously\n"; + constexpr std::int32_t test_input = 12; + constexpr std::int32_t expected_output = 24; + + constexpr std::size_t max_repetition_count{5}; + std::cout + << "Step 3: Starting test loop. call mathod, wait for it to start, call StopOfferService, check result. Repeat " + << max_repetition_count << " times.\n"; + std::size_t repetiction_count{0}; + + while (repetiction_count < max_repetition_count) + { + + std::cout << "Step 3.1 : (Re)Offering the service\n" << std::flush; + + auto async_result = + std::async(std::launch::async, [&proxy, test_input]() -> score::Result> { + while (true) + { + // this loop is required since the proxy might not yet be resubscribed to the reoffered service + auto result = proxy.long_running_method(test_input); + if (result.has_value()) + { + return result; + } + auto idling_time = std::chrono::milliseconds(1); + std::this_thread::sleep_for(idling_time); + } + }); + + std::cout << "Step 3.2 : Wait for handler to start, then call StopOfferService\n" << std::flush; + while (!execution_markers.method_started) + { + constexpr auto idling_time = std::chrono::microseconds(50); + std::cout << "."; + std::this_thread::sleep_for(idling_time); + } + + if (execution_markers.method_completed) + { + std::cout << " Step 3.3: StopOfferService was not calld since the method was already completed\n" + << std::flush; + continue; + } + + // green_light_for_completion needs to be set before calling StopOfferService, otherwise we will get a + // deadlock, since StopOfferService allways waits for the completion of the method calls before it + // completes. + execution_markers.green_light_for_completion = true; + skeleton.StopOfferService(); + std::cout << " Step 3.4: StopOfferService was called while the method was still running\n" << std::flush; + + ++repetiction_count; + check_result(std::move(async_result), expected_output); + ResetExecutionMarkers(execution_markers); + offer_service(skeleton); + } + + if (repetiction_count == 0) + { + std::cerr << "FAIL: StopOfferService was never called during method execution" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.cpp b/score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.cpp new file mode 100644 index 000000000..7e8adcb64 --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/test_method_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/methods/stop_offer_during_call/test_method_datatype.h" diff --git a/score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.h b/score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.h new file mode 100644 index 000000000..6c6272c67 --- /dev/null +++ b/score/mw/com/test/methods/stop_offer_during_call/test_method_datatype.h @@ -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 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_METHODS_STOP_OFFER_DURING_CALL_METHOD_DATATYPE_H +#define SCORE_MW_COM_TEST_METHODS_STOP_OFFER_DURING_CALL_METHOD_DATATYPE_H + +#include "score/mw/com/types.h" + +#include + +namespace score::mw::com::test +{ + +/// \brief Test interface for StopOfferService during active method call scenario +template +class StopOfferDuringCallInterface : public T::Base +{ + public: + using T::Base::Base; + + /// \brief Method that simulates a long-running operation + /// The handler will sleep for the specified milliseconds to allow testing + /// StopOfferService being called while the method is executing + typename T::template Method long_running_method{*this, "long_running_method"}; +}; + +/// \brief Proxy side of the test service +using StopOfferDuringCallProxy = score::mw::com::AsProxy; + +/// \brief Skeleton side of the test service +using StopOfferDuringCallSkeleton = score::mw::com::AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_METHODS_STOP_OFFER_DURING_CALL_METHOD_DATATYPE_H diff --git a/score/mw/com/test/partial_restart/methods/BUILD b/score/mw/com/test/partial_restart/methods/BUILD new file mode 100644 index 000000000..3fb32e0ab --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/BUILD @@ -0,0 +1,35 @@ +test_suite( + name = "component_tests", + tests = [ + "//score/mw/com/test/partial_restart/methods/consumer_restart/sct:partial_restart_methods_consumer_graceful_test", + "//score/mw/com/test/partial_restart/methods/consumer_restart/sct:partial_restart_methods_consumer_kill_test", + "//score/mw/com/test/partial_restart/methods/consumer_restart/sct:partial_restart_methods_consumer_method_reenable_test", + "//score/mw/com/test/partial_restart/methods/provider_restart/sct:partial_restart_methods_provider_graceful_no_proxy_test", + "//score/mw/com/test/partial_restart/methods/provider_restart/sct:partial_restart_methods_provider_graceful_test", + "//score/mw/com/test/partial_restart/methods/provider_restart/sct:partial_restart_methods_provider_kill_no_proxy_test", + "//score/mw/com/test/partial_restart/methods/provider_restart/sct:partial_restart_methods_provider_kill_test", + ], + visibility = ["//score/mw/com/test:__pkg__"], +) + +cc_library( + name = "test_method_datatype", + srcs = ["test_method_datatype.cpp"], + hdrs = ["test_method_datatype.h"], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + "//score/mw/com", + ], +) + +cc_library( + name = "consumer_handle_notification_data", + srcs = ["consumer_handle_notification_data.cpp"], + hdrs = ["consumer_handle_notification_data.h"], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + ":test_method_datatype", + "//score/mw/com", + "//score/mw/com/test/common_test_resources:check_point_control", + ], +) diff --git a/score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.cpp b/score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.cpp new file mode 100644 index 000000000..f6207e133 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.cpp @@ -0,0 +1,86 @@ +/******************************************************************************** + * 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/partial_restart/methods/consumer_handle_notification_data.h" + +#include +#include +#include + +namespace score::mw::com::test +{ + +void WaitTillServiceDisappears(HandleNotificationData& handle_notification_data) +{ + std::cerr << "WaitTillServiceDisappears: Waiting for service to disappear" << std::endl; + std::unique_lock lock{handle_notification_data.mutex}; + + // Use wait_for with timeout instead of indefinite wait. + // When a provider is killed abruptly or when a proxy is connected, the service discovery + // notification about service disappearance might not arrive immediately or at all. + // We use a reasonable timeout (5 seconds) to avoid waiting forever. + const std::chrono::seconds timeout{5}; + bool notified = handle_notification_data.condition_variable.wait_for(lock, timeout, [&handle_notification_data] { + return handle_notification_data.service_disappeared; + }); + + if (!notified) + { + std::cerr << "WaitTillServiceDisappears: Timeout waiting for service disappearance notification, " + << "assuming service disappeared" << std::endl; + // Manually set the flag since we're assuming disappearance + handle_notification_data.service_disappeared = true; + } + + handle_notification_data.service_disappeared = false; + std::cerr << "WaitTillServiceDisappears: Service disappeared" << std::endl; +} + +bool WaitTillServiceAppears(HandleNotificationData& handle_notification_data, + const std::chrono::seconds max_handle_notification_time) +{ + std::cerr << "WaitTillServiceAppears: Waiting for service to appear" << std::endl; + std::unique_lock lock{handle_notification_data.mutex}; + bool result = handle_notification_data.condition_variable.wait_for( + lock, max_handle_notification_time, [&handle_notification_data] { + return handle_notification_data.handle != nullptr; + }); + std::cerr << "WaitTillServiceAppears: Service appeared: " << (result ? "true" : "false") << std::endl; + return result; +} + +void HandleReceivedNotification( + const ServiceHandleContainer service_handle_container, + HandleNotificationData& handle_notification_data, + CheckPointControl& check_point_control) +{ + std::unique_lock lock{handle_notification_data.mutex}; + + if (service_handle_container.empty()) + { + std::cerr << "HandleReceivedNotification: Service disappeared (empty container)" << std::endl; + handle_notification_data.service_disappeared = true; + handle_notification_data.handle.reset(); + } + else + { + std::cerr << "HandleReceivedNotification: Service appeared (non-empty container, size: " + << service_handle_container.size() << ")" << std::endl; + handle_notification_data.service_disappeared = false; + handle_notification_data.handle = + std::make_unique(service_handle_container.front()); + } + + handle_notification_data.condition_variable.notify_all(); +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.h b/score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.h new file mode 100644 index 000000000..be2970a82 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.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_PARTIAL_RESTART_METHODS_CONSUMER_HANDLE_NOTIFICATION_DATA_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_HANDLE_NOTIFICATION_DATA_H + +#include "score/mw/com/test/common_test_resources/check_point_control.h" +#include "score/mw/com/test/partial_restart/methods/test_method_datatype.h" +#include "score/mw/com/types.h" + +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +struct HandleNotificationData +{ + // The handle variable alone is not enough to detect service disappearance. + // When a callback is set up using the StartFindService function, it may be called twice - once with 0 handles and + // once with multiple handles. If a thread is waiting and using the handle to + // detect service disappearance. It will never become aware that the + // service has disappeared, if the callbacks are executed first. To address this issue, an additional flag + // for service disappearance is introduced. + std::mutex mutex{}; + std::condition_variable condition_variable{}; + bool service_disappeared{false}; // used to detect spurious wakeup + std::unique_ptr handle{nullptr}; +}; + +void WaitTillServiceDisappears(HandleNotificationData& handle_notification_data); +bool WaitTillServiceAppears(HandleNotificationData& handle_notification_data, + const std::chrono::seconds max_handle_notification_time); + +void HandleReceivedNotification( + const ServiceHandleContainer service_handle_container, + HandleNotificationData& handle_notification_data, + CheckPointControl& check_point_control); + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_HANDLE_NOTIFICATION_DATA_H diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/BUILD b/score/mw/com/test/partial_restart/methods/consumer_restart/BUILD new file mode 100644 index 000000000..4d8b88752 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/BUILD @@ -0,0 +1,128 @@ +load("@score_baselibs//:bazel/packaging:pkg_adaptive_app.bzl", "pkg_adaptive_app") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") + +validate_json_schema_test( + name = "validate_mw_com_config_consumer_restart_schema", + json = "config/mw_com_config_consumer_restart.json", + schema = "//score/mw/com/impl/configuration:mw_com_config_schema", + tags = ["lint"], +) + +validate_json_schema_test( + name = "validate_mw_com_config_method_reenable_schema", + json = "config/mw_com_config_method_reenable.json", + schema = "//score/mw/com/impl/configuration:mw_com_config_schema", + tags = ["lint"], +) + +cc_binary( + name = "consumer_restart_application", + srcs = [ + "consumer_restart_application.cpp", + "consumer_restart_application.h", + ], + data = ["config/mw_com_config_consumer_restart.json"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":controller", + "//score/mw/com/test/common_test_resources:stop_token_sig_term_handler", + "//score/mw/com/test/common_test_resources:test_resources", + "@boost.program_options", + "@score_baselibs//score/language/futurecpp", + "@score_logging//score/mw/log", + ], +) + +cc_library( + name = "consumer", + srcs = ["consumer.cpp"], + hdrs = ["consumer.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:test_resources", + "//score/mw/com/test/partial_restart/methods:consumer_handle_notification_data", + "//score/mw/com/test/partial_restart/methods:test_method_datatype", + ], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + "//score/mw/com/test/common_test_resources:test_resources", + ], +) + +cc_library( + name = "provider", + srcs = ["provider.cpp"], + hdrs = ["provider.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/com", + "//score/mw/com/test/partial_restart/methods:test_method_datatype", + ], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + "//score/mw/com/test/common_test_resources:test_resources", + ], +) + +cc_library( + name = "controller", + srcs = ["controller.cpp"], + hdrs = ["controller.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:test_resources", + ], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + ":consumer", + ":provider", + "//score/mw/com/test/common_test_resources:shared_memory_object_guard", + "@score_baselibs//score/language/futurecpp", + ], +) + +pkg_adaptive_app( + name = "consumer_restart-pkg", + app_name = "consumer_restart_methods", + bin = [":consumer_restart_application"], + etc = [ + "config/mw_com_config_consumer_restart.json", + "config/logging.json", + ], + visibility = [ + "//score/mw/com/test/partial_restart/methods/consumer_restart/sct:__subpackages__", + ], +) + +cc_binary( + name = "consumer_method_reenable_test", + srcs = ["consumer_method_reenable_test.cpp"], + data = [ + "config/logging.json", + "config/mw_com_config.json", + ], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com:runtime", + "//score/mw/com/test/common_test_resources:test_resources", + "//score/mw/com/test/partial_restart/methods:test_method_datatype", + "@score_baselibs//score/language/futurecpp", + ], +) + +pkg_adaptive_app( + name = "consumer_method_reenable_test-pkg", + app_name = "consumer_method_reenable_test", + bin = [":consumer_method_reenable_test"], + etc = [ + "config/mw_com_config.json", + "config/logging.json", + ], + visibility = [ + "//score/mw/com/test/partial_restart/methods/consumer_restart/sct:__subpackages__", + ], +) diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/config/logging.json b/score/mw/com/test/partial_restart/methods/consumer_restart/config/logging.json new file mode 100644 index 000000000..59cd4e8a2 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/config/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "CR", + "appDesc": "consumer_restart", + "logLevel": "kDebug", + "logLevelThresholdConsole": "kDebug", + "logMode": "kRemote|kConsole" +} diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/config/mw_com_config.json b/score/mw/com/test/partial_restart/methods/consumer_restart/config/mw_com_config.json new file mode 100644 index 000000000..a6176c127 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/config/mw_com_config.json @@ -0,0 +1,49 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/methods/partial_restart/consumer_method_reenable/TestMethods", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 4002, + "methods": [ + { + "methodName": "test_method", + "methodId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "partial_restart/methods/consumer_method_reenable", + "serviceTypeName": "/test/methods/partial_restart/consumer_method_reenable/TestMethods", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 4002, + "asil-level": "QM", + "binding": "SHM", + "methods": [ + { + "methodName": "test_method", + "queueSize": 1 + } + ], + "allowedConsumer": { "QM": [0], "B": [0] }, + "allowedProvider": { "QM": [0], "B": [0] } + } + ] + } + ], + "global": { "asil-level": "QM" } +} diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/config/mw_com_config_consumer_restart.json b/score/mw/com/test/partial_restart/methods/consumer_restart/config/mw_com_config_consumer_restart.json new file mode 100644 index 000000000..eb12fb858 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/config/mw_com_config_consumer_restart.json @@ -0,0 +1,65 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/methods/partial_restart/consumer_restart/TestMethods", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 4001, + "methods": [ + { + "methodName": "test_method", + "methodId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "partial_restart/methods/consumer_restart", + "serviceTypeName": "/test/methods/partial_restart/consumer_restart/TestMethods", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 4001, + "asil-level": "QM", + "binding": "SHM", + "methods": [ + { + "methodName": "test_method", + "queueSize": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + }, + "allowedProvider": { + "QM": [ + 0 + ], + "B": [ + 0 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM" + } +} diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/consumer.cpp b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer.cpp new file mode 100644 index 000000000..f8e403e04 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer.cpp @@ -0,0 +1,176 @@ +/******************************************************************************** + * 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/partial_restart/methods/consumer_restart/consumer.h" +#include "score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.h" +#include "score/mw/com/test/partial_restart/methods/test_method_datatype.h" + +#include "score/mw/com/test/common_test_resources/check_point_control.h" +#include "score/mw/com/test/common_test_resources/consumer_resources.h" +#include "score/mw/com/test/common_test_resources/general_resources.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ +const std::chrono::seconds kMaxHandleNotificationWaitTime{15U}; +constexpr std::uint8_t kCheckpointMethodCallsSucceeded{1U}; +} // namespace + +void DoConsumerActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv, + bool enable_methods) noexcept +{ + // Initialize mw::com runtime explicitly, if we were called with cmd-line args from main/parent + if (argc > 0 && argv != nullptr) + { + std::cerr + << "Consumer: Initializing LoLa/mw::com runtime from cmd-line args handed over by parent/controller ..." + << std::endl; + mw::com::runtime::InitializeRuntime(argc, argv); + std::cerr << "Consumer: Initializing LoLa/mw::com runtime done." << std::endl; + } + + HandleNotificationData handle_notification_data{}; + + // ******************************************************************************** + // Step (1) - Start an async FindService Search + // ******************************************************************************** + auto instance_specifier_result = + score::mw::com::InstanceSpecifier::Create(std::string{"partial_restart/methods/consumer_restart"}); + if (!instance_specifier_result.has_value()) + { + std::cerr << "Consumer: Could not create instance specifier due to error " + << std::move(instance_specifier_result).error() << ", terminating!\n"; + check_point_control.ErrorOccurred(); + return; + } + + auto find_service_callback = [&handle_notification_data, &check_point_control](auto service_handle_container, + auto) noexcept { + HandleReceivedNotification(service_handle_container, handle_notification_data, check_point_control); + }; + + auto find_service_handle_result = StartFindService( + "Consumer", find_service_callback, instance_specifier_result.value(), check_point_control); + if (!find_service_handle_result.has_value()) + { + return; + } + + // Wait until Service Discovery returns a valid handle to create the Proxy + if (!WaitTillServiceAppears(handle_notification_data, kMaxHandleNotificationWaitTime)) + { + std::cerr << "Consumer Step (1): Did not receive handle in time!" << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + // ******************************************************************************** + // Step (2) - Create Proxy for found service + // ******************************************************************************** + if (enable_methods) + { + // Create proxy with methods enabled - full method test + auto lola_proxy_result = TestMethodServiceProxy::Create(*handle_notification_data.handle, {"test_method"}); + if (!(lola_proxy_result.has_value())) + { + std::cerr << "Consumer: Unable to create lola proxy:" << lola_proxy_result.error() << "\n"; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Consumer: Successfully created lola proxy (with methods)" << std::endl; + auto& method_proxy = lola_proxy_result.value(); + + // ******************************************************************************** + // Step (3) - Call method multiple times to verify it works + // ******************************************************************************** + const std::size_t method_call_count{5U}; + + for (std::size_t call_index = 0U; call_index < method_call_count; ++call_index) + { + std::int32_t arg1 = static_cast(call_index); + std::int32_t arg2 = static_cast(call_index * 2); + std::int32_t expected_result = arg1 + arg2; + + std::cout << "Consumer: Calling method with arguments (" << arg1 << ", " << arg2 << ")" << std::endl; + + auto result = method_proxy.test_method_(arg1, arg2); + if (!result.has_value()) + { + std::cerr << "Consumer: Method call failed with error: " << result.error().Message() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cout << "Consumer: Method returned: " << *result.value() << " (expected: " << expected_result << ")" + << std::endl; + + if (*result.value() != expected_result) + { + std::cerr << "Consumer: Method result mismatch! Got " << *result.value() << " but expected " + << expected_result << std::endl; + check_point_control.ErrorOccurred(); + return; + } + } + + std::cerr << "Consumer: All method calls succeeded - checkpoint (1) reached!" << std::endl; + } + else + { + // After consumer restart: create proxy WITHOUT methods enabled. + // This verifies that FindService and proxy creation work after consumer restart. + auto lola_proxy_result = TestMethodServiceProxy::Create(*handle_notification_data.handle); + if (!(lola_proxy_result.has_value())) + { + std::cerr << "Consumer: Unable to create lola proxy (reconnect):" << lola_proxy_result.error() << "\n"; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Consumer: Successfully created lola proxy (reconnect, without methods)" << std::endl; + std::cerr << "Consumer: Reconnection verified - checkpoint (1) reached!" << std::endl; + } + + check_point_control.CheckPointReached(kCheckpointMethodCallsSucceeded); + + // ******************************************************************************** + // Step (5) - Wait for controller notification to finish + // ******************************************************************************** + auto wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + std::cerr << "Consumer: Expected to get notification to finish gracefully but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cerr << "Consumer: Exiting gracefully." << std::endl; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/consumer.h b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer.h new file mode 100644 index 000000000..758cefb97 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer.h @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONSUMER_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONSUMER_H + +#include "score/mw/com/test/common_test_resources/check_point_control.h" + +#include + +namespace score::mw::com::test +{ + +/// \brief Consumer actions for consumer restart test. +/// The consumer finds the service, creates a proxy, calls methods to verify they work, +/// then signals checkpoint and waits for the controller to tell it to finish. +/// +/// \param enable_methods If true, creates proxy with method names and calls methods. +/// If false, creates proxy WITHOUT methods (just verifies FindService + proxy creation work). +/// The second consumer after restart must use false because the current LoLa implementation +/// does not clean up method shared memory when a consumer exits, so a new consumer +/// cannot re-create the method shared memory. +void DoConsumerActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv, + bool enable_methods) noexcept; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONSUMER_H diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_method_reenable_test.cpp b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_method_reenable_test.cpp new file mode 100644 index 000000000..bb182b3b9 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_method_reenable_test.cpp @@ -0,0 +1,272 @@ +/******************************************************************************** + * 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/check_point_control.h" +#include "score/mw/com/test/common_test_resources/child_process_guard.h" +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/shared_memory_object_creator.h" +#include "score/mw/com/test/partial_restart/methods/test_method_datatype.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include +#include + +namespace score::mw::com::test +{ +namespace +{ + +const std::chrono::seconds kMaxWaitTimeToReachCheckpoint{30U}; +const std::string_view kShmConsumerCheckpointControlFileName = "methods_consumer_method_reenable_checkpoint_file"; +const std::string_view kShmProviderCheckpointControlFileName = "methods_provider_method_reenable_checkpoint_file"; +const std::string_view kConsumerCheckpointControlName = "Consumer"; +const std::string_view kProviderCheckpointControlName = "Provider"; + +const InstanceSpecifier kInstanceSpecifier = + InstanceSpecifier::Create(std::string{"partial_restart/methods/consumer_method_reenable"}).value(); + +constexpr std::uint8_t kCheckpointMethodCallsSucceeded{1U}; + +void DoProviderActions(CheckPointControl& check_point_control, score::cpp::stop_token test_stop_token) noexcept +{ + auto skeleton_result = TestMethodServiceSkeleton::Create(kInstanceSpecifier); + if (!skeleton_result.has_value()) + { + std::cerr << "Provider: Failed to create skeleton: " << skeleton_result.error() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + auto skeleton = std::make_unique(std::move(skeleton_result).value()); + + auto handler = [](std::int32_t a, std::int32_t b) -> std::int32_t { + return a + b; + }; + + auto register_result = skeleton->test_method_.RegisterHandler(std::move(handler)); + if (!register_result.has_value()) + { + std::cerr << "Provider: Failed to register handler: " << register_result.error() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + auto offer_result = skeleton->OfferService(); + if (!offer_result.has_value()) + { + std::cerr << "Provider: Failed to offer service: " << offer_result.error() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cerr << "Provider: Service offered, checkpoint (1) reached" << std::endl; + check_point_control.CheckPointReached(kCheckpointMethodCallsSucceeded); + + // Wait for test completion + auto wait_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + check_point_control.ErrorOccurred(); + return; + } + + skeleton->StopOfferService(); +} + +void DoConsumerActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + bool enable_methods) noexcept +{ + auto find_result = TestMethodServiceProxy::FindService(kInstanceSpecifier); + if (!find_result.has_value() || find_result->size() != 1) + { + std::cerr << "Consumer: FindService failed" << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + if (enable_methods) + { + // First consumer OR fixed re-enablement: create proxy with methods + auto proxy_result = TestMethodServiceProxy::Create((*find_result)[0], {"test_method"}); + if (!proxy_result.has_value()) + { + std::cerr << "Consumer: Failed to create proxy with methods: " << proxy_result.error() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + auto proxy = std::make_unique(std::move(proxy_result).value()); + + // Call method 5 times + for (std::int32_t method_call_it = 0; method_call_it < 5; ++method_call_it) + { + std::int32_t a = method_call_it; + std::int32_t b = method_call_it * 2; + auto result = proxy->test_method_(a, b); + if (!result.has_value()) + { + std::cerr << "Consumer: Method call " << method_call_it << " failed: " << result.error() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + if (*result.value() != (a + b)) + { + std::cerr << "Consumer: Wrong result for call " << method_call_it << std::endl; + check_point_control.ErrorOccurred(); + return; + } + } + std::cerr << "Consumer: All method calls succeeded" << std::endl; + } + else + { + // No methods - just verify proxy creation works + auto proxy_result = TestMethodServiceProxy::Create((*find_result)[0]); + if (!proxy_result.has_value()) + { + std::cerr << "Consumer: Failed to create proxy: " << proxy_result.error() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Consumer: Proxy created (no methods)" << std::endl; + } + + check_point_control.CheckPointReached(kCheckpointMethodCallsSucceeded); + + auto wait_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + check_point_control.ErrorOccurred(); + return; + } +} + +} // namespace +} // namespace score::mw::com::test + +int main(int argc, const char** argv) +{ + using namespace score::mw::com::test; + + score::mw::com::runtime::InitializeRuntime(argc, argv); + + std::cerr << "\n=== Test: Consumer restart with method re-enablement ===" << std::endl; + + ObjectCleanupGuard object_cleanup_guard{}; + score::cpp::stop_source stop_source; + auto test_stop_token = stop_source.get_token(); + + // Create provider checkpoint control + auto provider_checkpoint_result = + CreateSharedCheckPointControl("Main", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!provider_checkpoint_result.has_value()) + { + return EXIT_FAILURE; + } + auto& provider_checkpoint = provider_checkpoint_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_result.value()); + + // Fork provider + auto provider_pid = ForkProcessAndRunInChildProcess("Main", "Provider", [&provider_checkpoint, test_stop_token]() { + DoProviderActions(provider_checkpoint, test_stop_token); + }); + if (!provider_pid.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(provider_pid.value()); + + TimeoutSupervisor timeout_supervisor{}; + + // Wait for provider to offer + auto notification = provider_checkpoint.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Main", notification, provider_checkpoint, kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Create consumer checkpoint control + auto consumer_checkpoint_result = + CreateSharedCheckPointControl("Main", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!consumer_checkpoint_result.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& consumer_checkpoint = consumer_checkpoint_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_result.value()); + + // First consumer: with methods + std::cerr << "Main: Starting first consumer (with methods)" << std::endl; + auto consumer_pid = ForkProcessAndRunInChildProcess("Main", "Consumer1", [&consumer_checkpoint, test_stop_token]() { + DoConsumerActions(consumer_checkpoint, test_stop_token, true); + }); + if (!consumer_pid.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(consumer_pid.value()); + + notification = consumer_checkpoint.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Main", notification, consumer_checkpoint, kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Kill first consumer + std::cerr << "Main: Killing first consumer" << std::endl; + consumer_checkpoint.FinishActions(); + if (!WaitForChildProcessToTerminate("Main", consumer_pid.value(), kMaxWaitTimeToReachCheckpoint)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Second consumer: ALSO with methods (the re-enablement test) + std::cerr << "Main: Starting second consumer (attempting method re-enablement)" << std::endl; + consumer_pid = ForkProcessAndRunInChildProcess("Main", "Consumer2", [&consumer_checkpoint, test_stop_token]() { + DoConsumerActions(consumer_checkpoint, test_stop_token, true); // enable_methods=true + }); + if (!consumer_pid.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(consumer_pid.value()); + + notification = consumer_checkpoint.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Main", notification, consumer_checkpoint, kCheckpointMethodCallsSucceeded)) + { + std::cerr << "FAIL: Second consumer could not re-enable methods" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Cleanup + consumer_checkpoint.FinishActions(); + provider_checkpoint.FinishActions(); + object_cleanup_guard.CleanUp(); + + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_restart_application.cpp b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_restart_application.cpp new file mode 100644 index 000000000..d99e7e37f --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_restart_application.cpp @@ -0,0 +1,133 @@ +/******************************************************************************** + * 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/partial_restart/methods/consumer_restart/consumer_restart_application.h" +#include "score/mw/com/test/partial_restart/methods/consumer_restart/controller.h" + +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" + +#include + +#include +#include +#include +#include + +namespace +{ + +/// \brief Test parameters for the ITF test variants for consumer restart with methods. +/// \details We have two variants for consumer restart ITF with methods. This is reflected +/// in the test parameter kill_consumer: +/// ITF variant 1: Consumer graceful/normal restart +/// ITF variant 2: Consumer kill/crash restart +struct TestParameters +{ + std::optional service_instance_manifest{}; + std::size_t number_test_iterations{}; + /// \brief shall the consumer be killed (true) or gracefully shutdown (false) before restart. + bool kill_consumer{false}; +}; + +std::optional ParseTestParameters(int argc, const char** argv) noexcept +{ + namespace po = boost::program_options; + + TestParameters test_parameters{}; + + // Reading in cmd-line params + po::options_description options; + + std::string service_instance_manifest{}; + + // clang-format off + options.add_options() + ("help", "Display the help message") + ("service_instance_manifest", po::value(&service_instance_manifest)->default_value(""), "Path to the com configuration file") + ("turns,t", po::value(&test_parameters.number_test_iterations), "Number of cycles (consumer restarts) to be done") + ("kill", po::value(&test_parameters.kill_consumer), "Shall the consumer get killed before restart or gracefully shutdown?"); + // clang-format on + po::variables_map args; + const auto parsed_args = + po::command_line_parser{argc, argv} + .options(options) + .style(po::command_line_style::unix_style | po::command_line_style::allow_long_disguise) + .run(); + po::store(parsed_args, args); + + if (args.count("help") > 0U) + { + std::cerr << options << std::endl; + return {}; + } + + po::notify(args); + + if (service_instance_manifest != "") + { + test_parameters.service_instance_manifest = service_instance_manifest; + } + return test_parameters; +} + +} // namespace + +int main(int argc, const char** argv) +{ + // Prerequisites for the test steps/sequence + score::cpp::stop_source test_stop_source{}; + const bool sig_term_handler_setup_success = score::mw::com::SetupStopTokenSigTermHandler(test_stop_source); + if (!sig_term_handler_setup_success) + { + std::cerr << "Test main: Unable to set signal handler for SIGINT and/or SIGTERM, cautiously continuing.\n"; + } + + const auto test_parameters_result = ParseTestParameters(argc, argv); + if (!(test_parameters_result.has_value())) + { + std::cerr << "Test main: Could not parse test parameters, exiting." << std::endl; + return EXIT_FAILURE; + } + const auto& test_parameters = test_parameters_result.value(); + score::cpp::set_assertion_handler(&score::mw::com::test::assertion_stdout_handler); + const int mw_com_argc = test_parameters.service_instance_manifest.has_value() ? argc : 0; + const char** mw_com_argv = test_parameters.service_instance_manifest.has_value() ? argv : nullptr; + + int test_result{}; + for (std::size_t test_iteration = 1U; test_iteration <= test_parameters.number_test_iterations; ++test_iteration) + { + std::cerr << "Test Main: Running iteration " << test_iteration << " of " + << test_parameters.number_test_iterations << " of Consumer-Restart-Methods-Test" << std::endl; + + if (test_parameters.kill_consumer == false) + { + test_result = + score::mw::com::test::DoConsumerNormalRestart(test_stop_source.get_token(), mw_com_argc, mw_com_argv); + } + else + { + test_result = + score::mw::com::test::DoConsumerKillRestart(test_stop_source.get_token(), mw_com_argc, mw_com_argv); + } + + if (test_result != EXIT_SUCCESS) + { + std::cerr << "Test Main: Iteration " << test_iteration << " of " << test_parameters.number_test_iterations + << " of Consumer-Restart-Methods-Test FAILED!" << std::endl; + return EXIT_FAILURE; + } + } + std::cerr << "Test Main: All " << test_parameters.number_test_iterations + << " iterations of Consumer-Restart-Methods-Test PASSED!" << std::endl; + return EXIT_SUCCESS; +} diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_restart_application.h b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_restart_application.h new file mode 100644 index 000000000..f780cb632 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/consumer_restart_application.h @@ -0,0 +1,16 @@ +/******************************************************************************** + * 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_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONSUMER_RESTART_APPLICATION_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONSUMER_RESTART_APPLICATION_H + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONSUMER_RESTART_APPLICATION_H diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/controller.cpp b/score/mw/com/test/partial_restart/methods/consumer_restart/controller.cpp new file mode 100644 index 000000000..8cbc62d4d --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/controller.cpp @@ -0,0 +1,368 @@ +/******************************************************************************** + * 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/partial_restart/methods/consumer_restart/controller.h" +#include "score/mw/com/test/partial_restart/methods/consumer_restart/consumer.h" +#include "score/mw/com/test/partial_restart/methods/consumer_restart/provider.h" + +#include "score/mw/com/test/common_test_resources/check_point_control.h" +#include "score/mw/com/test/common_test_resources/child_process_guard.h" +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/shared_memory_object_creator.h" + +#include + +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ + +const std::chrono::seconds kMaxWaitTimeToReachCheckpoint{30U}; + +const std::string_view kShmProviderCheckpointControlFileName = "methods_consumer_restart_provider_checkpoint_file"; +const std::string_view kShmConsumerCheckpointControlFileName = "methods_consumer_restart_consumer_checkpoint_file"; +const std::string_view kConsumerCheckpointControlName = "Consumer"; +const std::string_view kProviderCheckpointControlName = "Provider"; +constexpr std::uint8_t kCheckpointMethodCallsSucceeded{1U}; + +} // namespace + +/// Consumer Normal Restart Flow: +/// 1. Fork consumer (1st instance, enable_methods=true) → FindService, CreateProxy with methods, calls 5x +/// 2. Fork provider → creates skeleton, offers service → provider checkpoint 1 +/// 3. Wait provider checkpoint 1, then wait consumer checkpoint 1 (methods verified) +/// 4. Tell consumer to finish gracefully → wait for consumer to exit +/// 5. Fork consumer (2nd instance, enable_methods=false) → FindService, CreateProxy without methods +/// 6. Wait for second consumer checkpoint 1 (reconnection verified) +/// 7. Tell consumer and provider to finish +int DoConsumerNormalRestart(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept +{ + ObjectCleanupGuard object_cleanup_guard{}; + + // ******************************************************************************** + // Step (1) - Fork first consumer (with methods enabled) + // ******************************************************************************** + std::cerr << "Controller Step (1) - Fork first consumer instance (with methods)" << std::endl; + auto consumer_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (1)", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!(consumer_checkpoint_control_guard_result.has_value())) + { + return EXIT_FAILURE; + } + auto& consumer_checkpoint_control = consumer_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_control_guard_result.value()); + + constexpr bool kEnableMethods = true; + constexpr bool kDisableMethods = false; + + auto fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (1)", "Consumer", [&consumer_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, kEnableMethods); + }); + if (!fork_consumer_pid_guard.has_value()) + { + std::cerr << "Controller: Step (1) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + // ******************************************************************************** + // Step (2) - Fork provider process + // ******************************************************************************** + std::cerr << "Controller Step (2) - Fork provider process" << std::endl; + auto provider_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (2)", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!(provider_checkpoint_control_guard_result.has_value())) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& provider_checkpoint_control = provider_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_control_guard_result.value()); + + auto fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (2)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + std::cerr << "Controller: Step (2) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + score::mw::com::test::TimeoutSupervisor timeout_supervisor{}; + + // ******************************************************************************** + // Step (3) - Wait for provider to reach checkpoint (1) - service offered + // ******************************************************************************** + std::cerr << "Controller Step (3) - Waiting for provider to reach checkpoint 1" << std::endl; + auto notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (3)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // ******************************************************************************** + // Step (4) - Wait for first consumer to reach checkpoint (1) - methods called successfully + // ******************************************************************************** + std::cerr << "Controller Step (4) - Waiting for first consumer to reach checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (4)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // ******************************************************************************** + // Step (5) - Tell first consumer to finish gracefully and wait for exit + // ******************************************************************************** + std::cerr << "Controller Step (5) - Tell first consumer to finish gracefully" << std::endl; + consumer_checkpoint_control.FinishActions(); + + std::cerr << "Controller Step (5) - Wait for first consumer to exit" << std::endl; + if (!score::mw::com::test::WaitForChildProcessToTerminate( + "Controller: Step (5)", fork_consumer_pid_guard.value(), kMaxWaitTimeToReachCheckpoint)) + { + std::cerr << "Controller: First consumer did not exit in time!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + std::cerr << "Controller: First consumer exited successfully" << std::endl; + + // ******************************************************************************** + // Step (6) - Fork second consumer + // ******************************************************************************** + std::cerr << "Controller Step (6) - Fork second consumer instance (reconnect, no methods)" << std::endl; + fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (6)", "Consumer", [&consumer_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, kDisableMethods); + }); + if (!fork_consumer_pid_guard.has_value()) + { + std::cerr << "Controller: Step (6) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + // ******************************************************************************** + // Step (7) - Wait for second consumer to reach checkpoint (1) - reconnection verified + // ******************************************************************************** + std::cerr << "Controller Step (7) - Waiting for second consumer to reach checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (7)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // ******************************************************************************** + // Step (8) - Tell both consumer and provider to finish + // ******************************************************************************** + std::cerr << "Controller Step (8) - Tell consumer and provider to finish" << std::endl; + consumer_checkpoint_control.FinishActions(); + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller: Consumer normal restart test completed successfully!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_SUCCESS; +} + +/// Consumer Kill Restart Flow: +/// 1. Fork consumer (1st instance, enable_methods=true) → FindService, CreateProxy with methods, calls 5x +/// 2. Fork provider → creates skeleton, offers service → provider checkpoint 1 +/// 3. Wait provider checkpoint 1, then wait consumer checkpoint 1 (methods verified) +/// 4. SIGKILL consumer → wait for consumer to exit +/// 5. Fork consumer (2nd instance, enable_methods=false) → FindService, CreateProxy without methods +/// 6. Wait for second consumer checkpoint 1 (reconnection verified) +/// 7. Tell consumer and provider to finish +int DoConsumerKillRestart(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept +{ + ObjectCleanupGuard object_cleanup_guard{}; + + // ******************************************************************************** + // Step (1) - Fork first consumer (with methods enabled) + // ******************************************************************************** + std::cerr << "Controller Step (1) - Fork first consumer instance (with methods)" << std::endl; + auto consumer_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (1)", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!(consumer_checkpoint_control_guard_result.has_value())) + { + return EXIT_FAILURE; + } + auto& consumer_checkpoint_control = consumer_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_control_guard_result.value()); + + constexpr bool kEnableMethods = true; + constexpr bool kDisableMethods = false; + + auto fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (1)", "Consumer", [&consumer_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, kEnableMethods); + }); + if (!fork_consumer_pid_guard.has_value()) + { + std::cerr << "Controller: Step (1) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + // ******************************************************************************** + // Step (2) - Fork provider process + // ******************************************************************************** + std::cerr << "Controller Step (2) - Fork provider process" << std::endl; + auto provider_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (2)", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!(provider_checkpoint_control_guard_result.has_value())) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& provider_checkpoint_control = provider_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_control_guard_result.value()); + + auto fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (2)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + std::cerr << "Controller: Step (2) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + score::mw::com::test::TimeoutSupervisor timeout_supervisor{}; + + // ******************************************************************************** + // Step (3) - Wait for provider to reach checkpoint (1) - service offered + // ******************************************************************************** + std::cerr << "Controller Step (3) - Waiting for provider to reach checkpoint 1" << std::endl; + auto notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (3)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // ******************************************************************************** + // Step (4) - Wait for first consumer to reach checkpoint (1) - methods called successfully + // ******************************************************************************** + std::cerr << "Controller Step (4) - Waiting for first consumer to reach checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (4)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // ******************************************************************************** + // Step (5) - KILL first consumer process and wait for it to exit + // ******************************************************************************** + std::cerr << "Controller Step (5) - KILLING first consumer process" << std::endl; + if (kill(fork_consumer_pid_guard.value().GetPid(), SIGKILL) != 0) + { + std::cerr << "Controller: Failed to kill consumer!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (5) - Waiting for killed consumer to exit" << std::endl; + if (!score::mw::com::test::WaitForChildProcessToTerminate( + "Controller: Step (5)", fork_consumer_pid_guard.value(), kMaxWaitTimeToReachCheckpoint)) + { + std::cerr << "Controller: Killed consumer did not exit!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + std::cerr << "Controller: First consumer (killed) exited" << std::endl; + + // ******************************************************************************** + // Step (6) - Fork second consumer + // ******************************************************************************** + std::cerr << "Controller Step (6) - Fork second consumer instance (reconnect, no methods)" << std::endl; + fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (6)", "Consumer", [&consumer_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, kDisableMethods); + }); + if (!fork_consumer_pid_guard.has_value()) + { + std::cerr << "Controller: Step (6) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + // ******************************************************************************** + // Step (7) - Wait for second consumer to reach checkpoint (1) - reconnection verified + // ******************************************************************************** + std::cerr << "Controller Step (7) - Waiting for second consumer to reach checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (7)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // ******************************************************************************** + // Step (8) - Tell both consumer and provider to finish + // ******************************************************************************** + std::cerr << "Controller Step (8) - Tell consumer and provider to finish" << std::endl; + consumer_checkpoint_control.FinishActions(); + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller: Consumer kill restart test completed successfully!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_SUCCESS; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/controller.h b/score/mw/com/test/partial_restart/methods/consumer_restart/controller.h new file mode 100644 index 000000000..05333456e --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/controller.h @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONTROLLER_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONTROLLER_H + +#include + +namespace score::mw::com::test +{ + +/// \brief Test variant 1: Consumer graceful/normal restart. +/// Provider stays up the entire time. Consumer is started, calls methods successfully, +/// then exits gracefully. A new consumer is started and verifies it can find the service +/// and call methods successfully again. +int DoConsumerNormalRestart(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept; + +/// \brief Test variant 2: Consumer kill/crash restart. +/// Provider stays up the entire time. Consumer is started, calls methods successfully, +/// then is killed via SIGKILL. A new consumer is started and verifies it can find the service +/// and call methods successfully again. +int DoConsumerKillRestart(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_CONTROLLER_H diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/provider.cpp b/score/mw/com/test/partial_restart/methods/consumer_restart/provider.cpp new file mode 100644 index 000000000..7681baadb --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/provider.cpp @@ -0,0 +1,126 @@ +/******************************************************************************** + * 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/partial_restart/methods/consumer_restart/provider.h" +#include "score/mw/com/test/partial_restart/methods/test_method_datatype.h" + +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/provider_resources.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ + +std::atomic method_call_count{0U}; + +// Method handler that adds two numbers +std::int32_t TestMethodHandler(std::int32_t a, std::int32_t b) noexcept +{ + method_call_count++; + std::cout << "Provider: Method called with arguments (" << a << ", " << b + << "), call count: " << method_call_count.load() << std::endl; + return a + b; +} + +} // namespace + +void DoProviderActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv) noexcept +{ + if (argc > 0 && argv != nullptr) + { + std::cerr + << "Provider: Initializing LoLa/mw::com runtime from cmd-line args handed over by parent/controller ..." + << std::endl; + mw::com::runtime::InitializeRuntime(argc, argv); + std::cerr << "Provider: Initializing LoLa/mw::com runtime done." << std::endl; + } + + // ******************************************************************************** + // Step (1) - Create service instance/skeleton + // ******************************************************************************** + constexpr auto instance_specifier_string{"partial_restart/methods/consumer_restart"}; + std::cerr << "Provider: Before Skeleton Creation." << std::endl; + auto skeleton_wrapper_result = + CreateSkeleton("Provider", instance_specifier_string, check_point_control); + if (!(skeleton_wrapper_result.has_value())) + { + return; + } + auto& service_instance = skeleton_wrapper_result.value(); + + // ******************************************************************************** + // Step (2) - Register method handler + // ******************************************************************************** + std::cerr << "Provider: Registering method handler." << std::endl; + auto register_result = service_instance.test_method_.RegisterHandler([](std::int32_t a, std::int32_t b) noexcept { + return TestMethodHandler(a, b); + }); + if (!register_result.has_value()) + { + std::cerr << "Provider: Failed to register method handler: " << register_result.error().Message() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Provider: Method handler registered successfully." << std::endl; + + // ******************************************************************************** + // Step (3) - Offer Service. Checkpoint (1) is reached when service is offered - notify to Controller. + // ******************************************************************************** + if (test_stop_token.stop_requested()) + { + return; + } + + auto offer_service_result = + OfferService("Provider", service_instance, check_point_control); + if (!(offer_service_result.has_value())) + { + return; + } + std::cerr << "Provider: Service instance is offered." << std::endl; + check_point_control.CheckPointReached(1); + + // ******************************************************************************** + // Step (4) - Wait for controller notification to finish. + // Provider stays up the entire time while consumers restart. + // ******************************************************************************** + auto wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + std::cerr << "Provider: Expected to get notification to finish gracefully but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + // ******************************************************************************** + // Step (5) - StopOffer and exit + // ******************************************************************************** + service_instance.StopOfferService(); + std::cerr << "Provider: Exiting gracefully. Total method calls: " << method_call_count << std::endl; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/provider.h b/score/mw/com/test/partial_restart/methods/consumer_restart/provider.h new file mode 100644 index 000000000..0303904ae --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/provider.h @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_PROVIDER_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_PROVIDER_H + +#include "score/mw/com/test/common_test_resources/check_point_control.h" + +#include + +namespace score::mw::com::test +{ + +/// \brief Provider actions for consumer restart test. +/// The provider creates a skeleton, registers the method handler, offers the service, +/// then signals checkpoint and waits for the controller to tell it to finish. +/// The provider stays up the entire time while consumers restart. +void DoProviderActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv) noexcept; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_CONSUMER_RESTART_PROVIDER_H diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/sct/BUILD b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/BUILD new file mode 100644 index 000000000..911cc00ae --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/BUILD @@ -0,0 +1,48 @@ +# ******************************************************************************* +# 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("//platform/aas/tools/sctf/bazel_gen:sctf_gen.bzl", "py_sctf_test") + +py_sctf_test( + name = "partial_restart_methods_consumer_graceful_test", + srcs = [ + "__init__.py", + "partial_restart_methods_consumer_graceful_test.py", + "partial_restart_methods_consumer_test_fixture.py", + ], + data = ["//score/mw/com/test/partial_restart/methods/consumer_restart:consumer_restart-pkg"], + main = "partial_restart_methods_consumer_graceful_test.py", + module_root = "score/mw/com/test/partial_restart/methods/consumer_restart/sct", +) + +py_sctf_test( + name = "partial_restart_methods_consumer_kill_test", + srcs = [ + "__init__.py", + "partial_restart_methods_consumer_kill_test.py", + "partial_restart_methods_consumer_test_fixture.py", + ], + data = ["//score/mw/com/test/partial_restart/methods/consumer_restart:consumer_restart-pkg"], + main = "partial_restart_methods_consumer_kill_test.py", + module_root = "score/mw/com/test/partial_restart/methods/consumer_restart/sct", +) + +py_sctf_test( + name = "partial_restart_methods_consumer_method_reenable_test", + srcs = [ + "__init__.py", + "partial_restart_methods_consumer_method_reenable_test.py", + ], + data = ["//score/mw/com/test/partial_restart/methods/consumer_restart:consumer_method_reenable_test-pkg"], + main = "partial_restart_methods_consumer_method_reenable_test.py", + module_root = "score/mw/com/test/partial_restart/methods/consumer_restart/sct", +) diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_graceful_test.py b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_graceful_test.py new file mode 100644 index 000000000..88a12f69a --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_graceful_test.py @@ -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 +# ******************************************************************************* +import sctf +from partial_restart_methods_consumer_test_fixture import MethodsConsumerRestart + +# Test configuration +NUMBER_RESTART_CYCLES = 10 +KILL_CONSUMER = 0 + + +def test_partial_restart_methods_consumer_graceful(basic_sandbox): + """Test that a consumer can gracefully restart and continue calling methods on a provider.""" + with MethodsConsumerRestart(basic_sandbox, NUMBER_RESTART_CYCLES, KILL_CONSUMER): + pass + + +if __name__ == "__main__": + sctf.run(__file__) diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_kill_test.py b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_kill_test.py new file mode 100644 index 000000000..037c40a70 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_kill_test.py @@ -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 +# ******************************************************************************* +import sctf +from partial_restart_methods_consumer_test_fixture import MethodsConsumerRestart + +# Test configuration +NUMBER_RESTART_CYCLES = 10 +KILL_CONSUMER = 1 + + +def test_partial_restart_methods_consumer_kill(basic_sandbox): + """Test that a consumer can restart after being killed and continue calling methods on a provider.""" + with MethodsConsumerRestart(basic_sandbox, NUMBER_RESTART_CYCLES, KILL_CONSUMER): + pass + + +if __name__ == "__main__": + sctf.run(__file__) diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_method_reenable_test.py b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_method_reenable_test.py new file mode 100644 index 000000000..880f53bac --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_method_reenable_test.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 +# ******************************************************************************* +import sctf +from sctf import BaseSim + + +class ConsumerMethodReenableTest(BaseSim): + def __init__(self, environment, **kwargs): + super().__init__( + environment, + "bin/consumer_method_reenable_test", + [], + cwd="/opt/consumer_method_reenable_test", + wait_on_exit=True, + use_sandbox=True, + **kwargs + ) + + +def test_partial_restart_methods_consumer_method_reenable(basic_sandbox): + """Test consumer restart with method re-enablement.""" + with ConsumerMethodReenableTest(basic_sandbox): + pass + + +if __name__ == "__main__": + sctf.run(__file__) diff --git a/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_test_fixture.py b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_test_fixture.py new file mode 100644 index 000000000..62e3cd898 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/consumer_restart/sct/partial_restart_methods_consumer_test_fixture.py @@ -0,0 +1,34 @@ +# ******************************************************************************* +# 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 sctf import BaseSim + + +class MethodsConsumerRestart(BaseSim): + def __init__(self, environment, number_restart_cycles, kill_consumer, **kwargs): + args = [ + "--kill", + f"{kill_consumer}", + "--turns", + f"{number_restart_cycles}", + "--service_instance_manifest", + "etc/mw_com_config_consumer_restart.json", + ] + super().__init__( + environment, + "bin/consumer_restart_application", + args, + cwd="/opt/consumer_restart_methods", + wait_on_exit=True, + use_sandbox=True, + **kwargs + ) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/BUILD b/score/mw/com/test/partial_restart/methods/provider_restart/BUILD new file mode 100644 index 000000000..d3a84b8dc --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/BUILD @@ -0,0 +1,113 @@ +# ******************************************************************************* +# 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("//score/mw/com/test:pkg_application.bzl", "pkg_application") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//bazel/tools:json_schema_validator.bzl", "validate_json_schema_test") + +validate_json_schema_test( + name = "validate_mw_com_config_schema", + json = "config/mw_com_config.json", + schema = "//score/mw/com/impl/configuration:mw_com_config_schema", + tags = ["lint"], +) + +cc_binary( + name = "provider_restart_application", + srcs = [ + "provider_restart_application.cpp", + "provider_restart_application.h", + ], + data = ["config/mw_com_config.json"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":controller", + "//score/mw/com/test/common_test_resources:stop_token_sig_term_handler", + "//score/mw/com/test/common_test_resources:test_resources", + "@boost.program_options", + "@score_baselibs//score/language/futurecpp", + "@score_logging//score/mw/log", + ], +) + +cc_library( + name = "consumer", + srcs = ["consumer.cpp"], + hdrs = ["consumer.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:fail_test", + "//score/mw/com/test/common_test_resources:test_resources", + "//score/mw/com/test/partial_restart/methods:consumer_handle_notification_data", + "//score/mw/com/test/partial_restart/methods:test_method_datatype", + ], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + "//score/mw/com/test/common_test_resources:test_resources", + ], +) + +cc_library( + name = "provider", + srcs = ["provider.cpp"], + hdrs = ["provider.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/com", + "//score/mw/com/test/partial_restart/methods:test_method_datatype", + ], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + "//score/mw/com/test/common_test_resources:test_resources", + ], +) + +cc_library( + name = "controller", + srcs = ["controller.cpp"], + hdrs = ["controller.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:test_resources", + ], + visibility = ["//score/mw/com/test/partial_restart/methods:__subpackages__"], + deps = [ + ":consumer", + ":provider", + "//score/mw/com/test/common_test_resources:shared_memory_object_guard", + "@score_baselibs//score/language/futurecpp", + ], +) + +pkg_application( + name = "provider_restart-pkg", + app_name = "provider_restart_methods", + bin = [":provider_restart_application"], + etc = [ + "config/mw_com_config.json", + "config/logging.json", + ], + visibility = [ + "//score/mw/com/test/partial_restart/methods/provider_restart/integration_test:__subpackages__", + ], +) + + +test_suite( + name = "component_tests", + tests = [ + "//score/mw/com/test/partial_restart/methods/provider_restart/integration_test:component_tests", + ], +) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/config/logging.json b/score/mw/com/test/partial_restart/methods/provider_restart/config/logging.json new file mode 100644 index 000000000..de44fda27 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/config/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "PR", + "appDesc": "provider_restart", + "logLevel": "kDebug", + "logLevelThresholdConsole": "kDebug", + "logMode": "kRemote|kConsole" +} diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/config/mw_com_config.json b/score/mw/com/test/partial_restart/methods/provider_restart/config/mw_com_config.json new file mode 100644 index 000000000..7fbebeb97 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/config/mw_com_config.json @@ -0,0 +1,65 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/methods/partial_restart/provider_restart/TestMethods", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 4000, + "methods": [ + { + "methodName": "test_method", + "methodId": 1 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "partial_restart/methods/provider_restart", + "serviceTypeName": "/test/methods/partial_restart/provider_restart/TestMethods", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 4000, + "asil-level": "QM", + "binding": "SHM", + "methods": [ + { + "methodName": "test_method", + "queueSize": 1 + } + ], + "allowedConsumer": { + "QM": [ + 0,1263030 + ], + "B": [ + 0,1263030 + ] + }, + "allowedProvider": { + "QM": [ + 0,1263030 + ], + "B": [ + 0,1263030 + ] + } + } + ] + } + ], + "global": { + "asil-level": "QM" + } +} diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/consumer.cpp b/score/mw/com/test/partial_restart/methods/provider_restart/consumer.cpp new file mode 100644 index 000000000..f7030adff --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/consumer.cpp @@ -0,0 +1,345 @@ +/******************************************************************************** + * 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/partial_restart/methods/provider_restart/consumer.h" +#include "score/mw/com/test/common_test_resources/fail_test.h" +#include "score/mw/com/test/partial_restart/methods/consumer_handle_notification_data.h" +#include "score/mw/com/test/partial_restart/methods/test_method_datatype.h" + +#include "score/mw/com/test/common_test_resources/check_point_control.h" +#include "score/mw/com/test/common_test_resources/consumer_resources.h" +#include "score/mw/com/test/common_test_resources/general_resources.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ +const std::chrono::seconds kMaxHandleNotificationWaitTime{15U}; + +// Checkpoint identifiers +constexpr std::uint8_t kCheckpointInitialMethodCallsSucceeded{1U}; +constexpr std::uint8_t kCheckpointServiceDisappeared{2U}; +constexpr std::uint8_t kCheckpointServiceReappeared{3U}; +constexpr std::uint8_t kCheckpointMethodCallsAfterRestartSucceeded{4U}; + +void DoConsumerActionsWithProxy(CheckPointControl& check_point_control, + HandleNotificationData& handle_notification_data, + score::cpp::stop_token test_stop_token, + const ConsumerParameters&) noexcept +{ + // ******************************************************************************** + // Step (2) - Create Proxy for found service with methods enabled + // ******************************************************************************** + auto lola_proxy_result = TestMethodServiceProxy::Create(*handle_notification_data.handle, {"test_method"}); + if (!(lola_proxy_result.has_value())) + { + std::cerr << "Consumer: Unable to create lola proxy with methods:" << lola_proxy_result.error() << "\n"; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Consumer: Successfully created lola proxy with methods" << std::endl; + auto& method_proxy = lola_proxy_result.value(); + + // ******************************************************************************** + // Step (3) - Call method multiple times to verify it works + // ******************************************************************************** + const std::size_t method_call_count{5U}; + std::vector results; + + for (std::size_t call_index = 0U; call_index < method_call_count; ++call_index) + { + auto arg1 = static_cast(call_index); + auto arg2 = static_cast(call_index * 2); + std::int32_t expected_result = arg1 + arg2; + + std::cout << "Consumer: Calling method with arguments (" << arg1 << ", " << arg2 << ")" << std::endl; + + auto result = method_proxy.test_method_(arg1, arg2); + if (!result.has_value()) + { + std::cerr << "Consumer: Method call failed with error: " << result.error().Message() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cout << "Consumer: Method returned: " << *result.value() << " (expected: " << expected_result << ")" + << std::endl; + + if (*result.value() != expected_result) + { + std::cerr << "Consumer: Method result mismatch! Got " << *result.value() << " but expected " + << expected_result << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + results.push_back(*result.value()); + } + + // ******************************************************************************** + // Step (4) - Notify to Controller, that checkpoint (1) has been reached + // ******************************************************************************** + std::cerr << "Consumer: All initial method calls succeeded - checkpoint (1) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointInitialMethodCallsSucceeded); + + // ******************************************************************************** + // Step (5) - wait for controller to trigger further steps + // ******************************************************************************** + auto wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::PROCEED_NEXT_CHECKPOINT) + { + std::cerr << "Consumer: Expected to get notification to continue to next checkpoint but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + // ******************************************************************************** + // Step (6) - Wait for service to disappear (provider stops offering) + // ******************************************************************************** + std::cerr << "Consumer: Now waiting for service to disappear!" << std::endl; + WaitTillServiceDisappears(handle_notification_data); + + std::cerr << "Consumer: Service disappeared - checkpoint (2) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointServiceDisappeared); + + // ******************************************************************************** + // Step (7) - wait for controller notification to trigger further steps or finish. + // ******************************************************************************** + wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::PROCEED_NEXT_CHECKPOINT) + { + std::cerr << "Consumer: Expected to get notification to continue to next checkpoint but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + // ******************************************************************************** + // Step (8) - Wait for service to reappear (provider offers service again) + // ******************************************************************************** + std::cerr << "Consumer: Now waiting for service to reappear!" << std::endl; + if (!WaitTillServiceAppears(handle_notification_data, kMaxHandleNotificationWaitTime)) + { + std::cerr << "Consumer: Did not receive handle in time after provider restart!" << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cerr << "Consumer: Service reappeared - checkpoint (3) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointServiceReappeared); + + // ******************************************************************************** + // Step (9) - wait for controller notification to trigger further steps or finish. + // ******************************************************************************** + wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::PROCEED_NEXT_CHECKPOINT) + { + std::cerr << "Consumer: Expected to get notification to continue to next checkpoint but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + // ******************************************************************************** + // Step (10) - Call methods again after provider restart to verify reconnection + // ******************************************************************************** + for (std::size_t call_index = 0U; call_index < method_call_count;) + { + auto arg1 = static_cast(call_index + 10); + auto arg2 = static_cast(call_index * 3); + std::int32_t expected_result = arg1 + arg2; + + std::cout << "Consumer: Calling method after restart with arguments (" << arg1 << ", " << arg2 << ")" + << std::endl; + + auto call_method_until_success = [&method_proxy, &arg1, &arg2]() -> auto { + constexpr size_t call_retry_count{20U}; + // this loop is required since the proxy might not yet be resubscribed to the reoffered service + std::stringstream error_log{}; + for (std::size_t retry_idx = 0U; retry_idx < call_retry_count; ++retry_idx) + { + auto result = method_proxy.test_method_(arg1, arg2); + if (result.has_value()) + { + return result; + } + error_log << "Retry " << retry_idx << " failed with error:\n\t" << result.error() << "\n"; + + // after failiure, backoff with linearly growing waiting intervals before retrying + auto idling_time = std::chrono::milliseconds(retry_idx + 1U); + std::this_thread::sleep_for(idling_time); + } + // clang-format off + fail_test("Consumer: Method call after restart failed. A retry was attempted", call_retry_count, " times. ", + "Most likely proxy could not reconnect with the service after restart.", + " Here is the log of all call errors:\n", error_log.str()); + // clang-format on + SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(false); + }; + + auto result = call_method_until_success(); + + std::cout << "Consumer: Method after restart returned: " << *result.value() << " (expected: " << expected_result + << ")" << std::endl; + + if (*result.value() != expected_result) + { + std::cerr << "Consumer: Method result mismatch after restart! Got " << *result.value() << " but expected " + << expected_result << std::endl; + check_point_control.ErrorOccurred(); + return; + } + ++call_index; + } + + // ******************************************************************************** + // Step (11) - Notify controller that all calls after restart succeeded + // ******************************************************************************** + std::cerr << "Consumer: All method calls after provider restart succeeded - checkpoint (4) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointMethodCallsAfterRestartSucceeded); + + // ******************************************************************************** + // Step (12) - Wait for controller notification to finish + // ******************************************************************************** + wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + std::cerr << "Consumer: Expected to get notification to finish gracefully but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cerr << "Consumer: Exiting gracefully." << std::endl; +} + +} // namespace + +void DoConsumerActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv, + const ConsumerParameters& test_params) noexcept +{ + // Initialize mw::com runtime explicitly, if we were called with cmd-line args from main/parent + if (argc > 0 && argv != nullptr) + { + std::cerr + << "Consumer: Initializing LoLa/mw::com runtime from cmd-line args handed over by parent/controller ..." + << std::endl; + mw::com::runtime::InitializeRuntime(argc, argv); + std::cerr << "Consumer: Initializing LoLa/mw::com runtime done." << std::endl; + } + + HandleNotificationData handle_notification_data{}; + + // ******************************************************************************** + // Step (1) - Start an async FindService Search + // ******************************************************************************** + auto instance_specifier_result = + score::mw::com::InstanceSpecifier::Create(std::string{"partial_restart/methods/provider_restart"}); + if (!instance_specifier_result.has_value()) + { + std::cerr << "Consumer: Could not create instance specifier due to error " + << std::move(instance_specifier_result).error() << ", terminating!\n"; + check_point_control.ErrorOccurred(); + return; + } + + auto find_service_callback = [&handle_notification_data, &check_point_control](auto service_handle_container, + auto) noexcept { + HandleReceivedNotification(service_handle_container, handle_notification_data, check_point_control); + }; + + auto find_service_handle_result = StartFindService( + "Consumer", find_service_callback, instance_specifier_result.value(), check_point_control); + if (!find_service_handle_result.has_value()) + { + return; + } + + // Wait until Service Discovery returns a valid handle to create the Proxy + if (!WaitTillServiceAppears(handle_notification_data, kMaxHandleNotificationWaitTime)) + { + std::cerr << "Consumer Step (1): Did not receive handle in time!" << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + if (test_params.with_proxy) + { + DoConsumerActionsWithProxy(check_point_control, handle_notification_data, test_stop_token, test_params); + } + else + { + // For tests without proxy, just monitor service availability + std::cerr << "Consumer: Service found - checkpoint (1) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointInitialMethodCallsSucceeded); + + auto wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::PROCEED_NEXT_CHECKPOINT) + { + std::cerr << "Consumer: Expected to get notification to continue but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + WaitTillServiceDisappears(handle_notification_data); + std::cerr << "Consumer: Service disappeared - checkpoint (2) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointServiceDisappeared); + + wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::PROCEED_NEXT_CHECKPOINT) + { + std::cerr << "Consumer: Expected to get notification to continue but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + if (!WaitTillServiceAppears(handle_notification_data, kMaxHandleNotificationWaitTime)) + { + std::cerr << "Consumer: Did not receive handle in time after provider restart!" << std::endl; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Consumer: Service reappeared - checkpoint (3) reached!" << std::endl; + check_point_control.CheckPointReached(kCheckpointServiceReappeared); + + wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + std::cerr << "Consumer: Expected to get notification to finish gracefully but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + } + std::cerr << "Consumer: Finishing Actions." << std::endl; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/consumer.h b/score/mw/com/test/partial_restart/methods/provider_restart/consumer.h new file mode 100644 index 000000000..d7fee9340 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/consumer.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_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_CONSUMER_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_CONSUMER_H + +#include "score/mw/com/test/common_test_resources/check_point_control.h" + +#include + +namespace score::mw::com::test +{ + +struct ConsumerParameters +{ + public: + bool with_proxy; +}; + +void DoConsumerActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv, + const ConsumerParameters& params) noexcept; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_CONSUMER_H diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/controller.cpp b/score/mw/com/test/partial_restart/methods/provider_restart/controller.cpp new file mode 100644 index 000000000..dde84509d --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/controller.cpp @@ -0,0 +1,747 @@ +/******************************************************************************** + * 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/partial_restart/methods/provider_restart/controller.h" +#include "score/mw/com/test/partial_restart/methods/provider_restart/consumer.h" +#include "score/mw/com/test/partial_restart/methods/provider_restart/provider.h" + +#include "score/mw/com/test/common_test_resources/check_point_control.h" +#include "score/mw/com/test/common_test_resources/child_process_guard.h" +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/shared_memory_object_creator.h" + +#include + +#include +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ + +const std::chrono::seconds kMaxWaitTimeToReachCheckpoint{10U}; + +const std::string_view kShmProviderCheckpointControlFileName = "methods_provider_restart_provider_checkpoint_file"; +const std::string_view kShmConsumerCheckpointControlFileName = "methods_provider_restart_consumer_checkpoint_file"; +const std::string_view kConsumerCheckpointControlName = "Consumer"; +const std::string_view kProviderCheckpointControlName = "Provider"; +constexpr std::uint8_t kCheckpointMethodCallsSucceeded{1U}; +constexpr std::uint8_t kCheckpointServiceStopped{2U}; +constexpr std::uint8_t kCheckpointServiceReappeared{3U}; +constexpr std::uint8_t kCheckpointMethodsCalledAfterRestart{4U}; + +} // namespace + +int DoProviderNormalRestartWithProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept +{ + ObjectCleanupGuard object_cleanup_guard{}; + + // Step (1) - Fork consumer process + std::cerr << "Controller Step (1) - Fork consumer process" << std::endl; + auto consumer_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!(consumer_checkpoint_control_guard_result.has_value())) + { + return EXIT_FAILURE; + } + auto& consumer_checkpoint_control = consumer_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_control_guard_result.value()); + + score::mw::com::test::ConsumerParameters consumer_params{true}; // with_proxy = true + auto fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (1)", + "Consumer", + [&consumer_checkpoint_control, &consumer_params, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, consumer_params); + }); + if (!fork_consumer_pid_guard.has_value()) + { + std::cerr << "Controller Step (1) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + // Step (2) - Fork provider process + std::cerr << "Controller Step (2) - Fork provider process" << std::endl; + auto provider_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (2)", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!(provider_checkpoint_control_guard_result.has_value())) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& provider_checkpoint_control = provider_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_control_guard_result.value()); + + auto fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (2)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + std::cerr << "Controller Step (2) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + score::mw::com::test::TimeoutSupervisor timeout_supervisor{}; + + // Step (3) - Wait for provider to reach checkpoint (1) - service offered + std::cerr << "Controller Step (3) - Waiting for provider to reach checkpoint 1" << std::endl; + auto notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller Step (3)", notification_happened, provider_checkpoint_control, kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (4) - Wait for consumer to reach checkpoint (1) - methods called successfully + std::cerr << "Controller Step (4) - Waiting for consumer to reach checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller Step (4)", notification_happened, consumer_checkpoint_control, kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (5) - Trigger Consumer to proceed + std::cerr << "Controller Step (5) - Trigger Consumer to proceed to next checkpoint" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + // Step (6) - Trigger provider to proceed (will call StopOffer) + std::cerr << "Controller Step (6) - Trigger provider to proceed to next checkpoint" << std::endl; + provider_checkpoint_control.ProceedToNextCheckpoint(); + + // Step (7) - Wait for provider to reach checkpoint (2) - StopOffer completed + std::cerr << "Controller Step (7) - Waiting for provider to reach checkpoint 2" << std::endl; + notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller Step (7)", notification_happened, provider_checkpoint_control, kCheckpointServiceStopped)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + // Step (10b) - Trigger consumer to proceed and wait for service reappearance + std::cerr << "Controller Step (10b) - Trigger consumer to wait for service reappearance" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + // Step (8) - Wait for consumer to reach checkpoint (2) - detected service disappearance + std::cerr << "Controller Step (8) - Waiting for consumer to reach checkpoint 2" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller Step (8)", notification_happened, consumer_checkpoint_control, kCheckpointServiceStopped)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (9) - Tell provider to finish gracefully + std::cerr << "Controller Step (9) - Tell provider to finish gracefully" << std::endl; + provider_checkpoint_control.FinishActions(); + + // Step (10) - Wait for provider to exit + std::cerr << "Controller Step (10) - Wait for provider to exit" << std::endl; + if (!score::mw::com::test::WaitForChildProcessToTerminate( + "Controller Step (10)", fork_provider_pid_guard.value(), kMaxWaitTimeToReachCheckpoint)) + { + std::cerr << "Controller Step (10) Provider did not exit in time!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + std::cerr << "Controller Step (10) Provider exited successfully" << std::endl; + + // Step (11) - Restart provider + std::cerr << "Controller Step (11) - Restarting provider" << std::endl; + fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (11)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + std::cerr << "Controller Step (11) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + // Step (12) - Wait for provider to reach checkpoint (1) again - service offered + std::cerr << "Controller Step (12) - Waiting for restarted provider to reach checkpoint 1" << std::endl; + notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller Step (12)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (12b) - Trigger consumer to proceed and wait for service reappearance + std::cerr << "Controller Step (12b) - Trigger consumer to wait for service reappearance" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + // Step (13) - Wait for consumer to reach checkpoint (3) - detected service reappearance + std::cerr << "Controller Step (13) - Waiting for consumer to reach checkpoint 3" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller: Step (13)", notification_happened, consumer_checkpoint_control, kCheckpointServiceReappeared)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Consumer will now create new proxy and call methods to reach checkpoint 4 + // No trigger needed here - consumer already proceeded from Step 9 wait with the Step 12b trigger + + // Step (14) - Wait for consumer to reach checkpoint (4) - methods called successfully after restart + std::cerr << "Controller Step (14) - Waiting for consumer to reach checkpoint 4" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (14)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodsCalledAfterRestart)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (15) - Tell both consumer and provider to finish + std::cerr << "Controller Step (15) - Tell consumer and provider to finish" << std::endl; + consumer_checkpoint_control.FinishActions(); + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller: Test completed successfully!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_SUCCESS; +} + +int DoProviderNormalRestartWithoutProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept +{ + ObjectCleanupGuard object_cleanup_guard{}; + + // Similar to with-proxy test but consumer doesn't create proxy + std::cerr << "Controller Step (1) - Fork consumer process (without proxy)" << std::endl; + auto consumer_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!(consumer_checkpoint_control_guard_result.has_value())) + { + return EXIT_FAILURE; + } + auto& consumer_checkpoint_control = consumer_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_control_guard_result.value()); + + score::mw::com::test::ConsumerParameters consumer_params{false}; // with_proxy = false + auto fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (1)", + "Consumer", + [&consumer_checkpoint_control, &consumer_params, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, consumer_params); + }); + if (!fork_consumer_pid_guard.has_value()) + { + std::cerr << "Controller: Step (1) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + std::cerr << "Controller Step (2) - Fork provider process" << std::endl; + auto provider_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (2)", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!(provider_checkpoint_control_guard_result.has_value())) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& provider_checkpoint_control = provider_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_control_guard_result.value()); + + auto fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (2)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + std::cerr << "Controller: Step (2) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + score::mw::com::test::TimeoutSupervisor timeout_supervisor{}; + + // Wait for both to reach checkpoint 1 + std::cerr << "Controller Step (3) - Waiting for provider checkpoint 1" << std::endl; + auto notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (3)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (4) - Waiting for consumer checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (4)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Trigger provider restart sequence + std::cerr << "Controller Step (5) - Trigger consumer and provider to proceed" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + provider_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (6) - Waiting for provider checkpoint 2" << std::endl; + notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller: Step (6)", notification_happened, provider_checkpoint_control, kCheckpointServiceStopped)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (7) - Waiting for consumer checkpoint 2" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller: Step (7)", notification_happened, consumer_checkpoint_control, kCheckpointServiceStopped)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Restart provider + std::cerr << "Controller Step (8) - Tell provider to finish" << std::endl; + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller Step (9) - Wait for provider to exit" << std::endl; + + if (!score::mw::com::test::WaitForChildProcessToTerminate( + "Controller", fork_provider_pid_guard.value(), kMaxWaitTimeToReachCheckpoint)) + { + std::cerr << "Controller: Provider did not exit in time!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (10) - Restarting provider" << std::endl; + fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (10)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + std::cerr << "Controller: Step (10) failed, exiting." << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + std::cerr << "Controller Step (11) - Waiting for restarted provider checkpoint 1" << std::endl; + notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (11)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (11b) - Trigger consumer to proceed and wait for service reappearance + std::cerr << "Controller Step (11b) - Trigger consumer to wait for service reappearance" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (12) - Waiting for consumer checkpoint 3" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller: Step (12)", notification_happened, consumer_checkpoint_control, kCheckpointServiceReappeared)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Finish both + std::cerr << "Controller Step (13) - Tell consumer and provider to finish" << std::endl; + consumer_checkpoint_control.FinishActions(); + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller: Test completed successfully!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_SUCCESS; +} + +int DoProviderKillRestartWithProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept +{ + // Similar to normal restart but kills provider instead of graceful shutdown + ObjectCleanupGuard object_cleanup_guard{}; + + std::cerr << "Controller Step (1) - Fork consumer process" << std::endl; + auto consumer_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!(consumer_checkpoint_control_guard_result.has_value())) + { + return EXIT_FAILURE; + } + auto& consumer_checkpoint_control = consumer_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_control_guard_result.value()); + + score::mw::com::test::ConsumerParameters consumer_params{true}; + auto fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (1)", + "Consumer", + [&consumer_checkpoint_control, &consumer_params, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, consumer_params); + }); + if (!fork_consumer_pid_guard.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + std::cerr << "Controller Step (2) - Fork provider process" << std::endl; + auto provider_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (2)", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!(provider_checkpoint_control_guard_result.has_value())) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& provider_checkpoint_control = provider_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_control_guard_result.value()); + + auto fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (2)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + score::mw::com::test::TimeoutSupervisor timeout_supervisor{}; + + // Wait for initial setup + std::cerr << "Controller Step (3) - Waiting for provider checkpoint 1" << std::endl; + auto notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (3)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (4) - Waiting for consumer checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (4)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // KILL provider instead of graceful shutdown + std::cerr << "Controller Step (5) - KILLING provider process" << std::endl; + + const auto kill_result = fork_provider_pid_guard.value().KillChildProcess(); + if (!kill_result) + { + std::cerr << "Controller: Step (5) failed. Error killing provider child process" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (6) - Waiting for provider to exit" << std::endl; + if (!score::mw::com::test::WaitForChildProcessToTerminate( + "Controller", fork_provider_pid_guard.value(), kMaxWaitTimeToReachCheckpoint)) + { + std::cerr << "Controller: Provider did not exit after SIGKILL!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Trigger consumer to detect disappearance + std::cerr << "Controller Step (7) - Trigger consumer to proceed" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (8.1) - Waiting for consumer checkpoint 2" << std::endl; + // PERF: This step takes roughly 5 seconds to complete, which is two orders of magnitude longer than any other + // segment of the test. If the test keeps timing out then this should be investigated. + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller: Step (8.2)", notification_happened, consumer_checkpoint_control, kCheckpointServiceStopped)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + // Restart provider + std::cerr << "Controller Step (9) - Restarting provider after kill" << std::endl; + fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (9)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + std::cerr << "Controller Step (10) - Waiting for restarted provider checkpoint 1" << std::endl; + notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (10)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (10b) - Trigger consumer to proceed from Step 7 wait and enter WaitTillServiceAppears + std::cerr << "Controller Step (10b) - Trigger consumer to proceed to wait for service reappearance" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (11) - Waiting for consumer checkpoint 3" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller: Step (11)", notification_happened, consumer_checkpoint_control, kCheckpointServiceReappeared)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Now trigger consumer to proceed from Step 9 wait and create new proxy + std::cerr << "Controller Step (12) - Trigger consumer to create new proxy and call methods" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (12) - Waiting for consumer checkpoint 4" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + + if (!VerifyCheckpoint("Controller: Step (13)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodsCalledAfterRestart)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (14) - Trigger consumer to finish" << std::endl; + consumer_checkpoint_control.FinishActions(); + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller: Test completed successfully!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_SUCCESS; +} + +int DoProviderKillRestartWithoutProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept +{ + // Combination of without-proxy and kill variants + ObjectCleanupGuard object_cleanup_guard{}; + + std::cerr << "Controller Step (1) - Fork consumer process (without proxy)" << std::endl; + auto consumer_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller", kShmConsumerCheckpointControlFileName, kConsumerCheckpointControlName); + if (!(consumer_checkpoint_control_guard_result.has_value())) + { + return EXIT_FAILURE; + } + auto& consumer_checkpoint_control = consumer_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddConsumerCheckpointControlGuard(consumer_checkpoint_control_guard_result.value()); + + score::mw::com::test::ConsumerParameters consumer_params{false}; + auto fork_consumer_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (1)", + "Consumer", + [&consumer_checkpoint_control, &consumer_params, test_stop_token, argc, argv]() { + score::mw::com::test::DoConsumerActions( + consumer_checkpoint_control, test_stop_token, argc, argv, consumer_params); + }); + if (!fork_consumer_pid_guard.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkConsumerGuard(fork_consumer_pid_guard.value()); + + std::cerr << "Controller Step (2) - Fork provider process" << std::endl; + auto provider_checkpoint_control_guard_result = CreateSharedCheckPointControl( + "Controller Step (2)", kShmProviderCheckpointControlFileName, kProviderCheckpointControlName); + if (!(provider_checkpoint_control_guard_result.has_value())) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + auto& provider_checkpoint_control = provider_checkpoint_control_guard_result.value().GetObject(); + object_cleanup_guard.AddProviderCheckpointControlGuard(provider_checkpoint_control_guard_result.value()); + + auto fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (2)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + score::mw::com::test::TimeoutSupervisor timeout_supervisor{}; + + std::cerr << "Controller Step (3) - Waiting for provider checkpoint 1" << std::endl; + auto notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (3)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (4) - Waiting for consumer checkpoint 1" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (4)", + notification_happened, + consumer_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // KILL provider + std::cerr << "Controller Step (5) - KILLING provider process" << std::endl; + + if (kill(fork_provider_pid_guard.value().GetPid(), SIGKILL) != 0) + { + std::cerr << "Controller: Failed to kill provider!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (6) - Waiting for provider to exit" << std::endl; + if (!score::mw::com::test::WaitForChildProcessToTerminate( + "Controller", fork_provider_pid_guard.value(), kMaxWaitTimeToReachCheckpoint)) + { + std::cerr << "Controller: Provider did not exit after SIGKILL!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (7) - Trigger consumer to proceed" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (8) - Waiting for consumer checkpoint 2" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller: Step (8)", notification_happened, consumer_checkpoint_control, 2)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Restart provider + std::cerr << "Controller Step (9) - Restarting provider after kill" << std::endl; + fork_provider_pid_guard = score::mw::com::test::ForkProcessAndRunInChildProcess( + "Controller Step (9)", "Provider", [&provider_checkpoint_control, test_stop_token, argc, argv]() { + score::mw::com::test::DoProviderActions(provider_checkpoint_control, test_stop_token, argc, argv); + }); + if (!fork_provider_pid_guard.has_value()) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + object_cleanup_guard.AddForkProviderGuard(fork_provider_pid_guard.value()); + + std::cerr << "Controller Step (10) - Waiting for restarted provider checkpoint 1" << std::endl; + notification_happened = provider_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint("Controller Step (10)", + notification_happened, + provider_checkpoint_control, + kCheckpointMethodCallsSucceeded)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + // Step (10b) - Trigger consumer to proceed and wait for service reappearance + std::cerr << "Controller Step (10b) - Trigger consumer to wait for service reappearance" << std::endl; + consumer_checkpoint_control.ProceedToNextCheckpoint(); + + std::cerr << "Controller Step (11) - Waiting for consumer checkpoint 3" << std::endl; + notification_happened = consumer_checkpoint_control.WaitForCheckpointReachedOrError( + kMaxWaitTimeToReachCheckpoint, test_stop_token, timeout_supervisor); + if (!VerifyCheckpoint( + "Controller Step (11)", notification_happened, consumer_checkpoint_control, kCheckpointServiceReappeared)) + { + object_cleanup_guard.CleanUp(); + return EXIT_FAILURE; + } + + std::cerr << "Controller Step (12) - Tell consumer and provider to finish" << std::endl; + consumer_checkpoint_control.FinishActions(); + provider_checkpoint_control.FinishActions(); + + std::cerr << "Controller: Test completed successfully!" << std::endl; + object_cleanup_guard.CleanUp(); + return EXIT_SUCCESS; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/controller.h b/score/mw/com/test/partial_restart/methods/provider_restart/controller.h new file mode 100644 index 000000000..932ff3fa1 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/controller.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_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_CONTROLLER_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_CONTROLLER_H + +#include + +namespace score::mw::com::test +{ + +int DoProviderNormalRestartWithProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept; +int DoProviderNormalRestartWithoutProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept; +int DoProviderKillRestartWithProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept; +int DoProviderKillRestartWithoutProxy(score::cpp::stop_token test_stop_token, int argc, const char** argv) noexcept; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_CONTROLLER_H diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/BUILD b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/BUILD new file mode 100644 index 000000000..d28a9cd89 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/BUILD @@ -0,0 +1,69 @@ +# ******************************************************************************* +# 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:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +pkg_tar( + name = "filesystem", + deps = [ + "//score/mw/com/test/partial_restart/methods/provider_restart:provider_restart-pkg", + ], +) + +integration_test( + name = "partial_restart_methods_provider_graceful_test", + srcs = [ + "partial_restart_methods_provider_graceful_test.py", + "partial_restart_methods_provider_test_fixture.py", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "partial_restart_methods_provider_graceful_no_proxy_test", + srcs = [ + "partial_restart_methods_provider_graceful_no_proxy_test.py", + "partial_restart_methods_provider_test_fixture.py", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "partial_restart_methods_provider_kill_test", + srcs = [ + "partial_restart_methods_provider_kill_test.py", + "partial_restart_methods_provider_test_fixture.py", + ], + filesystem = ":filesystem", +) + +integration_test( + name = "partial_restart_methods_provider_kill_no_proxy_test", + srcs = [ + "partial_restart_methods_provider_kill_no_proxy_test.py", + "partial_restart_methods_provider_test_fixture.py", + ], + filesystem = ":filesystem", +) + +test_suite( + name = "component_tests", + tests = [ + ":partial_restart_methods_provider_graceful_test", + ":partial_restart_methods_provider_graceful_no_proxy_test", + ":partial_restart_methods_provider_kill_test", + ":partial_restart_methods_provider_kill_no_proxy_test", + ], + visibility = ["//score/mw/com/test/partial_restart/methods/provider_restart:__subpackages__"], +) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_graceful_no_proxy_test.py b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_graceful_no_proxy_test.py new file mode 100644 index 000000000..b86d5a85a --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_graceful_no_proxy_test.py @@ -0,0 +1,23 @@ +# ******************************************************************************* +# 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 partial_restart_methods_provider_test_fixture import provider_restart + +NUMBER_RESTART_CYCLES = 10 +WITH_PROXY = 0 +KILL_PROVIDER = 0 + + +def test_partial_restart_methods_provider_graceful_no_proxy(sut): + """Test that service can restart when no proxy is connected.""" + provider_restart(sut, NUMBER_RESTART_CYCLES, WITH_PROXY, KILL_PROVIDER) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_graceful_test.py b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_graceful_test.py new file mode 100644 index 000000000..f5e187fd6 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_graceful_test.py @@ -0,0 +1,23 @@ +# ******************************************************************************* +# 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 partial_restart_methods_provider_test_fixture import provider_restart + +NUMBER_RESTART_CYCLES = 10 +WITH_PROXY = 1 +KILL_PROVIDER = 0 + + +def test_partial_restart_methods_provider_graceful_no_proxy(sut): + """Test that methods work after provider graceful restart with proxy connected.""" + provider_restart(sut, NUMBER_RESTART_CYCLES, WITH_PROXY, KILL_PROVIDER) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_kill_no_proxy_test.py b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_kill_no_proxy_test.py new file mode 100644 index 000000000..08b9a9cf6 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_kill_no_proxy_test.py @@ -0,0 +1,23 @@ +# ******************************************************************************* +# 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 partial_restart_methods_provider_test_fixture import provider_restart + +NUMBER_RESTART_CYCLES = 3 +WITH_PROXY = 0 +KILL_PROVIDER = 1 + + +def test_partial_restart_methods_provider_graceful_no_proxy(sut): + """Test that methods work after provider graceful restart with proxy connected.""" + provider_restart(sut, NUMBER_RESTART_CYCLES, WITH_PROXY, KILL_PROVIDER) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_kill_test.py b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_kill_test.py new file mode 100644 index 000000000..2305ae096 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_kill_test.py @@ -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 +# ******************************************************************************* + +from partial_restart_methods_provider_test_fixture import provider_restart + +# PERF: An individual run takes slightly more than 5 seconds. Bottleneck is in +# step 8 of the controller. There is a perf taged comment in controller.cpp, +# at the bottleneck position. +# So the number of iterations should be kept to less than 10, since 10 +# repetitions can take longer than 60 seconds. + +NUMBER_RESTART_CYCLES = 7 +WITH_PROXY = 1 +KILL_PROVIDER = 1 + + +def test_partial_restart_methods_provider_graceful_no_proxy(sut): + """Test that methods work after provider kill restart with proxy connected.""" + provider_restart(sut, NUMBER_RESTART_CYCLES, WITH_PROXY, KILL_PROVIDER) diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_test_fixture.py b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_test_fixture.py new file mode 100644 index 000000000..5ab0a5b02 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/integration_test/partial_restart_methods_provider_test_fixture.py @@ -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 +# ******************************************************************************* + +def provider_restart(sut, number_restart_cycles, with_proxy, kill_provider): + args = [ + "--kill", + f"{kill_provider}", + "--turns", + f"{number_restart_cycles}", + "--with-proxy", + f"{with_proxy}", + "--service_instance_manifest", + "etc/mw_com_config.json", + ] + with sut.start_process( + f"./bin/provider_restart_application {' '.join(args)}", + cwd="/opt/provider_restart_methods", + ) as process: + assert process.wait_for_exit() == 0 diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/provider.cpp b/score/mw/com/test/partial_restart/methods/provider_restart/provider.cpp new file mode 100644 index 000000000..b233fb750 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/provider.cpp @@ -0,0 +1,148 @@ +/******************************************************************************** + * 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/partial_restart/methods/provider_restart/provider.h" +#include "score/mw/com/test/partial_restart/methods/test_method_datatype.h" + +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/provider_resources.h" + +#include "score/mw/com/runtime.h" +#include "score/mw/com/types.h" + +#include + +#include +#include +#include + +namespace score::mw::com::test +{ + +namespace +{ + +std::atomic method_call_count{0U}; + +// Method handler that adds two numbers +std::int32_t TestMethodHandler(std::int32_t a, std::int32_t b) noexcept +{ + method_call_count++; + std::cout << "Provider: Method called with arguments (" << a << ", " << b + << "), call count: " << method_call_count.load() << std::endl; + return a + b; +} + +} // namespace + +void DoProviderActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv) noexcept +{ + if (argc > 0 && argv != nullptr) + { + std::cerr + << "Provider: Initializing LoLa/mw::com runtime from cmd-line args handed over by parent/controller ..." + << std::endl; + mw::com::runtime::InitializeRuntime(argc, argv); + std::cerr << "Provider: Initializing LoLa/mw::com runtime done." << std::endl; + } + + // ******************************************************************************** + // Step (1) - Create service instance/skeleton + // ******************************************************************************** + + constexpr auto instance_specifier_string{"partial_restart/methods/provider_restart"}; + auto skeleton_wrapper_result = + CreateSkeleton("Provider", instance_specifier_string, check_point_control); + if (!(skeleton_wrapper_result.has_value())) + { + check_point_control.ErrorOccurred(); + return; + } + auto& service_instance = skeleton_wrapper_result.value(); + + // ******************************************************************************** + // Step (2) - Register method handler + // ******************************************************************************** + std::cerr << "Provider: Registering method handler." << std::endl; + auto register_result = service_instance.test_method_.RegisterHandler([](std::int32_t a, std::int32_t b) noexcept { + return TestMethodHandler(a, b); + }); + if (!register_result.has_value()) + { + std::cerr << "Provider: Failed to register method handler: " << register_result.error().Message() << std::endl; + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Provider: Method handler registered successfully." << std::endl; + + // ******************************************************************************** + // Step (3) - Offer Service. `Checkpoint` (1) is reached when service is offered - notify to `Controller`. + // ******************************************************************************** + if (test_stop_token.stop_requested()) + { + check_point_control.ErrorOccurred(); + return; + } + + auto offer_service_result = + OfferService("Provider", service_instance, check_point_control); + if (!(offer_service_result.has_value())) + { + check_point_control.ErrorOccurred(); + return; + } + std::cerr << "Provider: Service instance is offered." << std::endl; + check_point_control.CheckPointReached(1); + + // ******************************************************************************** + // Step (4) - Wait for proceed trigger from Controller + // ******************************************************************************** + auto wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::PROCEED_NEXT_CHECKPOINT) + { + std::cerr << "Provider Step (4): Expected to get notification to continue to next checkpoint but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + // ******************************************************************************** + // Step (5) - Stop offering the service (StopOffer) + // ******************************************************************************** + std::cerr << "Provider: Stopping service offering." << std::endl; + service_instance.StopOfferService(); + + // ******************************************************************************** + // Step (6) - Checkpoint(2) reached - notify controller + // ******************************************************************************** + std::cerr << "Provider: Notifying controller, that checkpoint(2) has been reached." << std::endl; + check_point_control.CheckPointReached(2); + + // ******************************************************************************** + // Step (7) - Wait for Controller trigger to terminate. + // ******************************************************************************** + wait_for_child_proceed_result = WaitForChildProceed(check_point_control, test_stop_token); + if (wait_for_child_proceed_result != CheckPointControl::ProceedInstruction::FINISH_ACTIONS) + { + std::cerr << "Provider: Expected to get notification to finish gracefully but got: " + << static_cast(wait_for_child_proceed_result) << std::endl; + check_point_control.ErrorOccurred(); + return; + } + + std::cerr << "Provider: Exiting gracefully. Total method calls: " << method_call_count.load() << std::endl; +} + +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/provider.h b/score/mw/com/test/partial_restart/methods/provider_restart/provider.h new file mode 100644 index 000000000..acac8ef22 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/provider.h @@ -0,0 +1,30 @@ +/******************************************************************************** + * 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_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_PROVIDER_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_PROVIDER_H + +#include "score/mw/com/test/common_test_resources/check_point_control.h" + +#include + +namespace score::mw::com::test +{ + +void DoProviderActions(CheckPointControl& check_point_control, + score::cpp::stop_token test_stop_token, + int argc, + const char** argv) noexcept; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_PROVIDER_H diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/provider_restart_application.cpp b/score/mw/com/test/partial_restart/methods/provider_restart/provider_restart_application.cpp new file mode 100644 index 000000000..e5631f395 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/provider_restart_application.cpp @@ -0,0 +1,157 @@ +/******************************************************************************** + * 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/partial_restart/methods/provider_restart/provider_restart_application.h" +#include "score/mw/com/test/partial_restart/methods/provider_restart/controller.h" + +#include "score/mw/com/test/common_test_resources/general_resources.h" +#include "score/mw/com/test/common_test_resources/stop_token_sig_term_handler.h" + +#include + +#include +#include +#include +#include + +namespace +{ + +/// \brief Test parameters for the ITF test variants for provider restart with methods. +/// \details We have four variants for provider restart ITF with methods. This is reflected +/// in the test parameters with_proxy and kill_provider: +/// ITF variant 1: Provider graceful/normal restart, while having a consumer with proxy +/// -> with_proxy{true}, kill_provider{false} +/// ITF variant 2: Provider graceful/normal restart, without proxy +/// -> with_proxy{false}, kill_provider{false} +/// ITF variant 3: Provider kill/crash restart, while having a consumer with proxy +/// -> with_proxy{true}, kill_provider{true} +/// ITF variant 4: Provider kill/crash restart, without proxy +/// -> with_proxy{false}, kill_provider{true} +struct TestParameters +{ + std::optional service_instance_manifest{}; + std::size_t number_test_iterations{}; + /// \brief shall a proxy be created on consumer side (which then also tests implicitly proxy-auto-reconnect) + bool with_proxy{true}; + /// \brief shall the provider be killed (true) or gracefully shutdown (false) before restart. + bool kill_provider{false}; +}; + +std::optional ParseTestParameters(int argc, const char** argv) noexcept +{ + namespace po = boost::program_options; + + TestParameters test_parameters{}; + + // Reading in cmd-line params + po::options_description options; + + std::string service_instance_manifest{}; + + // clang-format off + options.add_options() + ("help", "Display the help message") + ("service_instance_manifest", po::value(&service_instance_manifest)->default_value(""), "Path to the com configuration file") + ("turns,t", po::value(&test_parameters.number_test_iterations), "Number of cycles (provider restarts) to be done") + ("kill", po::value(&test_parameters.kill_provider), "Shall the provider get killed before restart or gracefully shutdown?") + ("with-proxy", po::value(&test_parameters.with_proxy), "Shall a proxy instance be created from any found service handle?"); + // clang-format on + po::variables_map args; + const auto parsed_args = + po::command_line_parser{argc, argv} + .options(options) + .style(po::command_line_style::unix_style | po::command_line_style::allow_long_disguise) + .run(); + po::store(parsed_args, args); + + if (args.count("help") > 0U) + { + std::cerr << options << std::endl; + return {}; + } + + po::notify(args); + + if (service_instance_manifest != "") + { + test_parameters.service_instance_manifest = service_instance_manifest; + } + return test_parameters; +} + +} // namespace + +int main(int argc, const char** argv) +{ + // Prerequisites for the test steps/sequence + score::cpp::stop_source test_stop_source{}; + const bool sig_term_handler_setup_success = score::mw::com::SetupStopTokenSigTermHandler(test_stop_source); + if (!sig_term_handler_setup_success) + { + std::cerr << "Test main: Unable to set signal handler for SIGINT and/or SIGTERM, cautiously continuing.\n"; + } + + const auto test_parameters_result = ParseTestParameters(argc, argv); + if (!(test_parameters_result.has_value())) + { + std::cerr << "Test main: Could not parse test parameters, exiting." << std::endl; + return EXIT_FAILURE; + } + const auto& test_parameters = test_parameters_result.value(); + score::cpp::set_assertion_handler(&score::mw::com::test::assertion_stdout_handler); + const int mw_com_argc = test_parameters.service_instance_manifest.has_value() ? argc : 0; + const char** mw_com_argv = test_parameters.service_instance_manifest.has_value() ? argv : nullptr; + + int test_result{}; + for (std::size_t test_iteration = 1U; test_iteration <= test_parameters.number_test_iterations; ++test_iteration) + { + std::cerr << "Test Main: Running iteration " << test_iteration << " of " + << test_parameters.number_test_iterations << " of Provider-Restart-Methods-Test" << std::endl; + + if (test_parameters.kill_provider == false) + { + if (test_parameters.with_proxy) + { + test_result = score::mw::com::test::DoProviderNormalRestartWithProxy( + test_stop_source.get_token(), mw_com_argc, mw_com_argv); + } + else + { + test_result = score::mw::com::test::DoProviderNormalRestartWithoutProxy( + test_stop_source.get_token(), mw_com_argc, mw_com_argv); + } + } + else + { + if (test_parameters.with_proxy) + { + test_result = score::mw::com::test::DoProviderKillRestartWithProxy( + test_stop_source.get_token(), mw_com_argc, mw_com_argv); + } + else + { + test_result = score::mw::com::test::DoProviderKillRestartWithoutProxy( + test_stop_source.get_token(), mw_com_argc, mw_com_argv); + } + } + + if (test_result != EXIT_SUCCESS) + { + std::cerr << "Test Main: Iteration " << test_iteration << " of " << test_parameters.number_test_iterations + << " of Provider-Restart-Methods-Test failed. Skipping any further iteration." << std::endl; + break; + } + } + + return test_result; +} diff --git a/score/mw/com/test/partial_restart/methods/provider_restart/provider_restart_application.h b/score/mw/com/test/partial_restart/methods/provider_restart/provider_restart_application.h new file mode 100644 index 000000000..a2e064a37 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/provider_restart/provider_restart_application.h @@ -0,0 +1,16 @@ +/******************************************************************************** + * 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_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_PROVIDER_RESTART_APPLICATION_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_PROVIDER_RESTART_APPLICATION_H + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_PROVIDER_RESTART_PROVIDER_RESTART_APPLICATION_H diff --git a/score/mw/com/test/partial_restart/methods/test_method_datatype.cpp b/score/mw/com/test/partial_restart/methods/test_method_datatype.cpp new file mode 100644 index 000000000..6fba867b0 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/test_method_datatype.cpp @@ -0,0 +1,20 @@ +/******************************************************************************** + * 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/partial_restart/methods/test_method_datatype.h" + +// Explicit template instantiation for proxy and skeleton +namespace score::mw::com::test +{ +template class TestMethodServiceInterface; +template class TestMethodServiceInterface; +} // namespace score::mw::com::test diff --git a/score/mw/com/test/partial_restart/methods/test_method_datatype.h b/score/mw/com/test/partial_restart/methods/test_method_datatype.h new file mode 100644 index 000000000..441668d35 --- /dev/null +++ b/score/mw/com/test/partial_restart/methods/test_method_datatype.h @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_TEST_METHOD_DATATYPE_H +#define SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_TEST_METHOD_DATATYPE_H + +#include "score/mw/com/impl/methods/proxy_method_with_in_args_and_return.h" +#include "score/mw/com/impl/methods/skeleton_method.h" +#include "score/mw/com/types.h" + +#include +#include + +namespace score::mw::com::test +{ + +template +class TestMethodServiceInterface : public Trait::Base +{ + public: + using Trait::Base::Base; + + static const std::vector method_names; + + // Method: takes two int32 arguments, returns int32 + typename Trait::template Method test_method_{*this, method_names[0]}; +}; + +template +const std::vector TestMethodServiceInterface::method_names{"test_method"}; + +using TestMethodServiceProxy = AsProxy; +using TestMethodServiceSkeleton = AsSkeleton; + +} // namespace score::mw::com::test + +#endif // SCORE_MW_COM_TEST_PARTIAL_RESTART_METHODS_TEST_METHOD_DATATYPE_H diff --git a/score/mw/com/types.h b/score/mw/com/types.h index 9200f365a..706cfefe8 100644 --- a/score/mw/com/types.h +++ b/score/mw/com/types.h @@ -89,6 +89,11 @@ using SamplePtr = impl::SamplePtr; template using SampleAllocateePtr = impl::SampleAllocateePtr; +/// \api +/// A pointer type which carries a pointer to the method return value in shared memory. +template +using MethodReturnTypePtr = impl::MethodReturnTypePtr; + /// \api /// \brief Callback for event notifications on proxy side. /// \requirement SWS_CM_00309