Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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",
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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".
Expand Down
34 changes: 32 additions & 2 deletions feature_integration_tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
```

Expand Down
49 changes: 42 additions & 7 deletions feature_integration_tests/test_cases/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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'.
Expand All @@ -88,18 +101,40 @@ 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]
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:
raise RuntimeError(
"Bazel build failed while running pytest --build-scenarios.\n"
f"Command: {' '.join(command)}\n"
f"Return code: {result.returncode}\n"
"See streamed Bazel output above for details."
)

# 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)
22 changes: 22 additions & 0 deletions feature_integration_tests/test_cases/requirements.txt.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Loading
Loading