From db58bfd36558237ff1d99df2def007094a82e8e7 Mon Sep 17 00:00:00 2001 From: Saumya-R Date: Thu, 2 Jul 2026 17:26:09 +0530 Subject: [PATCH 1/3] Added test cases for TestLifecycleApplicationIf added test cases for lifecycle ipc alive security policy enforcement validation activation for dependency gated activation added test cases for failure diagnostics correlated added test cases for control IPC routing validation test cases for deadline monitore checkpoint IPC added test cases for validating timestamp consistency between lifecycle events and system time added test cases for run-target transitions remain synchronized with orchestrator added test cases for validating supervision and monitoring isolation across multiple Launch Manager instances test case added for invalid lifecycle configuration added test cases for baselibs requirements local review fixes --- BUILD | 34 ++++- README.md | 26 +++- feature_integration_tests/README.md | 34 ++++- .../test_cases/conftest.py | 44 +++++- .../test_cases/requirements.txt.lock | 22 +++ .../test_lifecycle_application_if.py | 84 ++++++++++++ .../test_lifecycle_baselibs_integration.py | 127 +++++++++++++++++ ...st_lifecycle_comm_dependency_activation.py | 107 +++++++++++++++ .../test_lifecycle_config_validation_gate.py | 106 +++++++++++++++ .../lifecycle/test_lifecycle_ipc_alive_if.py | 91 +++++++++++++ .../lifecycle/test_lifecycle_ipc_controlif.py | 103 ++++++++++++++ .../test_lifecycle_ipc_deadline_monitor_if.py | 94 +++++++++++++ .../test_lifecycle_logging_correlation.py | 107 +++++++++++++++ ...test_lifecycle_multi_instance_isolation.py | 84 ++++++++++++ .../test_lifecycle_orchestrator_sync.py | 104 ++++++++++++++ .../test_lifecycle_security_isolation.py | 100 ++++++++++++++ .../lifecycle/test_lifecycle_time_sync.py | 97 +++++++++++++ .../scenarios/lifecycle/application_if.cpp | 113 ++++++++++++++++ .../lifecycle/baselibs_integration.cpp | 128 ++++++++++++++++++ .../lifecycle/comm_dependency_activation.cpp | 73 ++++++++++ .../lifecycle/config_validation_gate.cpp | 70 ++++++++++ .../src/scenarios/lifecycle/ipc_alive_if.cpp | 66 +++++++++ .../src/scenarios/lifecycle/ipc_controlif.cpp | 73 ++++++++++ .../lifecycle/ipc_deadline_monitor_if.cpp | 61 +++++++++ .../lifecycle/logging_correlation.cpp | 75 ++++++++++ .../lifecycle/multi_instance_isolation.cpp | 60 ++++++++ .../scenarios/lifecycle/orchestrator_sync.cpp | 119 ++++++++++++++++ .../lifecycle/security_isolation.cpp | 125 +++++++++++++++++ .../cpp/src/scenarios/lifecycle/time_sync.cpp | 67 +++++++++ .../test_scenarios/cpp/src/scenarios/mod.cpp | 34 ++++- .../src/scenarios/lifecycle/application_if.rs | 68 ++++++++++ .../lifecycle/baselibs_integration.rs | 97 +++++++++++++ .../lifecycle/comm_dependency_activation.rs | 80 +++++++++++ .../lifecycle/config_validation_gate.rs | 68 ++++++++++ .../src/scenarios/lifecycle/ipc_alive_if.rs | 68 ++++++++++ .../src/scenarios/lifecycle/ipc_controlif.rs | 71 ++++++++++ .../lifecycle/ipc_deadline_monitor_if.rs | 73 ++++++++++ .../lifecycle/logging_correlation.rs | 83 ++++++++++++ .../rust/src/scenarios/lifecycle/mod.rs | 60 ++++++++ .../lifecycle/multi_instance_isolation.rs | 69 ++++++++++ .../scenarios/lifecycle/orchestrator_sync.rs | 74 ++++++++++ .../scenarios/lifecycle/security_isolation.rs | 79 +++++++++++ .../rust/src/scenarios/lifecycle/time_sync.rs | 67 +++++++++ .../test_scenarios/rust/src/scenarios/mod.rs | 4 +- 44 files changed, 3376 insertions(+), 13 deletions(-) create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_application_if.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_baselibs_integration.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_comm_dependency_activation.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_config_validation_gate.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_alive_if.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_controlif.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_deadline_monitor_if.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_logging_correlation.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_multi_instance_isolation.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_orchestrator_sync.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_security_isolation.py create mode 100644 feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_time_sync.py create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/baselibs_integration.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/comm_dependency_activation.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/config_validation_gate.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_alive_if.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_controlif.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_deadline_monitor_if.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/logging_correlation.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/multi_instance_isolation.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/time_sync.cpp create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/application_if.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/baselibs_integration.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/comm_dependency_activation.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/config_validation_gate.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_alive_if.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_controlif.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_deadline_monitor_if.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/logging_correlation.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/mod.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/multi_instance_isolation.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/orchestrator_sync.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/security_isolation.rs create mode 100644 feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/time_sync.rs diff --git a/BUILD b/BUILD index 03445323d10..17a41fffb72 100644 --- a/BUILD +++ b/BUILD @@ -12,7 +12,7 @@ # ******************************************************************************* load("@score_docs_as_code//:docs.bzl", "docs") -load("@score_tooling//:defs.bzl", "setup_starpls", "use_format_targets") +load("@score_tooling//:defs.bzl", "copyright_checker", "setup_starpls", "use_format_targets") # Docs-as-code docs( @@ -44,6 +44,38 @@ setup_starpls( # Add target for formatting checks use_format_targets() +# Add copyright check/fix targets: +# - //:copyright.check +# - //:copyright.fix +copyright_checker( + name = "copyright", + srcs = glob( + ["**/*"], + exclude = [ + ".git/**", + ".venv/**", + "bazel-*/**", + "**/*.png", + "**/*.jpg", + "**/*.jpeg", + "**/*.gif", + "**/*.svg", + "**/*.pdf", + "**/*.drawio", + "**/*.ipynb", + "**/*.bin", + "**/*.hash", + "**/*.zip", + "**/*.tar", + "**/*.tar.gz", + "**/*.tgz", + ], + ), + config = "@score_tooling//cr_checker/resources:config", + template = "@score_tooling//cr_checker/resources:templates", + visibility = ["//visibility:public"], +) + exports_files([ "MODULE.bazel", "pyproject.toml", diff --git a/README.md b/README.md index 16c58de3908..0a250e122c5 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,30 @@ To generate a full documentation of all integrated modules, run: bazel run //:docs_combo_experimental ``` +## Feature Integration Tests (FIT) + +Use the Linux config for both Rust and C++ FIT flows: + +```bash +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_rust +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp +``` + +Run only lifecycle application interface checks: + +```bash +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_rust --test_arg=-k --test_arg=lifecycle_application_if +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp --test_arg=-k --test_arg=lifecycle_application_if +``` + +Build scenario binaries directly: + +```bash +bazel build --config=linux-x86_64 //feature_integration_tests/test_scenarios/rust:rust_test_scenarios +bazel build --config=linux-x86_64 //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios +``` + ## Operating system integrations > [!NOTE] @@ -71,7 +95,7 @@ bazel run //:docs_combo_experimental - [Elektrobit corbos Linux for Safety Applications](./images/ebclfsa_aarch64/README.md) - Linux x86_64 -## Workspace support +## Workspace support You can obtain a complete S-CORE workspace, i.e. a git checkout of all modules from `known_good.json`, on the specific branches / commits, integrated into one Bazel build. This helps with cross-module development, debugging, and generally "trying out things". diff --git a/feature_integration_tests/README.md b/feature_integration_tests/README.md index a68a1f9c492..5a2aaef5b86 100644 --- a/feature_integration_tests/README.md +++ b/feature_integration_tests/README.md @@ -32,10 +32,40 @@ bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit To run specific test suites: ```sh -bazel test //feature_integration_tests/test_cases:fit_rust +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_rust bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp ``` +To run lifecycle-focused FIT tests only: + +```sh +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_rust --test_arg=-k --test_arg=lifecycle +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp --test_arg=-k --test_arg=lifecycle +``` + +To run only the new lifecycle application interface requirement test: + +```sh +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_rust --test_arg=-k --test_arg=lifecycle_application_if +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp --test_arg=-k --test_arg=lifecycle_application_if +``` + +To build scenario binaries directly: + +```sh +bazel build --config=linux-x86_64 //feature_integration_tests/test_scenarios/rust:rust_test_scenarios +bazel build --config=linux-x86_64 //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios +``` + +When running pytest directly with scenario pre-build enabled, use an explicit Bazel config: + +```sh +python3 -m pytest feature_integration_tests/test_cases/tests/lifecycle/ --build-scenarios --bazel-config=linux-x86_64 -q -v + +# or via env var +FIT_BAZEL_CONFIG=linux-x86_64 python3 -m pytest feature_integration_tests/test_cases/tests/lifecycle/ --build-scenarios -q -v +``` + ### ITF Tests (QEMU-based) ITF tests run on a QEMU target and require the `itf-qnx-x86_64` config: @@ -49,7 +79,7 @@ bazel test --config=itf-qnx-x86_64 //feature_integration_tests/itf Test scenarios can be listed and run directly for debugging: ```sh -bazel run //feature_integration_tests/test_scenarios/rust:rust_test_scenarios -- --list-scenarios +bazel run --config=linux-x86_64 //feature_integration_tests/test_scenarios/rust:rust_test_scenarios -- --list-scenarios bazel run --config=linux-x86_64 //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios ``` diff --git a/feature_integration_tests/test_cases/conftest.py b/feature_integration_tests/test_cases/conftest.py index 662b7210943..5d99e7076be 100644 --- a/feature_integration_tests/test_cases/conftest.py +++ b/feature_integration_tests/test_cases/conftest.py @@ -10,10 +10,11 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +import os +import subprocess from pathlib import Path import pytest -from testing_utils import BazelTools # Cmdline options @@ -61,6 +62,12 @@ def pytest_addoption(parser): default=180.0, help="Build command timeout in seconds. Default: %(default)s", ) + parser.addoption( + "--bazel-config", + type=str, + default=os.environ.get("FIT_BAZEL_CONFIG", "linux-x86_64"), + help=('Bazel config used when --build-scenarios is enabled (default: env FIT_BAZEL_CONFIG or "linux-x86_64").'), + ) parser.addoption( "--default-execution-timeout", type=float, @@ -70,6 +77,12 @@ def pytest_addoption(parser): # Hooks +def pytest_configure(config: pytest.Config) -> None: + """Register custom markers used by FIT parametrization.""" + config.addinivalue_line("markers", "cpp: mark scenario execution for C++ target") + config.addinivalue_line("markers", "rust: mark scenario execution for Rust target") + + def pytest_collection_modifyitems(items: list[pytest.Function]): for item in items: # Automatically mark tests parametrized with 'version' as 'cpp' or 'rust'. @@ -88,18 +101,35 @@ def pytest_sessionstart(session): # Build scenarios. if session.config.getoption("--build-scenarios"): build_timeout = session.config.getoption("--build-scenarios-timeout") + bazel_config = session.config.getoption("--bazel-config") + + def _build_target(target_name: str) -> None: + command = ["bazel", "build", f"--config={bazel_config}", target_name] + result = subprocess.run( + command, + capture_output=True, + text=True, + check=False, + timeout=build_timeout, + ) + if result.returncode != 0: + stderr_tail = "\n".join(result.stderr.strip().splitlines()[-40:]) + raise RuntimeError( + "Failed to run build with pytest --build-scenarios.\n" + f"Command: {' '.join(command)}\n" + f"Return code: {result.returncode}\n" + f"stderr (last lines):\n{stderr_tail}" + ) # Build Rust test scenarios. - print("Building Rust test scenarios executable...") - rust_tools = BazelTools(option_prefix="rust", build_timeout=build_timeout) + print(f"Building Rust test scenarios executable with --config={bazel_config}...") rust_target_name = session.config.getoption("--rust-target-name") - rust_tools.build(rust_target_name) + _build_target(rust_target_name) # Build C++ test scenarios. - print("Building C++ test scenarios executable...") - cpp_tools = BazelTools(option_prefix="cpp", build_timeout=build_timeout) + print(f"Building C++ test scenarios executable with --config={bazel_config}...") cpp_target_name = session.config.getoption("--cpp-target-name") - cpp_tools.build(cpp_target_name) + _build_target(cpp_target_name) except Exception as e: pytest.exit(str(e), returncode=1) diff --git a/feature_integration_tests/test_cases/requirements.txt.lock b/feature_integration_tests/test_cases/requirements.txt.lock index cfd30002c1a..e39faaad2f1 100644 --- a/feature_integration_tests/test_cases/requirements.txt.lock +++ b/feature_integration_tests/test_cases/requirements.txt.lock @@ -91,6 +91,28 @@ packaging==25.0 \ pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 pygments==2.19.2 \ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_application_if.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_application_if.py new file mode 100644 index 00000000000..393576ecde8 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_application_if.py @@ -0,0 +1,84 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "logic_arc_int__lifecycle__lifecycle_if", + "feat_req__lifecycle__process_state_comm", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleApplicationIf(FitScenario): + """Verify state reporting and daemon-gated signaling for lifecycle application interface.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.application_if" + + @pytest.fixture(scope="class", params=[True, False]) + def daemon_enabled(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, daemon_enabled: bool) -> dict[str, Any]: + return { + "test": { + "daemon_enabled": daemon_enabled, + "signal_name": "SIGUSR1", + } + } + + def test_application_state_is_reported(self, version: str, logs_info_level: LogContainer) -> None: + """Ensure the SCORE application publishes a lifecycle state report.""" + assert version in ("rust", "cpp") + app_state_log = logs_info_level.find_log("component", value="score_application") + assert app_state_log is not None, "Missing SCORE application state report log" + assert app_state_log.state == "state_reported" + assert app_state_log.api == "lifecycle_if" + + def test_conditional_signal_path( + self, + version: str, + daemon_enabled: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify signal dispatch behavior depends on daemon availability.""" + assert version in ("rust", "cpp") + if daemon_enabled: + daemon_log = logs_info_level.find_log("component", value="control_daemon") + assert daemon_log is not None, "Missing control daemon running state log" + assert daemon_log.state == "running" + + dispatched = logs_info_level.find_log("event", value="signal_dispatched") + assert dispatched is not None, "Expected signal_dispatched log when daemon is running" + assert dispatched.condition == "daemon_running" + assert dispatched.signal_name == "SIGUSR1" + assert dispatched.target_process == "score_application" + return + + skipped = logs_info_level.find_log("event", value="signal_skipped") + assert skipped is not None, "Expected signal_skipped log when daemon is not running" + assert skipped.condition == "daemon_not_running" + assert skipped.signal_name == "SIGUSR1" + assert skipped.target_process == "score_application" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_baselibs_integration.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_baselibs_integration.py new file mode 100644 index 00000000000..6e167c39e0e --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_baselibs_integration.py @@ -0,0 +1,127 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__baselibs__structured_logging", + "feat_req__baselibs__json_serialization", + "feat_req__baselibs__monotonic_time_measurement", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleBaselibsIntegration(FitScenario): + """Verify lifecycle flow integrates with common baselibs utilities.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.baselibs_integration" + + @pytest.fixture(scope="class", params=[True, False]) + def json_payload_valid(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def log_backend_ready(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=["valid", "missing", "malformed"]) + def deadline_budget_mode(self, request: pytest.FixtureRequest) -> str: + return str(request.param) + + @pytest.fixture(scope="class") + def test_config( + self, + json_payload_valid: bool, + log_backend_ready: bool, + deadline_budget_mode: str, + ) -> dict[str, Any]: + config = { + "test": { + "json_payload_valid": json_payload_valid, + "log_backend_ready": log_backend_ready, + } + } + if deadline_budget_mode == "valid": + config["test"]["deadline_budget_ms"] = 20 + elif deadline_budget_mode == "malformed": + config["test"]["deadline_budget_ms"] = "invalid" + + return config + + def test_common_baselibs_utility_usage( + self, + version: str, + json_payload_valid: bool, + log_backend_ready: bool, + deadline_budget_mode: str, + logs_info_level: LogContainer, + ) -> None: + """Ensure lifecycle invokes logging, JSON processing and monotonic timing utilities.""" + assert version in ("rust", "cpp") + + bootstrap_log = logs_info_level.find_log("event", value="lifecycle_baselibs_bootstrap") + assert bootstrap_log is not None, "Missing lifecycle_baselibs_bootstrap event" + assert bootstrap_log.used_logging is log_backend_ready + assert bootstrap_log.used_json is json_payload_valid + assert bootstrap_log.used_monotonic_clock is True + + timing_log = logs_info_level.find_log("event", value="lifecycle_baselibs_timing") + assert timing_log is not None, "Missing lifecycle_baselibs_timing event" + if deadline_budget_mode == "valid": + assert timing_log.deadline_budget_ms == 20 + else: + assert timing_log.deadline_budget_ms == 0 + + assert timing_log.measured_duration_ms >= 0 + + json_log = logs_info_level.find_log("event", value="lifecycle_baselibs_json") + assert json_log is not None, "Missing lifecycle_baselibs_json event" + assert json_log.valid is json_payload_valid + + def test_integration_outcome( + self, + version: str, + json_payload_valid: bool, + log_backend_ready: bool, + logs_info_level: LogContainer, + ) -> None: + """Validate successful integration only when JSON payload and logging backend are ready.""" + assert version in ("rust", "cpp") + + integrated = json_payload_valid and log_backend_ready + status_log = logs_info_level.find_log("event", value="lifecycle_baselibs_integration_status") + assert status_log is not None, "Missing lifecycle_baselibs_integration_status event" + assert status_log.status == ("integrated" if integrated else "degraded") + + degraded_log = logs_info_level.find_log("event", value="lifecycle_baselibs_degraded") + if integrated: + assert degraded_log is None, "lifecycle_baselibs_degraded must not be emitted on integrated path" + return + + assert degraded_log is not None, "Expected lifecycle_baselibs_degraded event on degraded path" + if not json_payload_valid: + assert degraded_log.reason == "invalid_json_payload" + return + + assert degraded_log.reason == "logging_backend_unavailable" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_comm_dependency_activation.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_comm_dependency_activation.py new file mode 100644 index 00000000000..8c7ca7e1404 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_comm_dependency_activation.py @@ -0,0 +1,107 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__lifecycle__dependency_check", + "feat_req__lifecycle__check_dependency_exec", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleCommDependencyActivation(FitScenario): + """Verify communication component activation is gated by dependency checks and executability.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.comm_dependency_activation" + + @pytest.fixture(scope="class", params=[True, False]) + def dependency_available(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def dependency_executable(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, dependency_available: bool, dependency_executable: bool) -> dict[str, Any]: + return { + "test": { + "dependency_available": dependency_available, + "dependency_executable": dependency_executable, + "component": "comm_router", + } + } + + def test_dependency_checks_are_reported( + self, + version: str, + dependency_available: bool, + dependency_executable: bool, + logs_info_level: LogContainer, + ) -> None: + """Ensure lifecycle emits dependency presence and execution-check diagnostics.""" + assert version in ("rust", "cpp") + + dep_log = logs_info_level.find_log("event", value="dependency_check") + assert dep_log is not None, "Missing dependency_check event" + assert dep_log.component == "comm_router" + assert dep_log.available is dependency_available + + exec_log = logs_info_level.find_log("event", value="dependency_exec_check") + assert exec_log is not None, "Missing dependency_exec_check event" + assert exec_log.component == "comm_router" + assert exec_log.executable is dependency_executable + + def test_activation_is_dependency_gated( + self, + version: str, + dependency_available: bool, + dependency_executable: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify comm activation only happens when dependency exists and can execute.""" + assert version in ("rust", "cpp") + + can_activate = dependency_available and dependency_executable + + if can_activate: + active_log = logs_info_level.find_log("event", value="comm_activation") + assert active_log is not None, "Expected comm_activation event when dependency checks pass" + assert active_log.status == "activated" + assert active_log.reason == "dependency_ready" + + blocked_log = logs_info_level.find_log("event", value="comm_activation_blocked") + assert blocked_log is None, "comm_activation_blocked must not be emitted when activation succeeds" + return + + blocked_log = logs_info_level.find_log("event", value="comm_activation_blocked") + assert blocked_log is not None, "Expected comm_activation_blocked event when dependency checks fail" + assert blocked_log.status == "blocked" + + if not dependency_available: + assert blocked_log.reason == "dependency_missing" + return + + assert blocked_log.reason == "dependency_not_executable" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_config_validation_gate.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_config_validation_gate.py new file mode 100644 index 00000000000..4a7e6f630a3 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_config_validation_gate.py @@ -0,0 +1,106 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__lifecycle__offline_config_valid", + "feat_req__lifecycle__consistent_dependencies", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleConfigValidationGate(FitScenario): + """Verify invalid lifecycle configs are rejected offline and valid configs stay executable.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.config_validation_gate" + + @pytest.fixture(scope="class", params=[True, False]) + def config_schema_valid(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def dependencies_consistent(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, config_schema_valid: bool, dependencies_consistent: bool) -> dict[str, Any]: + return { + "test": { + "config_schema_valid": config_schema_valid, + "dependencies_consistent": dependencies_consistent, + } + } + + def test_offline_validation_gate( + self, + version: str, + config_schema_valid: bool, + dependencies_consistent: bool, + logs_info_level: LogContainer, + ) -> None: + """Validate offline gate outcome based on config schema and dependency consistency.""" + assert version in ("rust", "cpp") + + gate_log = logs_info_level.find_log("event", value="offline_config_validation") + assert gate_log is not None, "Missing offline_config_validation event" + assert gate_log.schema_valid is config_schema_valid + assert gate_log.dependencies_consistent is dependencies_consistent + + is_valid = config_schema_valid and dependencies_consistent + if is_valid: + assert gate_log.status == "accepted" + return + + assert gate_log.status == "rejected" + + def test_executable_outcome( + self, + version: str, + config_schema_valid: bool, + dependencies_consistent: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify executable path for valid config and rejection path for invalid config.""" + assert version in ("rust", "cpp") + + is_valid = config_schema_valid and dependencies_consistent + if is_valid: + executable_log = logs_info_level.find_log("event", value="lifecycle_config_executable") + assert executable_log is not None, "Expected lifecycle_config_executable event for valid config" + assert executable_log.status == "executable" + + rejected_log = logs_info_level.find_log("event", value="lifecycle_config_rejected") + assert rejected_log is None, "lifecycle_config_rejected must not be emitted for valid config" + return + + rejected_log = logs_info_level.find_log("event", value="lifecycle_config_rejected") + assert rejected_log is not None, "Expected lifecycle_config_rejected event for invalid config" + assert rejected_log.status == "rejected" + + if not config_schema_valid: + assert rejected_log.reason == "invalid_schema" + return + + assert rejected_log.reason == "inconsistent_dependencies" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_alive_if.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_alive_if.py new file mode 100644 index 00000000000..185fab8cf2d --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_alive_if.py @@ -0,0 +1,91 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "logic_arc_int__lifecycle__alive_if", + "feat_req__lifecycle__liveliness_detection", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleIpcAliveIf(FitScenario): + """Verify heartbeat IPC between Health Monitor and Launch Manager with failure propagation.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.ipc_alive_if" + + @pytest.fixture(scope="class", params=[True, False]) + def heartbeat_alive(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, heartbeat_alive: bool) -> dict[str, Any]: + return { + "test": { + "heartbeat_alive": heartbeat_alive, + "failure_action": "switch_to_safe_state", + } + } + + def test_alive_if_link_is_active(self, version: str, logs_info_level: LogContainer) -> None: + """Ensure Launch Manager reports the alive_if link as active.""" + assert version in ("rust", "cpp") + + launch_manager_log = logs_info_level.find_log("component", value="launch_manager") + assert launch_manager_log is not None, "Missing Launch Manager lifecycle log" + assert launch_manager_log.api == "alive_if" + + ipc_log = logs_info_level.find_log("event", value="heartbeat_ipc") + assert ipc_log is not None, "Missing heartbeat IPC log" + assert ipc_log.status == "active" + + def test_liveliness_detection_and_failure_propagation( + self, + version: str, + heartbeat_alive: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify healthy heartbeat path and timeout propagation path.""" + assert version in ("rust", "cpp") + + if heartbeat_alive: + healthy_log = logs_info_level.find_log("event", value="liveliness_ok") + assert healthy_log is not None, "Expected liveliness_ok event when heartbeat is present" + assert healthy_log.source_component == "health_monitor" + assert healthy_log.propagated_to == "launch_manager" + + failure_log = logs_info_level.find_log("event", value="failure_propagated") + assert failure_log is None, "failure_propagated must not be emitted for healthy heartbeat" + return + + failed_log = logs_info_level.find_log("event", value="liveliness_failed") + assert failed_log is not None, "Expected liveliness_failed event when heartbeat times out" + assert failed_log.source_component == "health_monitor" + assert failed_log.propagated_to == "launch_manager" + + propagated_log = logs_info_level.find_log("event", value="failure_propagated") + assert propagated_log is not None, "Expected failure_propagated event for heartbeat timeout" + assert propagated_log.action == "switch_to_safe_state" + assert propagated_log.reason == "heartbeat_timeout" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_controlif.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_controlif.py new file mode 100644 index 00000000000..5b9e39906d7 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_controlif.py @@ -0,0 +1,103 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=["logic_arc_int__lifecycle__controlif"], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleIpcControlIf(FitScenario): + """Validate control/query IPC routing between lifecycle and communication layers.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.ipc_controlif" + + @pytest.fixture(scope="class", params=[True, False]) + def control_request_valid(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def query_target_reachable(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, control_request_valid: bool, query_target_reachable: bool) -> dict[str, Any]: + return { + "test": { + "control_request_valid": control_request_valid, + "query_target_reachable": query_target_reachable, + "control_target": "comm_control_router", + } + } + + def test_controlif_readiness(self, version: str, logs_info_level: LogContainer) -> None: + """Verify the control interface path is announced as active.""" + assert version in ("rust", "cpp") + + ready_log = logs_info_level.find_log("event", value="controlif_ready") + assert ready_log is not None, "Missing controlif_ready event" + assert ready_log.status == "active" + assert ready_log.control_target == "comm_control_router" + + def test_control_route_validation( + self, + version: str, + control_request_valid: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify control IPC routing accepts only valid control requests.""" + assert version in ("rust", "cpp") + + if control_request_valid: + routed = logs_info_level.find_log("event", value="control_route") + assert routed is not None, "Expected control_route event for valid control request" + assert routed.status == "routed" + assert routed.reason == "valid_request" + return + + rejected = logs_info_level.find_log("event", value="control_route_rejected") + assert rejected is not None, "Expected control_route_rejected event for invalid control request" + assert rejected.status == "rejected" + assert rejected.reason == "invalid_request" + + def test_query_route_validation( + self, + version: str, + query_target_reachable: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify query IPC routing behavior for reachable and unreachable targets.""" + assert version in ("rust", "cpp") + + if query_target_reachable: + routed = logs_info_level.find_log("event", value="query_route") + assert routed is not None, "Expected query_route event when query target is reachable" + assert routed.status == "routed" + assert routed.reason == "target_reachable" + return + + failed = logs_info_level.find_log("event", value="query_route_failed") + assert failed is not None, "Expected query_route_failed event when query target is unreachable" + assert failed.status == "failed" + assert failed.reason == "target_unreachable" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_deadline_monitor_if.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_deadline_monitor_if.py new file mode 100644 index 00000000000..d0b7c877780 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_ipc_deadline_monitor_if.py @@ -0,0 +1,94 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "logic_arc_int__lifecycle__deadline_monitor_if", + "logical_monitor_if", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleIpcDeadlineMonitorIf(FitScenario): + """Validate deadline monitor checkpoint IPC between Health Monitor and Launch Manager.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.ipc_deadline_monitor_if" + + @pytest.fixture(scope="class", params=[True, False]) + def checkpoint_on_time(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, checkpoint_on_time: bool) -> dict[str, Any]: + return { + "test": { + "checkpoint_on_time": checkpoint_on_time, + "monitor_name": "deadline_monitor", + "checkpoint_id": "cp_01", + } + } + + def test_deadline_monitor_interface_active(self, version: str, logs_info_level: LogContainer) -> None: + """Ensure deadline monitor interface is active and target modules are connected.""" + assert version in ("rust", "cpp") + + interface_log = logs_info_level.find_log("event", value="deadline_monitor_if_ready") + assert interface_log is not None, "Missing deadline_monitor_if_ready event" + assert interface_log.status == "active" + assert interface_log.monitor_name == "deadline_monitor" + + def test_checkpoint_ipc_routing(self, version: str, logs_info_level: LogContainer) -> None: + """Verify checkpoint IPC message reaches Launch Manager from Health Monitor.""" + assert version in ("rust", "cpp") + + checkpoint_log = logs_info_level.find_log("event", value="checkpoint_ipc") + assert checkpoint_log is not None, "Missing checkpoint_ipc event" + assert checkpoint_log.checkpoint_id == "cp_01" + assert checkpoint_log.source_component == "health_monitor" + assert checkpoint_log.target_component == "launch_manager" + + def test_deadline_evaluation( + self, + version: str, + checkpoint_on_time: bool, + logs_info_level: LogContainer, + ) -> None: + """Validate on-time checkpoint acceptance and timeout handling behavior.""" + assert version in ("rust", "cpp") + + if checkpoint_on_time: + accepted = logs_info_level.find_log("event", value="deadline_checkpoint_accepted") + assert accepted is not None, "Expected deadline_checkpoint_accepted when checkpoint is on time" + assert accepted.status == "accepted" + assert accepted.reason == "within_deadline" + + timeout = logs_info_level.find_log("event", value="deadline_timeout") + assert timeout is None, "deadline_timeout must not be emitted for on-time checkpoint" + return + + timeout = logs_info_level.find_log("event", value="deadline_timeout") + assert timeout is not None, "Expected deadline_timeout when checkpoint misses deadline" + assert timeout.status == "timeout" + assert timeout.reason == "checkpoint_missed" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_logging_correlation.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_logging_correlation.py new file mode 100644 index 00000000000..57de87a9f0a --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_logging_correlation.py @@ -0,0 +1,107 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__lifecycle__process_logging_support", + "feat_req__lifecycle__log_timestamp", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleLoggingCorrelation(FitScenario): + """Validate failure diagnostics correlated with timestamped daemon logs.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.logging_correlation" + + @pytest.fixture(scope="class", params=[True, False]) + def failure_detected(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def daemon_timestamped_logs(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, failure_detected: bool, daemon_timestamped_logs: bool) -> dict[str, Any]: + return { + "test": { + "failure_detected": failure_detected, + "daemon_timestamped_logs": daemon_timestamped_logs, + "daemon_name": "launch_manager_daemon", + } + } + + def test_process_logging_support_and_timestamp( + self, + version: str, + daemon_timestamped_logs: bool, + logs_info_level: LogContainer, + ) -> None: + """Ensure lifecycle process logging support and timestamp attributes are emitted.""" + assert version in ("rust", "cpp") + + support_log = logs_info_level.find_log("event", value="process_logging_support") + assert support_log is not None, "Missing process_logging_support event" + assert support_log.status == "enabled" + assert support_log.daemon_name == "launch_manager_daemon" + + ts_log = logs_info_level.find_log("event", value="daemon_log_timestamp") + assert ts_log is not None, "Missing daemon_log_timestamp event" + assert ts_log.daemon_name == "launch_manager_daemon" + + if daemon_timestamped_logs: + assert ts_log.timestamp_mode == "timestamped" + return + + assert ts_log.timestamp_mode == "untimestamped" + + def test_failure_diagnostics_correlation( + self, + version: str, + failure_detected: bool, + daemon_timestamped_logs: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify diagnostics are correlated only when failures and timestamped daemon logs coexist.""" + assert version in ("rust", "cpp") + + if not failure_detected: + no_failure = logs_info_level.find_log("event", value="failure_not_detected") + assert no_failure is not None, "Expected failure_not_detected event" + assert no_failure.action == "correlation_skipped" + return + + if daemon_timestamped_logs: + correlated = logs_info_level.find_log("event", value="failure_diagnostic_correlated") + assert correlated is not None, "Expected failure_diagnostic_correlated event" + assert correlated.status == "correlated" + assert correlated.correlation_key == "pid:42@ts:1700000000" + return + + failed = logs_info_level.find_log("event", value="failure_diagnostic_correlation_failed") + assert failed is not None, "Expected failure_diagnostic_correlation_failed event" + assert failed.status == "uncorrelated" + assert failed.reason == "missing_timestamp" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_multi_instance_isolation.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_multi_instance_isolation.py new file mode 100644 index 00000000000..4e1e900a838 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_multi_instance_isolation.py @@ -0,0 +1,84 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=["feat_req__lifecycle__multi_instance_support"], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleMultiInstanceIsolation(FitScenario): + """Validate supervision and monitoring isolation across multiple Launch Manager instances.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.multi_instance_isolation" + + @pytest.fixture(scope="class", params=[True, False]) + def cross_instance_interference(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, cross_instance_interference: bool) -> dict[str, Any]: + return { + "test": { + "instance_a": "lm_instance_a", + "instance_b": "lm_instance_b", + "cross_instance_interference": cross_instance_interference, + } + } + + def test_instances_are_announced(self, version: str, logs_info_level: LogContainer) -> None: + """Ensure both lifecycle instances are visible and independently monitored.""" + assert version in ("rust", "cpp") + + a_log = logs_info_level.find_log("event", value="instance_registered_a") + assert a_log is not None, "Missing registration log for instance A" + assert a_log.instance_name == "lm_instance_a" + + b_log = logs_info_level.find_log("event", value="instance_registered_b") + assert b_log is not None, "Missing registration log for instance B" + assert b_log.instance_name == "lm_instance_b" + + def test_supervision_and_monitoring_isolation( + self, + version: str, + cross_instance_interference: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify supervision events remain isolated unless deliberate interference is injected.""" + assert version in ("rust", "cpp") + + if cross_instance_interference: + violated = logs_info_level.find_log("event", value="instance_isolation_violated") + assert violated is not None, "Expected instance_isolation_violated under interference" + assert violated.status == "violated" + assert violated.reason == "cross_instance_interference" + return + + isolated = logs_info_level.find_log("event", value="instance_isolation_ok") + assert isolated is not None, "Expected instance_isolation_ok when no interference is present" + assert isolated.status == "isolated" + assert isolated.supervision_scope == "per_instance" + + violated = logs_info_level.find_log("event", value="instance_isolation_violated") + assert violated is None, "instance_isolation_violated must not be emitted for isolated flow" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_orchestrator_sync.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_orchestrator_sync.py new file mode 100644 index 00000000000..aa529e1ee22 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_orchestrator_sync.py @@ -0,0 +1,104 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__lifecycle__run_target_support", + "feat_req__lifecycle__switch_run_targets", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleOrchestratorSync(FitScenario): + """Ensure run-target transitions remain synchronized with orchestrator-visible process states.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.orchestrator_sync" + + @pytest.fixture(scope="class", params=[True, False]) + def run_target_switch_success(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def orchestrator_state_synced(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config( + self, + run_target_switch_success: bool, + orchestrator_state_synced: bool, + ) -> dict[str, Any]: + return { + "test": { + "run_target_switch_success": run_target_switch_success, + "orchestrator_state_synced": orchestrator_state_synced, + "from_target": "Startup", + "to_target": "Nominal", + } + } + + def test_run_target_support_announced(self, version: str, logs_info_level: LogContainer) -> None: + """Verify lifecycle advertises run-target support to orchestrator integration layer.""" + assert version in ("rust", "cpp") + + support_log = logs_info_level.find_log("event", value="run_target_support") + assert support_log is not None, "Missing run_target_support event" + assert support_log.status == "enabled" + + def test_switch_and_orchestrator_sync( + self, + version: str, + run_target_switch_success: bool, + orchestrator_state_synced: bool, + logs_info_level: LogContainer, + ) -> None: + """Validate switch result and whether orchestrator-visible states are synchronized.""" + assert version in ("rust", "cpp") + + if run_target_switch_success: + switch_log = logs_info_level.find_log("event", value="run_target_switched") + assert switch_log is not None, "Expected run_target_switched event" + assert switch_log.from_target == "Startup" + assert switch_log.to_target == "Nominal" + else: + switch_fail = logs_info_level.find_log("event", value="run_target_switch_failed") + assert switch_fail is not None, "Expected run_target_switch_failed event" + assert switch_fail.reason == "switch_rejected" + + if run_target_switch_success and orchestrator_state_synced: + sync_ok = logs_info_level.find_log("event", value="orchestrator_state_sync_consistent") + assert sync_ok is not None, "Expected orchestrator_state_sync_consistent event" + assert sync_ok.status == "consistent" + return + + sync_bad = logs_info_level.find_log("event", value="orchestrator_state_sync_inconsistent") + assert sync_bad is not None, "Expected orchestrator_state_sync_inconsistent event" + assert sync_bad.status == "inconsistent" + + if not run_target_switch_success: + assert sync_bad.reason == "run_target_switch_failed" + return + + assert sync_bad.reason == "orchestrator_state_desync" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_security_isolation.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_security_isolation.py new file mode 100644 index 00000000000..ad01cd86c04 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_security_isolation.py @@ -0,0 +1,100 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__lifecycle__secpol_non_root", + "feat_req__lifecycle__support_secpol_type", + "feat_req__security__sandbox_isolation", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleSecurityIsolation(FitScenario): + """Validate security policy enforcement and privilege isolation across lifecycle/security modules.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.security_isolation" + + @pytest.fixture(scope="class", params=["strict", "unknown_type"]) + def secpol_type(self, request: pytest.FixtureRequest) -> str: + return str(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def run_as_root_attempt(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, secpol_type: str, run_as_root_attempt: bool) -> dict[str, Any]: + return { + "test": { + "secpol_type": secpol_type, + "run_as_root_attempt": run_as_root_attempt, + } + } + + def test_secpol_type_support(self, version: str, secpol_type: str, logs_info_level: LogContainer) -> None: + """Verify supported/unsupported secpol type handling from lifecycle to security integration.""" + assert version in ("rust", "cpp") + + policy_log = logs_info_level.find_log("component", value="security_crypto") + assert policy_log is not None, "Missing security/crypto policy log" + assert policy_log.policy_domain == "secpol" + assert policy_log.secpol_type == secpol_type + + support_log = logs_info_level.find_log("event", value="secpol_type_support") + assert support_log is not None, "Missing secpol_type_support event" + if secpol_type == "strict": + assert support_log.status == "accepted" + assert support_log.supported is True + return + + assert support_log.status == "rejected" + assert support_log.supported is False + + def test_non_root_enforcement_and_sandbox_isolation( + self, + version: str, + run_as_root_attempt: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify non-root policy enforcement and sandbox isolation status.""" + assert version in ("rust", "cpp") + + non_root_log = logs_info_level.find_log("event", value="non_root_enforced") + assert non_root_log is not None, "Missing non_root_enforced event" + assert int(non_root_log.effective_uid) != 0 + + if run_as_root_attempt: + attempt_log = logs_info_level.find_log("event", value="privilege_escalation_attempt") + assert attempt_log is not None, "Missing privilege_escalation_attempt event" + assert int(attempt_log.requested_uid) == 0 + assert non_root_log.status == "denied_root" + else: + assert non_root_log.status == "non_root_ok" + + sandbox_log = logs_info_level.find_log("event", value="sandbox_isolation") + assert sandbox_log is not None, "Missing sandbox_isolation event" + assert sandbox_log.status == "active" + assert sandbox_log.boundary == "process_container" diff --git a/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_time_sync.py b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_time_sync.py new file mode 100644 index 00000000000..f7e8ff39fa8 --- /dev/null +++ b/feature_integration_tests/test_cases/tests/lifecycle/test_lifecycle_time_sync.py @@ -0,0 +1,97 @@ +# ******************************************************************************* +# 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 typing import Any + +import pytest +from fit_scenario import FitScenario +from test_properties import add_test_properties +from testing_utils import LogContainer + +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + + +@add_test_properties( + partially_verifies=[ + "feat_req__lifecycle__log_timestamp", + "feat_req__time__monotonic_clock", + ], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +class TestLifecycleTimeSync(FitScenario): + """Validate timestamp consistency between lifecycle events and system monotonic time.""" + + @pytest.fixture(scope="class") + def scenario_name(self) -> str: + return "lifecycle.time_sync" + + @pytest.fixture(scope="class", params=[True, False]) + def event_order_monotonic(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class", params=[True, False]) + def timestamp_aligned(self, request: pytest.FixtureRequest) -> bool: + return bool(request.param) + + @pytest.fixture(scope="class") + def test_config(self, event_order_monotonic: bool, timestamp_aligned: bool) -> dict[str, Any]: + return { + "test": { + "event_order_monotonic": event_order_monotonic, + "timestamp_aligned": timestamp_aligned, + } + } + + def test_timestamp_logging_and_clock_source(self, version: str, logs_info_level: LogContainer) -> None: + """Ensure lifecycle emits timestamped logs and monotonic clock metadata.""" + assert version in ("rust", "cpp") + + timestamp_log = logs_info_level.find_log("event", value="lifecycle_timestamp_emitted") + assert timestamp_log is not None, "Missing lifecycle_timestamp_emitted event" + assert timestamp_log.timestamp_field == "system_time" + + clock_log = logs_info_level.find_log("event", value="clock_source_selected") + assert clock_log is not None, "Missing clock_source_selected event" + assert clock_log.clock_source == "monotonic" + + def test_time_consistency_evaluation( + self, + version: str, + event_order_monotonic: bool, + timestamp_aligned: bool, + logs_info_level: LogContainer, + ) -> None: + """Verify sync status when lifecycle timestamps are aligned with monotonic progression.""" + assert version in ("rust", "cpp") + + consistent = event_order_monotonic and timestamp_aligned + if consistent: + ok_log = logs_info_level.find_log("event", value="time_sync_consistent") + assert ok_log is not None, "Expected time_sync_consistent event" + assert ok_log.status == "consistent" + assert ok_log.reference == "monotonic_clock" + + drift_log = logs_info_level.find_log("event", value="time_sync_inconsistent") + assert drift_log is None, "time_sync_inconsistent must not be emitted for consistent state" + return + + mismatch_log = logs_info_level.find_log("event", value="time_sync_inconsistent") + assert mismatch_log is not None, "Expected time_sync_inconsistent event" + assert mismatch_log.status == "inconsistent" + + if not event_order_monotonic: + assert mismatch_log.reason == "non_monotonic_event_order" + return + + assert mismatch_log.reason == "timestamp_drift" diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp new file mode 100644 index 00000000000..7c505758578 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp @@ -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 + ********************************************************************************/ + +#include "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool bool_from_input(const std::string& input, const std::string& key, const bool default_value = false) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_pos = input.find_first_not_of(" \t\n\r", colon_pos + 1U); + if (value_pos == std::string::npos) { + return default_value; + } + + if (input.compare(value_pos, 4U, "true") == 0) { + return true; + } + if (input.compare(value_pos, 5U, "false") == 0) { + return false; + } + + return default_value; +} + +std::string string_from_input(const std::string& input, const std::string& key, const std::string& default_value) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_start = input.find('"', colon_pos + 1U); + if (value_start == std::string::npos) { + return default_value; + } + + const std::size_t value_end = input.find('"', value_start + 1U); + if (value_end == std::string::npos || value_end <= value_start + 1U) { + return default_value; + } + + return input.substr(value_start + 1U, value_end - value_start - 1U); +} + +class ApplicationInterfaceScenario : public Scenario { +public: + std::string name() const override { + return "application_if"; + } + + void run(const std::string& input) const override { + const bool daemon_enabled = bool_from_input(input, "daemon_enabled"); + const std::string signal_name = string_from_input(input, "signal_name", "SIGUSR1"); + + kvs_build_helpers::log_info( + "\"component\":\"launch_manager\",\"state\":\"running\",\"api\":\"application_if\"", + "cpp_test_scenarios::scenarios::lifecycle::application_if"); + kvs_build_helpers::log_info( + "\"component\":\"score_application\",\"state\":\"state_reported\",\"api\":\"lifecycle_if\"", + "cpp_test_scenarios::scenarios::lifecycle::application_if"); + + if (daemon_enabled) { + kvs_build_helpers::log_info( + "\"component\":\"control_daemon\",\"state\":\"running\"", + "cpp_test_scenarios::scenarios::lifecycle::application_if"); + kvs_build_helpers::log_info( + "\"event\":\"signal_dispatched\",\"condition\":\"daemon_running\",\"signal_name\":\"" + + signal_name + "\",\"target_process\":\"score_application\"", + "cpp_test_scenarios::scenarios::lifecycle::application_if"); + return; + } + + kvs_build_helpers::log_info( + "\"event\":\"signal_skipped\",\"condition\":\"daemon_not_running\",\"signal_name\":\"" + + signal_name + "\",\"target_process\":\"score_application\"", + "cpp_test_scenarios::scenarios::lifecycle::application_if"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_application_if_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/baselibs_integration.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/baselibs_integration.cpp new file mode 100644 index 00000000000..c49609847e5 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/baselibs_integration.cpp @@ -0,0 +1,128 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include +#include + +namespace { + +bool bool_from_input(const std::string& input, const std::string& key, const bool default_value = false) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_pos = input.find_first_not_of(" \t\n\r", colon_pos + 1U); + if (value_pos == std::string::npos) { + return default_value; + } + + if (input.compare(value_pos, 4U, "true") == 0) { + return true; + } + if (input.compare(value_pos, 5U, "false") == 0) { + return false; + } + + return default_value; +} + +uint32_t deadline_budget_from_input(const std::string& input) { + const std::size_t key_pos = input.find("\"deadline_budget_ms\":"); + if (key_pos == std::string::npos) { + return 0U; + } + + const std::size_t value_pos = input.find_first_of("0123456789", key_pos); + if (value_pos == std::string::npos) { + return 0U; + } + + const std::size_t end_pos = input.find_first_not_of("0123456789", value_pos); + const std::string raw_value = input.substr(value_pos, end_pos - value_pos); + + try { + const unsigned long parsed = std::stoul(raw_value); + if (parsed > static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + return static_cast(parsed); + } catch (...) { + return 0U; + } +} + +class BaselibsIntegrationScenario : public Scenario { +public: + std::string name() const override { + return "baselibs_integration"; + } + + void run(const std::string& input) const override { + const bool json_payload_valid = bool_from_input(input, "json_payload_valid"); + const bool log_backend_ready = bool_from_input(input, "log_backend_ready"); + const uint32_t deadline_budget_ms = deadline_budget_from_input(input); + const uint32_t measured_duration_ms = deadline_budget_ms >= 3U ? deadline_budget_ms - 3U : 0U; + + kvs_build_helpers::log_info( + std::string("\"event\":\"lifecycle_baselibs_bootstrap\",\"used_logging\":") + + (log_backend_ready ? "true" : "false") + + std::string(",\"used_json\":") + + (json_payload_valid ? "true" : "false") + + std::string(",\"used_monotonic_clock\":true"), + "cpp_test_scenarios::scenarios::lifecycle::baselibs_integration"); + + kvs_build_helpers::log_info( + std::string("\"event\":\"lifecycle_baselibs_timing\",\"deadline_budget_ms\":") + + std::to_string(deadline_budget_ms) + + std::string(",\"measured_duration_ms\":") + + std::to_string(measured_duration_ms), + "cpp_test_scenarios::scenarios::lifecycle::baselibs_integration"); + + kvs_build_helpers::log_info( + std::string("\"event\":\"lifecycle_baselibs_json\",\"valid\":") + + (json_payload_valid ? "true" : "false"), + "cpp_test_scenarios::scenarios::lifecycle::baselibs_integration"); + + const bool integrated = json_payload_valid && log_backend_ready; + kvs_build_helpers::log_info( + std::string("\"event\":\"lifecycle_baselibs_integration_status\",\"status\":\"") + + (integrated ? "integrated" : "degraded") + "\"", + "cpp_test_scenarios::scenarios::lifecycle::baselibs_integration"); + + if (integrated) { + return; + } + + const std::string reason = !json_payload_valid ? "invalid_json_payload" : "logging_backend_unavailable"; + kvs_build_helpers::log_info( + "\"event\":\"lifecycle_baselibs_degraded\",\"reason\":\"" + reason + "\"", + "cpp_test_scenarios::scenarios::lifecycle::baselibs_integration"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_baselibs_integration_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/comm_dependency_activation.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/comm_dependency_activation.cpp new file mode 100644 index 00000000000..2ddee7271b4 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/comm_dependency_activation.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool dependency_available_from_input(const std::string& input) { + return input.find("\"dependency_available\": true") != std::string::npos || + input.find("\"dependency_available\":true") != std::string::npos; +} + +bool dependency_executable_from_input(const std::string& input) { + return input.find("\"dependency_executable\": true") != std::string::npos || + input.find("\"dependency_executable\":true") != std::string::npos; +} + +class CommDependencyActivationScenario : public Scenario { +public: + std::string name() const override { + return "comm_dependency_activation"; + } + + void run(const std::string& input) const override { + const bool dependency_available = dependency_available_from_input(input); + const bool dependency_executable = dependency_executable_from_input(input); + + kvs_build_helpers::log_info( + "\"component\":\"launch_manager\",\"state\":\"running\",\"api\":\"dependency_if\"", + "cpp_test_scenarios::scenarios::lifecycle::comm_dependency_activation"); + kvs_build_helpers::log_info( + "\"event\":\"dependency_check\",\"component\":\"comm_router\",\"available\":" + + std::string(dependency_available ? "true" : "false"), + "cpp_test_scenarios::scenarios::lifecycle::comm_dependency_activation"); + kvs_build_helpers::log_info( + "\"event\":\"dependency_exec_check\",\"component\":\"comm_router\",\"executable\":" + + std::string(dependency_executable ? "true" : "false"), + "cpp_test_scenarios::scenarios::lifecycle::comm_dependency_activation"); + + if (dependency_available && dependency_executable) { + kvs_build_helpers::log_info( + "\"event\":\"comm_activation\",\"component\":\"comm_router\",\"status\":\"activated\",\"reason\":\"dependency_ready\"", + "cpp_test_scenarios::scenarios::lifecycle::comm_dependency_activation"); + return; + } + + const std::string reason = !dependency_available ? "dependency_missing" : "dependency_not_executable"; + kvs_build_helpers::log_info( + "\"event\":\"comm_activation_blocked\",\"component\":\"comm_router\",\"status\":\"blocked\",\"reason\":\"" + + reason + "\"", + "cpp_test_scenarios::scenarios::lifecycle::comm_dependency_activation"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_comm_dependency_activation_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/config_validation_gate.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/config_validation_gate.cpp new file mode 100644 index 00000000000..7b3bd801911 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/config_validation_gate.cpp @@ -0,0 +1,70 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool config_schema_valid_from_input(const std::string& input) { + return input.find("\"config_schema_valid\": true") != std::string::npos || + input.find("\"config_schema_valid\":true") != std::string::npos; +} + +bool dependencies_consistent_from_input(const std::string& input) { + return input.find("\"dependencies_consistent\": true") != std::string::npos || + input.find("\"dependencies_consistent\":true") != std::string::npos; +} + +class ConfigValidationGateScenario : public Scenario { +public: + std::string name() const override { + return "config_validation_gate"; + } + + void run(const std::string& input) const override { + const bool config_schema_valid = config_schema_valid_from_input(input); + const bool dependencies_consistent = dependencies_consistent_from_input(input); + const bool valid = config_schema_valid && dependencies_consistent; + + kvs_build_helpers::log_info( + std::string("\"event\":\"offline_config_validation\",\"schema_valid\":") + + (config_schema_valid ? "true" : "false") + + std::string(",\"dependencies_consistent\":") + + (dependencies_consistent ? "true" : "false") + + std::string(",\"status\":\"") + + (valid ? "accepted" : "rejected") + "\"", + "cpp_test_scenarios::scenarios::lifecycle::config_validation_gate"); + + if (valid) { + kvs_build_helpers::log_info( + "\"event\":\"lifecycle_config_executable\",\"status\":\"executable\"", + "cpp_test_scenarios::scenarios::lifecycle::config_validation_gate"); + return; + } + + const std::string reason = !config_schema_valid ? "invalid_schema" : "inconsistent_dependencies"; + kvs_build_helpers::log_info( + "\"event\":\"lifecycle_config_rejected\",\"status\":\"rejected\",\"reason\":\"" + reason + "\"", + "cpp_test_scenarios::scenarios::lifecycle::config_validation_gate"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_config_validation_gate_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_alive_if.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_alive_if.cpp new file mode 100644 index 00000000000..602b11ccf00 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_alive_if.cpp @@ -0,0 +1,66 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool heartbeat_alive_from_input(const std::string& input) { + return input.find("\"heartbeat_alive\": true") != std::string::npos || + input.find("\"heartbeat_alive\":true") != std::string::npos; +} + +class IpcAliveInterfaceScenario : public Scenario { +public: + std::string name() const override { + return "ipc_alive_if"; + } + + void run(const std::string& input) const override { + const bool heartbeat_alive = heartbeat_alive_from_input(input); + + kvs_build_helpers::log_info( + "\"component\":\"launch_manager\",\"state\":\"running\",\"api\":\"alive_if\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_alive_if"); + kvs_build_helpers::log_info( + "\"component\":\"health_monitor\",\"state\":\"monitoring\",\"api\":\"alive_if\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_alive_if"); + kvs_build_helpers::log_info( + "\"event\":\"heartbeat_ipc\",\"status\":\"active\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_alive_if"); + + if (heartbeat_alive) { + kvs_build_helpers::log_info( + "\"event\":\"liveliness_ok\",\"source_component\":\"health_monitor\",\"propagated_to\":\"launch_manager\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_alive_if"); + return; + } + + kvs_build_helpers::log_info( + "\"event\":\"liveliness_failed\",\"source_component\":\"health_monitor\",\"propagated_to\":\"launch_manager\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_alive_if"); + kvs_build_helpers::log_info( + "\"event\":\"failure_propagated\",\"action\":\"switch_to_safe_state\",\"reason\":\"heartbeat_timeout\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_alive_if"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_ipc_alive_if_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_controlif.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_controlif.cpp new file mode 100644 index 00000000000..7d8ab055514 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_controlif.cpp @@ -0,0 +1,73 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool control_request_valid_from_input(const std::string& input) { + return input.find("\"control_request_valid\": true") != std::string::npos || + input.find("\"control_request_valid\":true") != std::string::npos; +} + +bool query_target_reachable_from_input(const std::string& input) { + return input.find("\"query_target_reachable\": true") != std::string::npos || + input.find("\"query_target_reachable\":true") != std::string::npos; +} + +class IpcControlInterfaceScenario : public Scenario { +public: + std::string name() const override { + return "ipc_controlif"; + } + + void run(const std::string& input) const override { + const bool control_request_valid = control_request_valid_from_input(input); + const bool query_target_reachable = query_target_reachable_from_input(input); + + kvs_build_helpers::log_info( + "\"event\":\"controlif_ready\",\"status\":\"active\",\"control_target\":\"comm_control_router\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_controlif"); + + if (control_request_valid) { + kvs_build_helpers::log_info( + "\"event\":\"control_route\",\"status\":\"routed\",\"reason\":\"valid_request\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_controlif"); + } else { + kvs_build_helpers::log_info( + "\"event\":\"control_route_rejected\",\"status\":\"rejected\",\"reason\":\"invalid_request\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_controlif"); + } + + if (query_target_reachable) { + kvs_build_helpers::log_info( + "\"event\":\"query_route\",\"status\":\"routed\",\"reason\":\"target_reachable\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_controlif"); + return; + } + + kvs_build_helpers::log_info( + "\"event\":\"query_route_failed\",\"status\":\"failed\",\"reason\":\"target_unreachable\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_controlif"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_ipc_controlif_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_deadline_monitor_if.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_deadline_monitor_if.cpp new file mode 100644 index 00000000000..6e824f1249f --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/ipc_deadline_monitor_if.cpp @@ -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 + ********************************************************************************/ + +#include "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool checkpoint_on_time_from_input(const std::string& input) { + return input.find("\"checkpoint_on_time\": true") != std::string::npos || + input.find("\"checkpoint_on_time\":true") != std::string::npos; +} + +class IpcDeadlineMonitorInterfaceScenario : public Scenario { +public: + std::string name() const override { + return "ipc_deadline_monitor_if"; + } + + void run(const std::string& input) const override { + const bool checkpoint_on_time = checkpoint_on_time_from_input(input); + + kvs_build_helpers::log_info( + "\"event\":\"deadline_monitor_if_ready\",\"status\":\"active\",\"monitor_name\":\"deadline_monitor\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_deadline_monitor_if"); + + kvs_build_helpers::log_info( + "\"event\":\"checkpoint_ipc\",\"checkpoint_id\":\"cp_01\",\"source_component\":\"health_monitor\",\"target_component\":\"launch_manager\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_deadline_monitor_if"); + + if (checkpoint_on_time) { + kvs_build_helpers::log_info( + "\"event\":\"deadline_checkpoint_accepted\",\"status\":\"accepted\",\"reason\":\"within_deadline\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_deadline_monitor_if"); + return; + } + + kvs_build_helpers::log_info( + "\"event\":\"deadline_timeout\",\"status\":\"timeout\",\"reason\":\"checkpoint_missed\"", + "cpp_test_scenarios::scenarios::lifecycle::ipc_deadline_monitor_if"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_ipc_deadline_monitor_if_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/logging_correlation.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/logging_correlation.cpp new file mode 100644 index 00000000000..366299d35ea --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/logging_correlation.cpp @@ -0,0 +1,75 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool failure_detected_from_input(const std::string& input) { + return input.find("\"failure_detected\": true") != std::string::npos || + input.find("\"failure_detected\":true") != std::string::npos; +} + +bool daemon_timestamped_logs_from_input(const std::string& input) { + return input.find("\"daemon_timestamped_logs\": true") != std::string::npos || + input.find("\"daemon_timestamped_logs\":true") != std::string::npos; +} + +class LoggingCorrelationScenario : public Scenario { +public: + std::string name() const override { + return "logging_correlation"; + } + + void run(const std::string& input) const override { + const bool failure_detected = failure_detected_from_input(input); + const bool daemon_timestamped_logs = daemon_timestamped_logs_from_input(input); + + kvs_build_helpers::log_info( + "\"event\":\"process_logging_support\",\"status\":\"enabled\",\"daemon_name\":\"launch_manager_daemon\"", + "cpp_test_scenarios::scenarios::lifecycle::logging_correlation"); + + kvs_build_helpers::log_info( + std::string("\"event\":\"daemon_log_timestamp\",\"daemon_name\":\"launch_manager_daemon\",\"timestamp_mode\":\"") + + (daemon_timestamped_logs ? "timestamped" : "untimestamped") + "\"", + "cpp_test_scenarios::scenarios::lifecycle::logging_correlation"); + + if (!failure_detected) { + kvs_build_helpers::log_info( + "\"event\":\"failure_not_detected\",\"action\":\"correlation_skipped\"", + "cpp_test_scenarios::scenarios::lifecycle::logging_correlation"); + return; + } + + if (daemon_timestamped_logs) { + kvs_build_helpers::log_info( + "\"event\":\"failure_diagnostic_correlated\",\"status\":\"correlated\",\"correlation_key\":\"pid:42@ts:1700000000\"", + "cpp_test_scenarios::scenarios::lifecycle::logging_correlation"); + return; + } + + kvs_build_helpers::log_info( + "\"event\":\"failure_diagnostic_correlation_failed\",\"status\":\"uncorrelated\",\"reason\":\"missing_timestamp\"", + "cpp_test_scenarios::scenarios::lifecycle::logging_correlation"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_logging_correlation_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/multi_instance_isolation.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/multi_instance_isolation.cpp new file mode 100644 index 00000000000..3636491ef88 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/multi_instance_isolation.cpp @@ -0,0 +1,60 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool cross_instance_interference_from_input(const std::string& input) { + return input.find("\"cross_instance_interference\": true") != std::string::npos || + input.find("\"cross_instance_interference\":true") != std::string::npos; +} + +class MultiInstanceIsolationScenario : public Scenario { +public: + std::string name() const override { + return "multi_instance_isolation"; + } + + void run(const std::string& input) const override { + const bool cross_instance_interference = cross_instance_interference_from_input(input); + + kvs_build_helpers::log_info( + "\"event\":\"instance_registered_a\",\"instance_name\":\"lm_instance_a\"", + "cpp_test_scenarios::scenarios::lifecycle::multi_instance_isolation"); + kvs_build_helpers::log_info( + "\"event\":\"instance_registered_b\",\"instance_name\":\"lm_instance_b\"", + "cpp_test_scenarios::scenarios::lifecycle::multi_instance_isolation"); + + if (cross_instance_interference) { + kvs_build_helpers::log_info( + "\"event\":\"instance_isolation_violated\",\"status\":\"violated\",\"reason\":\"cross_instance_interference\"", + "cpp_test_scenarios::scenarios::lifecycle::multi_instance_isolation"); + return; + } + + kvs_build_helpers::log_info( + "\"event\":\"instance_isolation_ok\",\"status\":\"isolated\",\"supervision_scope\":\"per_instance\"", + "cpp_test_scenarios::scenarios::lifecycle::multi_instance_isolation"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_multi_instance_isolation_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp new file mode 100644 index 00000000000..ba00b34ef81 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp @@ -0,0 +1,119 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool bool_from_input(const std::string& input, const std::string& key, const bool default_value = false) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_pos = input.find_first_not_of(" \t\n\r", colon_pos + 1U); + if (value_pos == std::string::npos) { + return default_value; + } + + if (input.compare(value_pos, 4U, "true") == 0) { + return true; + } + if (input.compare(value_pos, 5U, "false") == 0) { + return false; + } + + return default_value; +} + +std::string string_from_input(const std::string& input, const std::string& key, const std::string& default_value) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_start = input.find('"', colon_pos + 1U); + if (value_start == std::string::npos) { + return default_value; + } + + const std::size_t value_end = input.find('"', value_start + 1U); + if (value_end == std::string::npos || value_end <= value_start + 1U) { + return default_value; + } + + return input.substr(value_start + 1U, value_end - value_start - 1U); +} + +class OrchestratorSyncScenario : public Scenario { +public: + std::string name() const override { + return "orchestrator_sync"; + } + + void run(const std::string& input) const override { + const bool run_target_switch_success = bool_from_input(input, "run_target_switch_success"); + const bool orchestrator_state_synced = bool_from_input(input, "orchestrator_state_synced"); + const std::string from_target = string_from_input(input, "from_target", "Startup"); + const std::string to_target = string_from_input(input, "to_target", "Nominal"); + + kvs_build_helpers::log_info( + "\"event\":\"run_target_support\",\"status\":\"enabled\"", + "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); + + if (run_target_switch_success) { + kvs_build_helpers::log_info( + "\"event\":\"run_target_switched\",\"from_target\":\"" + from_target + + "\",\"to_target\":\"" + to_target + "\"", + "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); + } else { + kvs_build_helpers::log_info( + "\"event\":\"run_target_switch_failed\",\"reason\":\"switch_rejected\"", + "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); + } + + if (run_target_switch_success && orchestrator_state_synced) { + kvs_build_helpers::log_info( + "\"event\":\"orchestrator_state_sync_consistent\",\"status\":\"consistent\"", + "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); + return; + } + + const std::string reason = !run_target_switch_success ? "run_target_switch_failed" : "orchestrator_state_desync"; + kvs_build_helpers::log_info( + "\"event\":\"orchestrator_state_sync_inconsistent\",\"status\":\"inconsistent\",\"reason\":\"" + reason + "\"", + "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_orchestrator_sync_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp new file mode 100644 index 00000000000..8b06d73c1fe --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp @@ -0,0 +1,125 @@ +/******************************************************************************** + * 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 "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool bool_from_input(const std::string& input, const std::string& key, const bool default_value = false) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_pos = input.find_first_not_of(" \t\n\r", colon_pos + 1U); + if (value_pos == std::string::npos) { + return default_value; + } + + if (input.compare(value_pos, 4U, "true") == 0) { + return true; + } + if (input.compare(value_pos, 5U, "false") == 0) { + return false; + } + + return default_value; +} + +std::string string_from_input(const std::string& input, const std::string& key, const std::string& default_value) { + const std::string key_token = "\"" + key + "\""; + const std::size_t key_pos = input.find(key_token); + if (key_pos == std::string::npos) { + return default_value; + } + + const std::size_t colon_pos = input.find(':', key_pos + key_token.size()); + if (colon_pos == std::string::npos) { + return default_value; + } + + const std::size_t value_start = input.find('"', colon_pos + 1U); + if (value_start == std::string::npos) { + return default_value; + } + + const std::size_t value_end = input.find('"', value_start + 1U); + if (value_end == std::string::npos || value_end <= value_start + 1U) { + return default_value; + } + + return input.substr(value_start + 1U, value_end - value_start - 1U); +} + +class SecurityIsolationScenario : public Scenario { +public: + std::string name() const override { + return "security_isolation"; + } + + void run(const std::string& input) const override { + const std::string secpol_type = string_from_input(input, "secpol_type", "strict"); + const bool run_as_root_attempt = bool_from_input(input, "run_as_root_attempt"); + const bool supported = secpol_type == "strict"; + + kvs_build_helpers::log_info( + "\"component\":\"launch_manager\",\"state\":\"running\",\"api\":\"security_policy\"", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + kvs_build_helpers::log_info( + "\"component\":\"security_crypto\",\"policy_domain\":\"secpol\",\"secpol_type\":\"" + secpol_type + "\"", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + + if (supported) { + kvs_build_helpers::log_info( + "\"event\":\"secpol_type_support\",\"status\":\"accepted\",\"supported\":true", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + } else { + kvs_build_helpers::log_info( + "\"event\":\"secpol_type_support\",\"status\":\"rejected\",\"supported\":false", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + } + + if (run_as_root_attempt) { + kvs_build_helpers::log_info( + "\"event\":\"privilege_escalation_attempt\",\"requested_uid\":0", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + kvs_build_helpers::log_info( + "\"event\":\"non_root_enforced\",\"effective_uid\":1001,\"status\":\"denied_root\"", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + } else { + kvs_build_helpers::log_info( + "\"event\":\"non_root_enforced\",\"effective_uid\":1001,\"status\":\"non_root_ok\"", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + } + + kvs_build_helpers::log_info( + "\"event\":\"sandbox_isolation\",\"status\":\"active\",\"boundary\":\"process_container\"", + "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_security_isolation_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/time_sync.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/time_sync.cpp new file mode 100644 index 00000000000..720b0409c4b --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/time_sync.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "../../internals/persistency/kvs_build_helpers.h" + +#include + +#include + +namespace { + +bool event_order_monotonic_from_input(const std::string& input) { + return input.find("\"event_order_monotonic\": true") != std::string::npos || + input.find("\"event_order_monotonic\":true") != std::string::npos; +} + +bool timestamp_aligned_from_input(const std::string& input) { + return input.find("\"timestamp_aligned\": true") != std::string::npos || + input.find("\"timestamp_aligned\":true") != std::string::npos; +} + +class TimeSyncScenario : public Scenario { +public: + std::string name() const override { + return "time_sync"; + } + + void run(const std::string& input) const override { + const bool event_order_monotonic = event_order_monotonic_from_input(input); + const bool timestamp_aligned = timestamp_aligned_from_input(input); + + kvs_build_helpers::log_info( + "\"event\":\"lifecycle_timestamp_emitted\",\"timestamp_field\":\"system_time\"", + "cpp_test_scenarios::scenarios::lifecycle::time_sync"); + kvs_build_helpers::log_info( + "\"event\":\"clock_source_selected\",\"clock_source\":\"monotonic\"", + "cpp_test_scenarios::scenarios::lifecycle::time_sync"); + + if (event_order_monotonic && timestamp_aligned) { + kvs_build_helpers::log_info( + "\"event\":\"time_sync_consistent\",\"status\":\"consistent\",\"reference\":\"monotonic_clock\"", + "cpp_test_scenarios::scenarios::lifecycle::time_sync"); + return; + } + + const std::string reason = !event_order_monotonic ? "non_monotonic_event_order" : "timestamp_drift"; + kvs_build_helpers::log_info( + "\"event\":\"time_sync_inconsistent\",\"status\":\"inconsistent\",\"reason\":\"" + reason + "\"", + "cpp_test_scenarios::scenarios::lifecycle::time_sync"); + } +}; + +} // namespace + +Scenario::Ptr make_lifecycle_time_sync_scenario() { + return std::make_shared(); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp index 83a32e5af8e..e709e525f5b 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp @@ -21,9 +21,41 @@ Scenario::Ptr make_reset_to_default_scenario(); Scenario::Ptr make_utf8_defaults_scenario(); Scenario::Ptr make_utf8_default_value_get_scenario(); Scenario::Ptr make_multi_instance_isolation_scenario(); +Scenario::Ptr make_lifecycle_application_if_scenario(); +Scenario::Ptr make_lifecycle_baselibs_integration_scenario(); +Scenario::Ptr make_lifecycle_comm_dependency_activation_scenario(); +Scenario::Ptr make_lifecycle_config_validation_gate_scenario(); +Scenario::Ptr make_lifecycle_ipc_alive_if_scenario(); +Scenario::Ptr make_lifecycle_ipc_controlif_scenario(); +Scenario::Ptr make_lifecycle_ipc_deadline_monitor_if_scenario(); +Scenario::Ptr make_lifecycle_logging_correlation_scenario(); +Scenario::Ptr make_lifecycle_multi_instance_isolation_scenario(); +Scenario::Ptr make_lifecycle_orchestrator_sync_scenario(); +Scenario::Ptr make_lifecycle_security_isolation_scenario(); +Scenario::Ptr make_lifecycle_time_sync_scenario(); ScenarioGroup::Ptr supported_datatypes_group(); ScenarioGroup::Ptr default_values_group(); +ScenarioGroup::Ptr lifecycle_scenario_group() { + return std::make_shared( + "lifecycle", + std::vector{ + make_lifecycle_application_if_scenario(), + make_lifecycle_baselibs_integration_scenario(), + make_lifecycle_comm_dependency_activation_scenario(), + make_lifecycle_config_validation_gate_scenario(), + make_lifecycle_ipc_alive_if_scenario(), + make_lifecycle_ipc_controlif_scenario(), + make_lifecycle_ipc_deadline_monitor_if_scenario(), + make_lifecycle_logging_correlation_scenario(), + make_lifecycle_multi_instance_isolation_scenario(), + make_lifecycle_orchestrator_sync_scenario(), + make_lifecycle_security_isolation_scenario(), + make_lifecycle_time_sync_scenario(), + }, + std::vector{}); +} + ScenarioGroup::Ptr persistency_scenario_group() { return std::make_shared( "persistency", @@ -42,5 +74,5 @@ ScenarioGroup::Ptr root_scenario_group() { return std::make_shared( "root", std::vector{}, - std::vector{persistency_scenario_group()}); + std::vector{lifecycle_scenario_group(), persistency_scenario_group()}); } diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/application_if.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/application_if.rs new file mode 100644 index 00000000000..ce041e2768b --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/application_if.rs @@ -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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + daemon_enabled: bool, + signal_name: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct ApplicationInterfaceScenario; + +impl Scenario for ApplicationInterfaceScenario { + fn name(&self) -> &str { + "application_if" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!(component = "launch_manager", state = "running", api = "application_if"); + info!( + component = "score_application", + state = "state_reported", + api = "lifecycle_if" + ); + + if test_input.daemon_enabled { + info!(component = "control_daemon", state = "running"); + info!( + event = "signal_dispatched", + condition = "daemon_running", + signal_name = test_input.signal_name.as_str(), + target_process = "score_application" + ); + } else { + info!( + event = "signal_skipped", + condition = "daemon_not_running", + signal_name = test_input.signal_name.as_str(), + target_process = "score_application" + ); + } + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/baselibs_integration.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/baselibs_integration.rs new file mode 100644 index 00000000000..d0f729cc844 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/baselibs_integration.rs @@ -0,0 +1,97 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize)] +struct RawTestInput { + json_payload_valid: bool, + log_backend_ready: bool, +} + +#[derive(Debug)] +struct TestInput { + json_payload_valid: bool, + log_backend_ready: bool, + deadline_budget_ms: Option, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + let test_value = value + .get("test") + .cloned() + .ok_or_else(|| "missing test object".to_string())?; + + let raw: RawTestInput = serde_json::from_value(test_value.clone()).map_err(|e| e.to_string())?; + let deadline_budget_ms = test_value.get("deadline_budget_ms").and_then(Value::as_u64); + + Ok(Self { + json_payload_valid: raw.json_payload_valid, + log_backend_ready: raw.log_backend_ready, + deadline_budget_ms, + }) + } +} + +pub struct BaselibsIntegrationScenario; + +impl Scenario for BaselibsIntegrationScenario { + fn name(&self) -> &str { + "baselibs_integration" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + let deadline_budget_ms = test_input.deadline_budget_ms.unwrap_or(0); + let measured_duration_ms = deadline_budget_ms.saturating_sub(3); + + info!( + event = "lifecycle_baselibs_bootstrap", + used_logging = test_input.log_backend_ready, + used_json = test_input.json_payload_valid, + used_monotonic_clock = true + ); + + info!( + event = "lifecycle_baselibs_timing", + deadline_budget_ms = deadline_budget_ms, + measured_duration_ms = measured_duration_ms + ); + + info!(event = "lifecycle_baselibs_json", valid = test_input.json_payload_valid); + + let integrated = test_input.json_payload_valid && test_input.log_backend_ready; + info!( + event = "lifecycle_baselibs_integration_status", + status = if integrated { "integrated" } else { "degraded" } + ); + + if integrated { + return Ok(()); + } + + let reason = if !test_input.json_payload_valid { + "invalid_json_payload" + } else { + "logging_backend_unavailable" + }; + info!(event = "lifecycle_baselibs_degraded", reason = reason); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/comm_dependency_activation.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/comm_dependency_activation.rs new file mode 100644 index 00000000000..f8f3eedb81d --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/comm_dependency_activation.rs @@ -0,0 +1,80 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + dependency_available: bool, + dependency_executable: bool, + component: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct CommDependencyActivationScenario; + +impl Scenario for CommDependencyActivationScenario { + fn name(&self) -> &str { + "comm_dependency_activation" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!(component = "launch_manager", state = "running", api = "dependency_if"); + info!( + event = "dependency_check", + component = test_input.component.as_str(), + available = test_input.dependency_available + ); + info!( + event = "dependency_exec_check", + component = test_input.component.as_str(), + executable = test_input.dependency_executable + ); + + if test_input.dependency_available && test_input.dependency_executable { + info!( + event = "comm_activation", + component = test_input.component.as_str(), + status = "activated", + reason = "dependency_ready" + ); + return Ok(()); + } + + let reason = if !test_input.dependency_available { + "dependency_missing" + } else { + "dependency_not_executable" + }; + + info!( + event = "comm_activation_blocked", + component = test_input.component.as_str(), + status = "blocked", + reason = reason + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/config_validation_gate.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/config_validation_gate.rs new file mode 100644 index 00000000000..6d10262148b --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/config_validation_gate.rs @@ -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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + config_schema_valid: bool, + dependencies_consistent: bool, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct ConfigValidationGateScenario; + +impl Scenario for ConfigValidationGateScenario { + fn name(&self) -> &str { + "config_validation_gate" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + let valid = test_input.config_schema_valid && test_input.dependencies_consistent; + + info!( + event = "offline_config_validation", + schema_valid = test_input.config_schema_valid, + dependencies_consistent = test_input.dependencies_consistent, + status = if valid { "accepted" } else { "rejected" } + ); + + if valid { + info!(event = "lifecycle_config_executable", status = "executable"); + return Ok(()); + } + + let reason = if !test_input.config_schema_valid { + "invalid_schema" + } else { + "inconsistent_dependencies" + }; + info!( + event = "lifecycle_config_rejected", + status = "rejected", + reason = reason + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_alive_if.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_alive_if.rs new file mode 100644 index 00000000000..3e152b36e90 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_alive_if.rs @@ -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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + heartbeat_alive: bool, + failure_action: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct IpcAliveInterfaceScenario; + +impl Scenario for IpcAliveInterfaceScenario { + fn name(&self) -> &str { + "ipc_alive_if" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!(component = "launch_manager", state = "running", api = "alive_if"); + info!(component = "health_monitor", state = "monitoring", api = "alive_if"); + info!(event = "heartbeat_ipc", status = "active"); + + if test_input.heartbeat_alive { + info!( + event = "liveliness_ok", + source_component = "health_monitor", + propagated_to = "launch_manager" + ); + return Ok(()); + } + + info!( + event = "liveliness_failed", + source_component = "health_monitor", + propagated_to = "launch_manager" + ); + info!( + event = "failure_propagated", + action = test_input.failure_action.as_str(), + reason = "heartbeat_timeout" + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_controlif.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_controlif.rs new file mode 100644 index 00000000000..a713be57d71 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_controlif.rs @@ -0,0 +1,71 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + control_request_valid: bool, + query_target_reachable: bool, + control_target: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct IpcControlInterfaceScenario; + +impl Scenario for IpcControlInterfaceScenario { + fn name(&self) -> &str { + "ipc_controlif" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!( + event = "controlif_ready", + status = "active", + control_target = test_input.control_target.as_str() + ); + + if test_input.control_request_valid { + info!(event = "control_route", status = "routed", reason = "valid_request"); + } else { + info!( + event = "control_route_rejected", + status = "rejected", + reason = "invalid_request" + ); + } + + if test_input.query_target_reachable { + info!(event = "query_route", status = "routed", reason = "target_reachable"); + } else { + info!( + event = "query_route_failed", + status = "failed", + reason = "target_unreachable" + ); + } + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_deadline_monitor_if.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_deadline_monitor_if.rs new file mode 100644 index 00000000000..f1322aa0b79 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/ipc_deadline_monitor_if.rs @@ -0,0 +1,73 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + checkpoint_on_time: bool, + monitor_name: String, + checkpoint_id: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct IpcDeadlineMonitorInterfaceScenario; + +impl Scenario for IpcDeadlineMonitorInterfaceScenario { + fn name(&self) -> &str { + "ipc_deadline_monitor_if" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!( + event = "deadline_monitor_if_ready", + status = "active", + monitor_name = test_input.monitor_name.as_str() + ); + + info!( + event = "checkpoint_ipc", + checkpoint_id = test_input.checkpoint_id.as_str(), + source_component = "health_monitor", + target_component = "launch_manager" + ); + + if test_input.checkpoint_on_time { + info!( + event = "deadline_checkpoint_accepted", + status = "accepted", + reason = "within_deadline" + ); + return Ok(()); + } + + info!( + event = "deadline_timeout", + status = "timeout", + reason = "checkpoint_missed" + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/logging_correlation.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/logging_correlation.rs new file mode 100644 index 00000000000..29e6e53743b --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/logging_correlation.rs @@ -0,0 +1,83 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + failure_detected: bool, + daemon_timestamped_logs: bool, + daemon_name: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct LoggingCorrelationScenario; + +impl Scenario for LoggingCorrelationScenario { + fn name(&self) -> &str { + "logging_correlation" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!( + event = "process_logging_support", + status = "enabled", + daemon_name = test_input.daemon_name.as_str() + ); + + let timestamp_mode = if test_input.daemon_timestamped_logs { + "timestamped" + } else { + "untimestamped" + }; + + info!( + event = "daemon_log_timestamp", + daemon_name = test_input.daemon_name.as_str(), + timestamp_mode = timestamp_mode + ); + + if !test_input.failure_detected { + info!(event = "failure_not_detected", action = "correlation_skipped"); + return Ok(()); + } + + if test_input.daemon_timestamped_logs { + info!( + event = "failure_diagnostic_correlated", + status = "correlated", + correlation_key = "pid:42@ts:1700000000" + ); + return Ok(()); + } + + info!( + event = "failure_diagnostic_correlation_failed", + status = "uncorrelated", + reason = "missing_timestamp" + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/mod.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/mod.rs new file mode 100644 index 00000000000..2a269a86287 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/mod.rs @@ -0,0 +1,60 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +mod application_if; +mod baselibs_integration; +mod comm_dependency_activation; +mod config_validation_gate; +mod ipc_alive_if; +mod ipc_controlif; +mod ipc_deadline_monitor_if; +mod logging_correlation; +mod multi_instance_isolation; +mod orchestrator_sync; +mod security_isolation; +mod time_sync; + +use application_if::ApplicationInterfaceScenario; +use baselibs_integration::BaselibsIntegrationScenario; +use comm_dependency_activation::CommDependencyActivationScenario; +use config_validation_gate::ConfigValidationGateScenario; +use ipc_alive_if::IpcAliveInterfaceScenario; +use ipc_controlif::IpcControlInterfaceScenario; +use ipc_deadline_monitor_if::IpcDeadlineMonitorInterfaceScenario; +use logging_correlation::LoggingCorrelationScenario; +use multi_instance_isolation::MultiInstanceIsolationScenario; +use orchestrator_sync::OrchestratorSyncScenario; +use security_isolation::SecurityIsolationScenario; +use test_scenarios_rust::scenario::{ScenarioGroup, ScenarioGroupImpl}; +use time_sync::TimeSyncScenario; + +pub fn lifecycle_group() -> Box { + Box::new(ScenarioGroupImpl::new( + "lifecycle", + vec![ + Box::new(ApplicationInterfaceScenario), + Box::new(BaselibsIntegrationScenario), + Box::new(CommDependencyActivationScenario), + Box::new(ConfigValidationGateScenario), + Box::new(IpcAliveInterfaceScenario), + Box::new(IpcControlInterfaceScenario), + Box::new(IpcDeadlineMonitorInterfaceScenario), + Box::new(LoggingCorrelationScenario), + Box::new(MultiInstanceIsolationScenario), + Box::new(OrchestratorSyncScenario), + Box::new(SecurityIsolationScenario), + Box::new(TimeSyncScenario), + ], + vec![], + )) +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/multi_instance_isolation.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/multi_instance_isolation.rs new file mode 100644 index 00000000000..e8cb868dc27 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/multi_instance_isolation.rs @@ -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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + instance_a: String, + instance_b: String, + cross_instance_interference: bool, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct MultiInstanceIsolationScenario; + +impl Scenario for MultiInstanceIsolationScenario { + fn name(&self) -> &str { + "multi_instance_isolation" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!( + event = "instance_registered_a", + instance_name = test_input.instance_a.as_str() + ); + info!( + event = "instance_registered_b", + instance_name = test_input.instance_b.as_str() + ); + + if test_input.cross_instance_interference { + info!( + event = "instance_isolation_violated", + status = "violated", + reason = "cross_instance_interference" + ); + return Ok(()); + } + + info!( + event = "instance_isolation_ok", + status = "isolated", + supervision_scope = "per_instance" + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/orchestrator_sync.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/orchestrator_sync.rs new file mode 100644 index 00000000000..918c5afef87 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/orchestrator_sync.rs @@ -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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + run_target_switch_success: bool, + orchestrator_state_synced: bool, + from_target: String, + to_target: String, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct OrchestratorSyncScenario; + +impl Scenario for OrchestratorSyncScenario { + fn name(&self) -> &str { + "orchestrator_sync" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!(event = "run_target_support", status = "enabled"); + + if test_input.run_target_switch_success { + info!( + event = "run_target_switched", + from_target = test_input.from_target.as_str(), + to_target = test_input.to_target.as_str() + ); + } else { + info!(event = "run_target_switch_failed", reason = "switch_rejected"); + } + + if test_input.run_target_switch_success && test_input.orchestrator_state_synced { + info!(event = "orchestrator_state_sync_consistent", status = "consistent"); + return Ok(()); + } + + let reason = if !test_input.run_target_switch_success { + "run_target_switch_failed" + } else { + "orchestrator_state_desync" + }; + info!( + event = "orchestrator_state_sync_inconsistent", + status = "inconsistent", + reason = reason + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/security_isolation.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/security_isolation.rs new file mode 100644 index 00000000000..fad060e3125 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/security_isolation.rs @@ -0,0 +1,79 @@ +// ******************************************************************************* +// 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 +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + secpol_type: String, + run_as_root_attempt: bool, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct SecurityIsolationScenario; + +impl Scenario for SecurityIsolationScenario { + fn name(&self) -> &str { + "security_isolation" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + let supported = test_input.secpol_type == "strict"; + + info!(component = "launch_manager", state = "running", api = "security_policy"); + info!( + component = "security_crypto", + policy_domain = "secpol", + secpol_type = test_input.secpol_type.as_str() + ); + + if supported { + info!(event = "secpol_type_support", status = "accepted", supported = true); + } else { + info!(event = "secpol_type_support", status = "rejected", supported = false); + } + + if test_input.run_as_root_attempt { + info!(event = "privilege_escalation_attempt", requested_uid = 0); + info!( + event = "non_root_enforced", + effective_uid = 1001, + status = "denied_root" + ); + } else { + info!( + event = "non_root_enforced", + effective_uid = 1001, + status = "non_root_ok" + ); + } + + info!( + event = "sandbox_isolation", + status = "active", + boundary = "process_container" + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/time_sync.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/time_sync.rs new file mode 100644 index 00000000000..0a9a746fa35 --- /dev/null +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/lifecycle/time_sync.rs @@ -0,0 +1,67 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +use serde::Deserialize; +use serde_json::Value; +use test_scenarios_rust::scenario::Scenario; +use tracing::info; + +#[derive(Deserialize, Debug)] +struct TestInput { + event_order_monotonic: bool, + timestamp_aligned: bool, +} + +impl TestInput { + fn from_json(input: &str) -> Result { + let value: Value = serde_json::from_str(input).map_err(|e| e.to_string())?; + serde_json::from_value(value["test"].clone()).map_err(|e| e.to_string()) + } +} + +pub struct TimeSyncScenario; + +impl Scenario for TimeSyncScenario { + fn name(&self) -> &str { + "time_sync" + } + + fn run(&self, input: &str) -> Result<(), String> { + let test_input = TestInput::from_json(input)?; + + info!(event = "lifecycle_timestamp_emitted", timestamp_field = "system_time"); + info!(event = "clock_source_selected", clock_source = "monotonic"); + + if test_input.event_order_monotonic && test_input.timestamp_aligned { + info!( + event = "time_sync_consistent", + status = "consistent", + reference = "monotonic_clock" + ); + return Ok(()); + } + + let reason = if !test_input.event_order_monotonic { + "non_monotonic_event_order" + } else { + "timestamp_drift" + }; + info!( + event = "time_sync_inconsistent", + status = "inconsistent", + reason = reason + ); + + Ok(()) + } +} diff --git a/feature_integration_tests/test_scenarios/rust/src/scenarios/mod.rs b/feature_integration_tests/test_scenarios/rust/src/scenarios/mod.rs index 00f66457722..8ebbb373121 100644 --- a/feature_integration_tests/test_scenarios/rust/src/scenarios/mod.rs +++ b/feature_integration_tests/test_scenarios/rust/src/scenarios/mod.rs @@ -13,15 +13,17 @@ use test_scenarios_rust::scenario::{ScenarioGroup, ScenarioGroupImpl}; mod basic; +mod lifecycle; mod persistency; use basic::basic_scenario_group; +use lifecycle::lifecycle_group; use persistency::persistency_group; pub fn root_scenario_group() -> Box { Box::new(ScenarioGroupImpl::new( "root", vec![], - vec![basic_scenario_group(), persistency_group()], + vec![basic_scenario_group(), lifecycle_group(), persistency_group()], )) } From f932d961fb5cdf974f34efc5bb699b0dfa4e6629 Mon Sep 17 00:00:00 2001 From: Saumya-R Date: Fri, 3 Jul 2026 14:34:50 +0530 Subject: [PATCH 2/3] removed escape characters --- .../test_cases/conftest.py | 2 ++ .../src/scenarios/lifecycle/application_if.cpp | 16 +++++++++++++--- .../scenarios/lifecycle/orchestrator_sync.cpp | 18 +++++++++++++++--- .../scenarios/lifecycle/security_isolation.cpp | 14 +++++++++++++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/feature_integration_tests/test_cases/conftest.py b/feature_integration_tests/test_cases/conftest.py index 5d99e7076be..df84d9ac4ae 100644 --- a/feature_integration_tests/test_cases/conftest.py +++ b/feature_integration_tests/test_cases/conftest.py @@ -114,10 +114,12 @@ def _build_target(target_name: str) -> None: ) if result.returncode != 0: stderr_tail = "\n".join(result.stderr.strip().splitlines()[-40:]) + stdout_tail = "\n".join(result.stdout.strip().splitlines()[-40:]) raise RuntimeError( "Failed to run build with pytest --build-scenarios.\n" f"Command: {' '.join(command)}\n" f"Return code: {result.returncode}\n" + f"stdout (last lines):\n{stdout_tail}\n" f"stderr (last lines):\n{stderr_tail}" ) diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp index 7c505758578..c08af543280 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/application_if.cpp @@ -80,7 +80,17 @@ class ApplicationInterfaceScenario : public Scenario { void run(const std::string& input) const override { const bool daemon_enabled = bool_from_input(input, "daemon_enabled"); const std::string signal_name = string_from_input(input, "signal_name", "SIGUSR1"); - + const std::string escaped_signal_name = [&signal_name]() { + std::string out; + out.reserve(signal_name.size()); + for (char c : signal_name) { + if (c == '\\' || c == '"') { + out.push_back('\\'); + } + out.push_back(c); + } + return out; + }(); kvs_build_helpers::log_info( "\"component\":\"launch_manager\",\"state\":\"running\",\"api\":\"application_if\"", "cpp_test_scenarios::scenarios::lifecycle::application_if"); @@ -94,14 +104,14 @@ class ApplicationInterfaceScenario : public Scenario { "cpp_test_scenarios::scenarios::lifecycle::application_if"); kvs_build_helpers::log_info( "\"event\":\"signal_dispatched\",\"condition\":\"daemon_running\",\"signal_name\":\"" + - signal_name + "\",\"target_process\":\"score_application\"", + escaped_signal_name + "\",\"target_process\":\"score_application\"", "cpp_test_scenarios::scenarios::lifecycle::application_if"); return; } kvs_build_helpers::log_info( "\"event\":\"signal_skipped\",\"condition\":\"daemon_not_running\",\"signal_name\":\"" + - signal_name + "\",\"target_process\":\"score_application\"", + escaped_signal_name + "\",\"target_process\":\"score_application\"", "cpp_test_scenarios::scenarios::lifecycle::application_if"); } }; diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp index ba00b34ef81..39b71e4d2f7 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/orchestrator_sync.cpp @@ -82,15 +82,27 @@ class OrchestratorSyncScenario : public Scenario { const bool orchestrator_state_synced = bool_from_input(input, "orchestrator_state_synced"); const std::string from_target = string_from_input(input, "from_target", "Startup"); const std::string to_target = string_from_input(input, "to_target", "Nominal"); - + const auto json_escape = [](const std::string& s) { + std::string out; + out.reserve(s.size()); + for (char c : s) { + if (c == '\\' || c == '"') { + out.push_back('\\'); + } + out.push_back(c); + } + return out; + }; + const std::string escaped_from_target = json_escape(from_target); + const std::string escaped_to_target = json_escape(to_target); kvs_build_helpers::log_info( "\"event\":\"run_target_support\",\"status\":\"enabled\"", "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); if (run_target_switch_success) { kvs_build_helpers::log_info( - "\"event\":\"run_target_switched\",\"from_target\":\"" + from_target + - "\",\"to_target\":\"" + to_target + "\"", + "\"event\":\"run_target_switched\",\"from_target\":\"" + escaped_from_target + + "\",\"to_target\":\"" + escaped_to_target + "\"", "cpp_test_scenarios::scenarios::lifecycle::orchestrator_sync"); } else { kvs_build_helpers::log_info( diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp index 8b06d73c1fe..c7464938e1a 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/lifecycle/security_isolation.cpp @@ -79,6 +79,18 @@ class SecurityIsolationScenario : public Scenario { void run(const std::string& input) const override { const std::string secpol_type = string_from_input(input, "secpol_type", "strict"); + const auto json_escape = [](const std::string& s) { + std::string out; + out.reserve(s.size()); + for (char c : s) { + if (c == '\\' || c == '"') { + out.push_back('\\'); + } + out.push_back(c); + } + return out; + }; + const std::string escaped_secpol_type = json_escape(secpol_type); const bool run_as_root_attempt = bool_from_input(input, "run_as_root_attempt"); const bool supported = secpol_type == "strict"; @@ -86,7 +98,7 @@ class SecurityIsolationScenario : public Scenario { "\"component\":\"launch_manager\",\"state\":\"running\",\"api\":\"security_policy\"", "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); kvs_build_helpers::log_info( - "\"component\":\"security_crypto\",\"policy_domain\":\"secpol\",\"secpol_type\":\"" + secpol_type + "\"", + "\"component\":\"security_crypto\",\"policy_domain\":\"secpol\",\"secpol_type\":\"" + escaped_secpol_type + "\"", "cpp_test_scenarios::scenarios::lifecycle::security_isolation"); if (supported) { From e9bfdcf132309f2b5e41d61e7f55faa97fa7f50b Mon Sep 17 00:00:00 2001 From: Saumya-R Date: Fri, 3 Jul 2026 17:40:42 +0530 Subject: [PATCH 3/3] streaming bazel output to the terminal --- .../test_cases/conftest.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/feature_integration_tests/test_cases/conftest.py b/feature_integration_tests/test_cases/conftest.py index df84d9ac4ae..b2d571ce3d9 100644 --- a/feature_integration_tests/test_cases/conftest.py +++ b/feature_integration_tests/test_cases/conftest.py @@ -105,22 +105,25 @@ def pytest_sessionstart(session): def _build_target(target_name: str) -> None: command = ["bazel", "build", f"--config={bazel_config}", target_name] - result = subprocess.run( - command, - capture_output=True, - text=True, - check=False, - timeout=build_timeout, - ) + try: + result = subprocess.run( + command, + check=False, + timeout=build_timeout, + ) + except subprocess.TimeoutExpired as exc: + raise RuntimeError( + "Bazel build timed out while running pytest --build-scenarios.\n" + f"Command: {' '.join(command)}\n" + f"Timeout (seconds): {build_timeout}" + ) from exc + if result.returncode != 0: - stderr_tail = "\n".join(result.stderr.strip().splitlines()[-40:]) - stdout_tail = "\n".join(result.stdout.strip().splitlines()[-40:]) raise RuntimeError( - "Failed to run build with pytest --build-scenarios.\n" + "Bazel build failed while running pytest --build-scenarios.\n" f"Command: {' '.join(command)}\n" f"Return code: {result.returncode}\n" - f"stdout (last lines):\n{stdout_tail}\n" - f"stderr (last lines):\n{stderr_tail}" + "See streamed Bazel output above for details." ) # Build Rust test scenarios.