diff --git a/score/mw/com/test/generic_skeleton/BUILD.bazel b/score/mw/com/test/generic_skeleton/BUILD.bazel new file mode 100644 index 000000000..cac1ad1ad --- /dev/null +++ b/score/mw/com/test/generic_skeleton/BUILD.bazel @@ -0,0 +1,144 @@ +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_cc//cc:defs.bzl", "cc_binary") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") +load("//score/mw/com/test:pkg_application.bzl", "pkg_application") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "generic_typed_interaction_64_byte_app", + srcs = ["generic_typed_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=64"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +cc_binary( + name = "generic_typed_interaction_16_byte_app", + srcs = ["generic_typed_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=16"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +cc_binary( + name = "generic_typed_interaction_8_byte_app", + srcs = ["generic_typed_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=8"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +cc_binary( + name = "generic_typed_interaction_32_byte_app", + srcs = ["generic_typed_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=32"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +pkg_application( + name = "generic_typed_interaction_app-pkg", + app_name = "generic_typed_interaction_app", + bin = [ + ":generic_typed_interaction_64_byte_app", + ":generic_typed_interaction_16_byte_app", + ":generic_typed_interaction_8_byte_app", + ":generic_typed_interaction_32_byte_app", + ], + etc = [ + "mw_com_config.json", + "logging.json", + ], + visibility = ["//score/mw/com/test/generic_skeleton:__subpackages__"], +) + +cc_binary( + name = "generic_generic_interaction_64_byte_app", + srcs = ["generic_generic_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=64"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +cc_binary( + name = "generic_generic_interaction_32_byte_app", + srcs = ["generic_generic_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=32"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +cc_binary( + name = "generic_generic_interaction_16_byte_app", + srcs = ["generic_generic_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=16"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +cc_binary( + name = "generic_generic_interaction_8_byte_app", + srcs = ["generic_generic_interaction_app.cpp"], + defines = ["PAYLOAD_SIZE=8"], + features = COMPILER_WARNING_FEATURES, + deps = [ + "//score/mw/com", + "//score/mw/com/test/common_test_resources:sample_sender_receiver", + "//score/mw/com/test/common_test_resources:sctf_test_runner", + ], +) + +pkg_application( + name = "generic_generic_interaction_app-pkg", + app_name = "generic_generic_interaction_app", + bin = [ + ":generic_generic_interaction_64_byte_app", + ":generic_generic_interaction_32_byte_app", + ":generic_generic_interaction_16_byte_app", + ":generic_generic_interaction_8_byte_app", + ], + etc = [ + "mw_com_config.json", + "logging.json", + ], + visibility = ["//score/mw/com/test/generic_skeleton:__subpackages__"], +) diff --git a/score/mw/com/test/generic_skeleton/generic_generic_interaction_app.cpp b/score/mw/com/test/generic_skeleton/generic_generic_interaction_app.cpp new file mode 100644 index 000000000..1057c16c4 --- /dev/null +++ b/score/mw/com/test/generic_skeleton/generic_generic_interaction_app.cpp @@ -0,0 +1,204 @@ +#include "score/mw/com/impl/generic_proxy.h" +#include "score/mw/com/impl/generic_skeleton.h" +#include "score/mw/com/impl/instance_specifier.h" +#include "score/mw/com/runtime.h" +#include "score/mw/com/runtime_configuration.h" +#include "score/mw/log/logging.h" + +#include +#include +#include +#include +#include +#include +#include + +// Default to 64-byte if not specified by the build system +#ifndef PAYLOAD_SIZE +#define PAYLOAD_SIZE 64 +#endif + +namespace +{ + +struct MyEventData +{ + uint64_t counter; +#if PAYLOAD_SIZE > 8 + char padding[PAYLOAD_SIZE - 8]; +#endif +}; + +constexpr std::string_view kInstanceSpecifier = "/test/generic/generic/interaction"; +constexpr int kSamplesToProcess = 30; +constexpr int kSamplesToSubscribe = 5; + +#if PAYLOAD_SIZE == 64 +constexpr std::string_view kEventName = "Event64Byte"; +#elif PAYLOAD_SIZE == 32 +constexpr std::string_view kEventName = "Event32Byte"; +#elif PAYLOAD_SIZE == 16 +constexpr std::string_view kEventName = "Event16Byte"; +#elif PAYLOAD_SIZE == 8 +constexpr std::string_view kEventName = "Event8Byte"; +#else +#error "Unsupported payload size configured." +#endif + +int run_provider() +{ + const auto instance_specifier = score::mw::com::impl::InstanceSpecifier::Create(kInstanceSpecifier).value(); + std::cout << "[PROVIDER] Instance specifier created." << std::endl; + const score::mw::com::impl::DataTypeMetaInfo meta{sizeof(MyEventData), alignof(MyEventData)}; + std::cout << "[PROVIDER] DataTypeMetaInfo created (size=" << sizeof(MyEventData) + << ", align=" << alignof(MyEventData) << ")." << std::endl; + const std::vector events = {{kEventName, meta}}; + std::cout << "[PROVIDER] EventInfo vector created for event: " << kEventName << std::endl; + + score::mw::com::impl::GenericSkeletonServiceElementInfo create_params; + create_params.events = events; + std::cout << "[PROVIDER] GenericSkeletonServiceElementInfo prepared." << std::endl; + + std::cout << "[PROVIDER] Calling GenericSkeleton::Create..." << std::endl; + auto skeleton_res = score::mw::com::impl::GenericSkeleton::Create(instance_specifier, create_params); + if (!skeleton_res.has_value()) + { + std::cerr << "[PROVIDER] GenericSkeleton::Create FAILED." << std::endl; + return 1; + } + auto& skeleton = skeleton_res.value(); + std::cout << "[PROVIDER] GenericSkeleton created." << std::endl; + + std::cout << "[PROVIDER] Calling skeleton.OfferService()..." << std::endl; + if (!skeleton.OfferService().has_value()) + { + std::cerr << "[PROVIDER] OfferService FAILED." << std::endl; + return 1; + } + std::cout << "[PROVIDER] OfferService SUCCEEDED." << std::endl; + + std::cout << "[PROVIDER] Getting event reference for " << kEventName << "..." << std::endl; + auto it = skeleton.GetEvents().find(kEventName); + if (it == skeleton.GetEvents().cend()) + { + std::cerr << "[PROVIDER] Could not find event: " << kEventName << std::endl; + return 1; + } + std::cout << "[PROVIDER] Event reference obtained." << std::endl; + + // Get reference to the GenericSkeletonEvent + auto& generic_event = const_cast(it->second); + + std::cout << "[PROVIDER] Generic-Generic " << PAYLOAD_SIZE << "-byte - Waiting 5s for consumer to subscribe..." + << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "[PROVIDER] Finished initial 5s sleep." << std::endl; + + for (int i = 0; i < kSamplesToProcess; ++i) + { + auto sample_res = generic_event.Allocate(); + if (!sample_res.has_value()) + { + std::cerr << "[PROVIDER] Allocation failed for sample: " << i << std::endl; + return 1; + } + std::cout << "[PROVIDER] Sample " << i << " allocated." << std::endl; + + auto* typed_sample = static_cast(sample_res.value().Get()); + typed_sample->counter = i; + + std::cout << "[PROVIDER] Sending sample: " << i << std::endl; + generic_event.Send(std::move(sample_res.value())); + std::cout << "[PROVIDER] " << PAYLOAD_SIZE << "-byte Event Sent sample: " << i << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + std::cout << "[PROVIDER] All samples sent." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(15)); + std::cout << "[PROVIDER] Finished post-send 15s sleep. Calling StopOfferService()..." << std::endl; + skeleton.StopOfferService(); + std::cout << "[PROVIDER] StopOfferService() completed." << std::endl; + return 0; +} + +int run_consumer() +{ + const auto instance_specifier = score::mw::com::impl::InstanceSpecifier::Create(kInstanceSpecifier).value(); + + score::Result> handles_res; + int retries = 0; + while (retries < 50) + { + handles_res = score::mw::com::impl::GenericProxy::FindService(instance_specifier); + if (handles_res.has_value() && !handles_res.value().empty()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + retries++; + } + if (!handles_res.has_value() || handles_res.value().empty()) + return 1; + + auto proxy_res = score::mw::com::impl::GenericProxy::Create(handles_res.value()[0]); + if (!proxy_res.has_value()) + return 1; + auto& proxy = proxy_res.value(); + + auto event_it = proxy.GetEvents().find(kEventName); + if (event_it == proxy.GetEvents().cend()) + return 1; + + // Get reference to the GenericProxyEvent + auto& generic_event = event_it->second; + generic_event.Subscribe(kSamplesToSubscribe); + + uint64_t expected = 0; + uint64_t received = 0; + int data_mismatches = 0; + + while (received < kSamplesToProcess) + { + // std::cout << "[CONSUMER] " << PAYLOAD_SIZE << "-byte Waking up, calling GetNewSamples..." << std::endl; + + // The receiver callback operates on type-erased memory (SamplePtr) + generic_event.GetNewSamples( + [&](auto sample) { + auto* typed_sample = static_cast(sample.get()); + if (typed_sample->counter != expected) + { + std::cerr << "[CONSUMER] " << PAYLOAD_SIZE << "-byte Data mismatch! Expected: " << expected + << ", got: " << typed_sample->counter << std::endl; + data_mismatches++; + } + else + { + std::cout << "[CONSUMER] " << PAYLOAD_SIZE + << "-byte Event Received sample: " << typed_sample->counter << std::endl; + } + expected++; + received++; + }, + kSamplesToSubscribe); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + if (data_mismatches > 0) + { + std::cerr << "[CONSUMER] Test failed with " << data_mismatches << " mismatches." << std::endl; + return 1; + } + return 0; +} +} // namespace + +int main(int argc, const char* argv[]) +{ + std::string mode; + for (int i = 1; i < argc; ++i) + if (std::string(argv[i]) == "--mode" && i + 1 < argc) + mode = argv[++i]; + score::mw::com::runtime::InitializeRuntime(score::mw::com::runtime::RuntimeConfiguration(argc, argv)); + if (mode == "provider") + return run_provider(); + if (mode == "consumer") + return run_consumer(); + return 1; +} diff --git a/score/mw/com/test/generic_skeleton/generic_typed_interaction_app.cpp b/score/mw/com/test/generic_skeleton/generic_typed_interaction_app.cpp new file mode 100644 index 000000000..fee3d2a4a --- /dev/null +++ b/score/mw/com/test/generic_skeleton/generic_typed_interaction_app.cpp @@ -0,0 +1,222 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/com/impl/generic_skeleton.h" +#include "score/mw/com/impl/instance_specifier.h" +#include "score/mw/com/impl/proxy_event.h" +#include "score/mw/com/impl/traits.h" +#include "score/mw/com/runtime.h" +#include "score/mw/com/runtime_configuration.h" +#include "score/mw/log/logging.h" + +#include +#include +#include +#include +#include +#include + +// Default to 64-byte if not specified by the build system +#ifndef PAYLOAD_SIZE +#define PAYLOAD_SIZE 64 +#endif + +namespace +{ + +struct MyEventData +{ + uint64_t counter; +#if PAYLOAD_SIZE > 8 + char padding[PAYLOAD_SIZE - 8]; +#endif +}; + +constexpr std::string_view kInstanceSpecifier = "/test/generic/typed/interaction"; +constexpr int kSamplesToProcess = 30; +constexpr int kSamplesToSubscribe = 5; + +#if PAYLOAD_SIZE == 64 +constexpr std::string_view kEventName = "Event64Byte"; +#elif PAYLOAD_SIZE == 32 +constexpr std::string_view kEventName = "Event32Byte"; +#elif PAYLOAD_SIZE == 16 +constexpr std::string_view kEventName = "Event16Byte"; +#elif PAYLOAD_SIZE == 8 +constexpr std::string_view kEventName = "Event8Byte"; +#else +#error "Unsupported payload size configured." +#endif + +int run_provider() +{ + const auto instance_specifier = + score::mw::com::impl::InstanceSpecifier::Create(std::string{kInstanceSpecifier}).value(); + const score::mw::com::impl::DataTypeMetaInfo meta{sizeof(MyEventData), alignof(MyEventData)}; + const std::vector events = {{kEventName, meta}}; + + score::mw::com::impl::GenericSkeletonServiceElementInfo create_params; + create_params.events = events; + + auto skeleton_res = score::mw::com::impl::GenericSkeleton::Create(instance_specifier, create_params); + if (!skeleton_res.has_value()) + { + score::mw::log::LogFatal("GenericSkeletonProvider") << "Failed to create skeleton."; + return 1; + } + auto& skeleton = skeleton_res.value(); + + if (!skeleton.OfferService().has_value()) + { + score::mw::log::LogFatal("GenericSkeletonProvider") << "Failed to offer service."; + return 1; + } + + auto it = skeleton.GetEvents().find(kEventName); + if (it == skeleton.GetEvents().cend()) + { + score::mw::log::LogFatal("GenericSkeletonProvider") << "Failed to find event in skeleton."; + return 1; + } + auto& generic_event = const_cast(it->second); + + // Wait for the consumer to start and subscribe BEFORE sending data + score::mw::log::LogInfo("GenericSkeletonProvider") + << PAYLOAD_SIZE << "-byte - Waiting 5s for consumer to subscribe..."; + std::this_thread::sleep_for(std::chrono::seconds(5)); + + for (int i = 0; i < kSamplesToProcess; ++i) + { + auto sample_res = generic_event.Allocate(); + if (!sample_res.has_value()) + { + score::mw::log::LogFatal("GenericSkeletonProvider") << "Failed to allocate sample."; + return 1; + } + auto* typed_sample = static_cast(sample_res.value().Get()); + typed_sample->counter = i; + generic_event.Send(std::move(sample_res.value())); + + score::mw::log::LogInfo("GenericSkeletonProvider") << PAYLOAD_SIZE << "-byte Event Sent sample: " << i; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + std::this_thread::sleep_for(std::chrono::seconds(15)); + skeleton.StopOfferService(); + return 0; +} + +template +class MyTestService : public Trait::Base +{ + public: + using Trait::Base::Base; + typename Trait::template Event event_{*this, std::string{kEventName}}; +}; +using MyTestServiceProxy = score::mw::com::impl::AsProxy; + +int run_consumer() +{ + const auto instance_specifier = + score::mw::com::impl::InstanceSpecifier::Create(std::string{kInstanceSpecifier}).value(); + + score::Result> handles_res; + int retries = 0; + while (retries < 50) // Try for up to 5 seconds + { + handles_res = MyTestServiceProxy::FindService(instance_specifier); + if (handles_res.has_value() && !handles_res.value().empty()) + { + break; // Service found! + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + retries++; + } + + if (!handles_res.has_value() || handles_res.value().empty()) + { + score::mw::log::LogFatal("TypedProxyConsumer") << "Failed to find service after waiting."; + return 1; + } + + auto proxy_res = MyTestServiceProxy::Create(handles_res.value()[0]); + if (!proxy_res.has_value()) + { + score::mw::log::LogFatal("TypedProxyConsumer") << "Failed to create proxy."; + return 1; + } + auto& proxy = proxy_res.value(); + + uint64_t received = 0; + uint64_t expected = 0; + int data_mismatches = 0; + proxy.event_.Subscribe(kSamplesToSubscribe); + + while (received < kSamplesToProcess) + { + proxy.event_.GetNewSamples( + [&](score::mw::com::SamplePtr sample) { + if (sample->counter != expected) + { + score::mw::log::LogFatal("TypedProxyConsumer") + << PAYLOAD_SIZE << "-byte data failed! Exp " << expected << ", got " << sample->counter; + data_mismatches++; + } + else + { + score::mw::log::LogInfo("TypedProxyConsumer") + << "Received " << PAYLOAD_SIZE << "-byte sample: " << sample->counter; + } + expected++; + received++; + }, + kSamplesToSubscribe); + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + if (data_mismatches > 0) + { + score::mw::log::LogFatal("TypedProxyConsumer") << "Test failed with " << data_mismatches << " mismatches."; + return 1; + } + return 0; +} + +} // namespace + +int main(int argc, const char* argv[]) +{ + std::string mode; + for (int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + if (arg == "--mode" && i + 1 < argc) + { + mode = argv[++i]; + } + } + + score::mw::com::runtime::InitializeRuntime(score::mw::com::runtime::RuntimeConfiguration(argc, argv)); + + if (mode == "provider") + { + return run_provider(); + } + else if (mode == "consumer") + { + return run_consumer(); + } + + score::mw::log::LogFatal("Main") << "Invalid or missing mode. Use --mode provider or --mode consumer."; + return 1; +} diff --git a/score/mw/com/test/generic_skeleton/integration_test/BUILD.bazel b/score/mw/com/test/generic_skeleton/integration_test/BUILD.bazel new file mode 100644 index 000000000..b2ebed53b --- /dev/null +++ b/score/mw/com/test/generic_skeleton/integration_test/BUILD.bazel @@ -0,0 +1,37 @@ +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("//quality/integration_testing:integration_testing.bzl", "integration_test") + +package(default_visibility = ["//visibility:public"]) + +integration_test( + name = "test_generic_typed_interaction", + timeout = "moderate", + srcs = ["test_generic_typed_interaction.py"], + filesystem = "//score/mw/com/test/generic_skeleton:generic_typed_interaction_app-pkg", +) + +integration_test( + name = "test_generic_generic_interaction", + timeout = "moderate", + srcs = ["test_generic_generic_interaction.py"], + filesystem = "//score/mw/com/test/generic_skeleton:generic_generic_interaction_app-pkg", +) + +test_suite( + name = "tests", + tests = [ + ":test_generic_generic_interaction", + ":test_generic_typed_interaction", + ], +) diff --git a/score/mw/com/test/generic_skeleton/integration_test/test_generic_generic_interaction.py b/score/mw/com/test/generic_skeleton/integration_test/test_generic_generic_interaction.py new file mode 100644 index 000000000..4a76314c0 --- /dev/null +++ b/score/mw/com/test/generic_skeleton/integration_test/test_generic_generic_interaction.py @@ -0,0 +1,97 @@ +# 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 + +import time +import logging +import pytest + +logger = logging.getLogger(__name__) + +def run_interaction_app(target, app_bin, mode, config_path, cwd, wait_on_exit=False, **kwargs): + """Helper to run an application using the framework's native wrap_exec method.""" + args = ["--mode", mode, "--service_instance_manifest", config_path] + return target.wrap_exec(app_bin, args, cwd=cwd, wait_on_exit=wait_on_exit, **kwargs) + +@pytest.mark.xfail(reason="Generic Skeleton base pointer bug") +def test_generic_generic_interaction_64_byte(target): + """ + Tests data validation for a 64-byte payload where both the + provider and consumer are type-erased generic interfaces. + """ + app_root = "/opt/generic_generic_interaction_app/" + app_bin = "./bin/generic_generic_interaction_64_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + # ADDED enforce_clean_shutdown=False and disabled LSAN here + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) # Give provider a moment to initialize + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass + +@pytest.mark.xfail(reason="Generic Skeleton base pointer bug") +def test_generic_generic_interaction_32_byte(target): + """ + Tests data validation for a 32-byte payload where both the + provider and consumer are type-erased generic interfaces. + """ + app_root = "/opt/generic_generic_interaction_app/" + app_bin = "./bin/generic_generic_interaction_32_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + # ADDED enforce_clean_shutdown=False and disabled LSAN here + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) # Give provider a moment to initialize + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass + +@pytest.mark.xfail(reason="Generic Skeleton base pointer bug") +def test_generic_generic_interaction_16_byte(target): + """ + Tests data validation for a 16-byte payload where both the + provider and consumer are type-erased generic interfaces. + """ + app_root = "/opt/generic_generic_interaction_app/" + app_bin = "./bin/generic_generic_interaction_16_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + # ADDED enforce_clean_shutdown=False and disabled LSAN here + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) # Give provider a moment to initialize + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass + +@pytest.mark.xfail(reason="Generic Skeleton base pointer bug") +def test_generic_generic_interaction_8_byte(target): + """ + Tests data validation for an 8-byte payload where both the + provider and consumer are type-erased generic interfaces. + """ + app_root = "/opt/generic_generic_interaction_app/" + app_bin = "./bin/generic_generic_interaction_8_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + # ADDED enforce_clean_shutdown=False and disabled LSAN here + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) # Give provider a moment to initialize + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass \ No newline at end of file diff --git a/score/mw/com/test/generic_skeleton/integration_test/test_generic_typed_interaction.py b/score/mw/com/test/generic_skeleton/integration_test/test_generic_typed_interaction.py new file mode 100644 index 000000000..2f8c7b828 --- /dev/null +++ b/score/mw/com/test/generic_skeleton/integration_test/test_generic_typed_interaction.py @@ -0,0 +1,93 @@ +# 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 + +import time +import logging +import pytest + +logger = logging.getLogger(__name__) + +def run_interaction_app(target, app_bin, mode, config_path, cwd, wait_on_exit=False, **kwargs): + """Helper to run an application using the framework's native wrap_exec method.""" + args = ["--mode", mode, "--service_instance_manifest", config_path] + return target.wrap_exec(app_bin, args, cwd=cwd, wait_on_exit=wait_on_exit, **kwargs) + +@pytest.mark.xfail(reason="Generic Skeleton memory alignment and base pointer bug") +def test_generic_typed_interaction_64_byte(target): + """ + Tests data validation and boundary checks for a 64-byte payload. + """ + app_root = "/opt/generic_typed_interaction_app/" + app_bin = "./bin/generic_typed_interaction_64_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + # Added enforce_clean_shutdown=False and disabled LSAN so forceful shutdown doesn't fail the test + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + # Give the provider a moment to initialize and offer the service + # to prevent a race condition where the consumer starts too quickly. + time.sleep(2) + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + # INCREASED wait_timeout to 60 to ensure it has time to finish + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass + +@pytest.mark.xfail(reason="Generic Skeleton memory alignment and base pointer bug") +def test_generic_typed_interaction_32_byte(target): + """ + Tests data validation and boundary checks for a 32-byte payload. + """ + app_root = "/opt/generic_typed_interaction_app/" + app_bin = "./bin/generic_typed_interaction_32_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass + +@pytest.mark.xfail(reason="Generic Skeleton memory alignment and base pointer bug") +def test_generic_typed_interaction_16_byte(target): + """ + Tests data validation and boundary checks for a 16-byte payload. + """ + app_root = "/opt/generic_typed_interaction_app/" + app_bin = "./bin/generic_typed_interaction_16_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass + +@pytest.mark.xfail(reason="Generic Skeleton memory alignment and base pointer bug") +def test_generic_typed_interaction_8_byte(target): + """ + Tests data validation and boundary checks for an 8-byte payload. + """ + app_root = "/opt/generic_typed_interaction_app/" + app_bin = "./bin/generic_typed_interaction_8_byte_app" + config_path = "./etc/mw_com_config.json" + + logger.info(f"Starting provider: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "provider", config_path, cwd=app_root, enforce_clean_shutdown=False, env={"ASAN_OPTIONS": "detect_leaks=0"}): + time.sleep(2) + + logger.info(f"Starting consumer: {app_bin} in {app_root}") + with run_interaction_app(target, app_bin, "consumer", config_path, cwd=app_root, wait_on_exit=True, wait_timeout=60): + pass \ No newline at end of file diff --git a/score/mw/com/test/generic_skeleton/logging.json b/score/mw/com/test/generic_skeleton/logging.json new file mode 100644 index 000000000..cec2ece39 --- /dev/null +++ b/score/mw/com/test/generic_skeleton/logging.json @@ -0,0 +1,8 @@ +{ + "appId": "GENT", + "appDesc": "generic_typed_interaction_test", + "logLevel": "kInfo", + "logLevelThresholdConsole": "kInfo", + "logMode": "kConsole", + "dynamicDatarouterIdentifiers" : true +} \ No newline at end of file diff --git a/score/mw/com/test/generic_skeleton/mw_com_config.json b/score/mw/com/test/generic_skeleton/mw_com_config.json new file mode 100644 index 000000000..420bde2d4 --- /dev/null +++ b/score/mw/com/test/generic_skeleton/mw_com_config.json @@ -0,0 +1,142 @@ +{ + "serviceTypes": [ + { + "serviceTypeName": "/test/service/GenericTypedInteraction", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 7001, + "events": [ + { + "eventName": "Event8Byte", + "eventId": 1 + }, + { + "eventName": "Event64Byte", + "eventId": 2 + }, + { + "eventName": "Event16Byte", + "eventId": 3 + }, + { + "eventName": "Event32Byte", + "eventId": 4 + } + ] + } + ] + }, + { + "serviceTypeName": "/test/service/GenericGenericInteraction", + "version": { + "major": 1, + "minor": 0 + }, + "bindings": [ + { + "binding": "SHM", + "serviceId": 7002, + "events": [ + { + "eventName": "Event8Byte", + "eventId": 1 + }, + { + "eventName": "Event64Byte", + "eventId": 2 + }, + { + "eventName": "Event16Byte", + "eventId": 3 + }, + { + "eventName": "Event32Byte", + "eventId": 4 + } + ] + } + ] + } + ], + "serviceInstances": [ + { + "instanceSpecifier": "/test/generic/typed/interaction", + "serviceTypeName": "/test/service/GenericTypedInteraction", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "Event8Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + }, + { + "eventName": "Event64Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + }, + { + "eventName": "Event16Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + }, + { + "eventName": "Event32Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + } + ] + } + ] + }, + { + "instanceSpecifier": "/test/generic/generic/interaction", + "serviceTypeName": "/test/service/GenericGenericInteraction", + "version": { + "major": 1, + "minor": 0 + }, + "instances": [ + { + "instanceId": 1, + "asil-level": "QM", + "binding": "SHM", + "events": [ + { + "eventName": "Event64Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + }, + { + "eventName": "Event32Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + }, + { + "eventName": "Event8Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + }, + { + "eventName": "Event16Byte", + "numberOfSampleSlots": 5, + "maxSubscribers": 1 + } + ] + } + ] + } + ] +} \ No newline at end of file