From cd71b9299cc1c04e9eb0a9661edfdee23271049f Mon Sep 17 00:00:00 2001 From: Chris Langhans Date: Tue, 5 May 2026 15:05:04 +0200 Subject: [PATCH 1/3] build(docs): add score_docs_as_code dep and docs() target - Add score_docs_as_code v4.0.1 dependency to MODULE.bazel - Configure Java 17 in .bazelrc for PlantUML smetana layout - Add docs() Bazel target to BUILD (load() at top per buildifier) - Ignore Sphinx _build/ output and generated ubproject.toml - Add docs/conf.py with project metadata and score_sphinx_bundle --- .bazelrc | 5 +++++ .gitignore | 6 ++++++ BUILD | 5 +++++ MODULE.bazel | 7 +++++++ docs/conf.py | 19 +++++++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 docs/conf.py diff --git a/.bazelrc b/.bazelrc index 09b59b6..24b7fdc 100644 --- a/.bazelrc +++ b/.bazelrc @@ -12,3 +12,8 @@ build:x86_64-qnx --credential_helper=*.qnx.com=%workspace%/tools/qnx_credential_ test:qemu-integration --config=x86_64-qnx test:qemu-integration --run_under=//scripts:run_under_qemu + +build --java_language_version=17 +build --java_runtime_version=remotejdk_17 +build --tool_java_language_version=17 +build --tool_java_runtime_version=remotejdk_17 diff --git a/.gitignore b/.gitignore index 65e53b2..9768341 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,9 @@ bazel-* *__pycache__* .venv/ *.egg-info/ + +# Sphinx build output +_build/ + +# Auto-generated by score_sphinx_bundle +docs/ubproject.toml diff --git a/BUILD b/BUILD index d7664c3..d7272fc 100644 --- a/BUILD +++ b/BUILD @@ -12,6 +12,7 @@ # ******************************************************************************* load("@rules_python//python:defs.bzl", "py_library") load("@rules_python//python:pip.bzl", "compile_pip_requirements") +load("@score_docs_as_code//:docs.bzl", "docs") load("@score_tooling//:defs.bzl", "copyright_checker") compile_pip_requirements( @@ -74,3 +75,7 @@ copyright_checker( template = "@score_tooling//cr_checker/resources:templates", visibility = ["//visibility:public"], ) + +docs( + source_dir = "docs", +) diff --git a/MODULE.bazel b/MODULE.bazel index 8ac4f45..48f24bf 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -117,6 +117,13 @@ bazel_dep(name = "score_tooling", version = "1.1.2") bazel_dep(name = "score_bazel_platforms", version = "0.1.2") bazel_dep(name = "platforms", version = "1.0.0") +############################################################################### +# +# Documentation dependencies +# +############################################################################### +bazel_dep(name = "score_docs_as_code", version = "4.0.1") + ################################################################################ # # Load DLT dependencies diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..6499ece --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,19 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +project = "Score ITF" +project_url = "https://github.com/eclipse-score/itf" + +extensions = [ + "score_sphinx_bundle", +] From 72cadbc4c5d9ad40212346a6535c4ed8f8ac4056 Mon Sep 17 00:00:00 2001 From: Chris Langhans Date: Tue, 5 May 2026 15:05:12 +0200 Subject: [PATCH 2/3] docs: add Sphinx documentation site Add full documentation under docs/ covering: - How-to guides: get started, write tests, plugin usage - Reference: Bazel macros/rules, plugin CLI args, Target API - Concepts: architecture overview with PlantUML diagram, plugin system, capability model, plugin loading order - Stub sections for manual, release, safety_mgt, security_mgt, verification_report per S-CORE module folder structure spec --- docs/concepts/architecture.rst | 163 ++++++++++++++++++++++ docs/concepts/index.rst | 25 ++++ docs/concepts/itf_architecture.puml | 61 +++++++++ docs/how-to/get_started.md | 146 ++++++++++++++++++++ docs/how-to/index.rst | 27 ++++ docs/how-to/plugins.md | 164 ++++++++++++++++++++++ docs/how-to/write_tests.md | 174 ++++++++++++++++++++++++ docs/index.rst | 56 ++++++++ docs/manual/index.rst | 28 ++++ docs/reference/bazel_macros.rst | 170 +++++++++++++++++++++++ docs/reference/index.rst | 26 ++++ docs/reference/plugins.rst | 204 ++++++++++++++++++++++++++++ docs/release/index.rst | 26 ++++ docs/safety_mgt/index.rst | 26 ++++ docs/security_mgt/index.rst | 28 ++++ docs/verification_report/index.rst | 28 ++++ 16 files changed, 1352 insertions(+) create mode 100644 docs/concepts/architecture.rst create mode 100644 docs/concepts/index.rst create mode 100644 docs/concepts/itf_architecture.puml create mode 100644 docs/how-to/get_started.md create mode 100644 docs/how-to/index.rst create mode 100644 docs/how-to/plugins.md create mode 100644 docs/how-to/write_tests.md create mode 100644 docs/index.rst create mode 100644 docs/manual/index.rst create mode 100644 docs/reference/bazel_macros.rst create mode 100644 docs/reference/index.rst create mode 100644 docs/reference/plugins.rst create mode 100644 docs/release/index.rst create mode 100644 docs/safety_mgt/index.rst create mode 100644 docs/security_mgt/index.rst create mode 100644 docs/verification_report/index.rst diff --git a/docs/concepts/architecture.rst b/docs/concepts/architecture.rst new file mode 100644 index 0000000..17e739c --- /dev/null +++ b/docs/concepts/architecture.rst @@ -0,0 +1,163 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +.. _itf_architecture: + +Architecture +============ + +This page explains the core design decisions in ITF: the target abstraction +layer, the capability system, the plugin lifecycle, and how ITF integrates +bidirectionally with Bazel. + +.. plantuml:: itf_architecture.puml + +Target abstraction layer +------------------------ + +The central concept in ITF is the ``Target``. A target represents the device +or environment under test — a Docker container, a QEMU virtual machine, or +real hardware. All target types expose the same interface, so test code does +not need to know which environment it runs on. + +.. code-block:: python + + class Target: + def execute(self, command): ... + def upload(self, local_path, remote_path): ... + def download(self, remote_path, local_path): ... + def restart(self): ... + def get_capabilities(self) -> Set[str]: ... + +A test that calls ``target.execute("uname -a")`` runs unchanged against a +Docker container or a QEMU VM. The target type is determined at build time by +the ``plugins`` attribute on ``py_itf_test``, and at run time by the CLI +args (e.g. ``--docker-image``) that configure the chosen plugin. + +Capability system +----------------- + +Different target environments support different operations. A plain Docker +container supports ``exec`` and file transfer but not SSH or SFTP unless an +SSH server is installed. A QEMU VM provides SSH, SFTP, and network-level +operations. + +Each ``Target`` subclass declares its capabilities, either by passing them +to the base constructor or by relying on ``Target.REQUIRED_CAPABILITIES`` +(``exec``, ``file_transfer``, ``restart``), which is always merged in. +``DockerTarget`` uses only the required capabilities, so it passes no +extras: + +.. code-block:: python + + class DockerTarget(Target): + def __init__(self, container): + super().__init__() # capabilities come from REQUIRED_CAPABILITIES + self.container = container + +Tests can be guarded against targets that lack required capabilities using the +``@requires_capabilities`` decorator: + +.. code-block:: python + + from score.itf.plugins.core import requires_capabilities + + @requires_capabilities("ssh", "sftp") + def test_file_roundtrip(target): + ... + +If the active target does not provide all listed capabilities, pytest skips the +test with a clear message. This keeps test suites portable: the same file can +run against Docker for fast feedback and against a QEMU VM for full-system +integration, skipping tests that do not apply. + +Tests can also query capabilities at runtime and branch accordingly: + +.. code-block:: python + + def test_adaptive(target): + if target.has_capability("ssh"): + with target.ssh() as ssh: + ssh.execute_command("echo hello") + else: + exit_code, _ = target.execute("echo hello") + +Plugin lifecycle +---------------- + +Each plugin contributes a ``target_init`` pytest fixture. ITF's core plugin +calls this fixture to obtain the target instance, then wraps it in the +``target`` fixture that test functions receive. + +The lifecycle for a single test is: + +1. **Setup**: The plugin's ``target_init`` fixture starts the target + (spins up a container, boots a QEMU VM, connects to hardware). +2. **Test execution**: The test function receives the ``target`` fixture + and exercises the system under test. +3. **Teardown**: ``target_init`` tears down the target (stops the + container, shuts down the VM). + +With ``--keep-target``, steps 1 and 3 run once per session instead of once +per test function. This is faster but means tests share target state, so it +should only be used when tests are designed to be order-independent. + +**Plugin loading order is deterministic but should not be relied upon.** +The core plugin is always registered first. The remaining plugins are +registered with pytest in the exact order they are listed in +``py_itf_test.plugins``. While this order is stable, plugins are designed +to be independent of each other — no plugin should depend on another +plugin's initialisation having completed first. + +Why a plugin-based design +-------------------------- + +Plugin-based design was chosen for three reasons: + +**Separation of concerns.** Target management logic (starting containers, +booting VMs) is entirely isolated from test logic. A test that calls +``target.execute()`` has no dependency on Docker or QEMU APIs. + +**Extensibility without forking.** Custom targets (real hardware, emulators, +cloud VMs) are added by implementing ``Target`` and ``target_init`` in a new +plugin. No changes to the ITF core are needed. + +**Bazel-native composition.** Because plugins are declared as Bazel targets +with ``py_itf_plugin``, they carry their own Python libraries, data files, +and CLI args. Combining plugins — for example Docker + DLT — is as simple as +listing both labels in ``py_itf_test.plugins``. Bazel resolves transitive +dependencies automatically. + +Bidirectional Bazel integration +--------------------------------- + +ITF integrates with Bazel in both directions: + +**Build-time** (Bazel → ITF): The ``py_itf_test`` symbolic macro creates a +``py_test`` binary that bundles the test code and all plugin Python +libraries. Plugin CLI args (e.g. ``--docker-image``, paths from +``$(location ...)``) are resolved at analysis time and baked into a launcher +script. This means test hermetically carry their full dependency graph, +including container images or QEMU images referenced via Bazel labels. + +**Run-time** (ITF → Bazel): ITF uses Bazel's runfiles mechanism to locate +data files at runtime. The ``$(location ...)`` substitution in ``args`` +produces runfiles-relative paths that work regardless of where Bazel places +files in the output tree. Test results are reported via JUnit XML to +``$XML_OUTPUT_FILE``, integrating with Bazel's native test reporting and +caching. + +This design means ITF tests participate fully in Bazel's incremental build +and caching: a test is only re-run if its source, its dependencies, or its +configuration changes. diff --git a/docs/concepts/index.rst b/docs/concepts/index.rst new file mode 100644 index 0000000..36fa344 --- /dev/null +++ b/docs/concepts/index.rst @@ -0,0 +1,25 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +.. _itf_concepts: + +Concepts +======== + +Architecture, design decisions, and explanatory material for ITF. + +.. toctree:: + :maxdepth: 1 + + architecture diff --git a/docs/concepts/itf_architecture.puml b/docs/concepts/itf_architecture.puml new file mode 100644 index 0000000..b0b71d4 --- /dev/null +++ b/docs/concepts/itf_architecture.puml @@ -0,0 +1,61 @@ +@startuml ITF Architecture +!pragma layout smetana +skinparam componentStyle rectangle +skinparam defaultFontName sans-serif +skinparam linetype ortho + +' ── Test layer ──────────────────────────────────────────────────────────────── +package "Test Code" { + component "test_*.py\n(pytest test function)" as TestFn + component "@requires_capabilities\n(optional guard)" as RC +} + +' ── ITF Core ────────────────────────────────────────────────────────────────── +package "ITF Core (score.itf.plugins.core)" { + component "target fixture" as TargetFx + component "target_init fixture\n(plugin hook)" as TargetInit +} + +' ── Target abstraction ──────────────────────────────────────────────────────── +package "Target Abstraction" { + component "Target (base class)\n──────────────────\nexecute(cmd)\nupload(src, dst)\ndownload(src, dst)\nrestart()\nhas_capability(cap)\nget_capabilities() : Set[str]" as Target +} + +' ── Plugin implementations ──────────────────────────────────────────────────── +package "Plugins" { + component "Docker Plugin\n──────────\ncapabilities:\nexec | file_transfer\nrestart" as DockerPlugin + + component "QEMU Plugin\n──────────\ncapabilities:\nssh | sftp | exec\nfile_transfer | restart" as QemuPlugin + + component "DLT Plugin\n──────────\nDltWindow\ndlt_config fixture" as DltPlugin +} + +' ── Infrastructure ──────────────────────────────────────────────────────────── +package "Infrastructure" { + database "Docker Daemon" as DockerInfra + database "QEMU VM" as QemuInfra +} + +' ── Bazel build layer ───────────────────────────────────────────────────────── +package "Bazel Build" { + component "py_itf_test\n(symbolic macro)" as PyItfTest + component "py_itf_plugin\n(rule)" as PyItfPlugin +} + +' ── Relationships ───────────────────────────────────────────────────────────── +TestFn -down-> TargetFx : receives +TestFn .right.> RC : guarded by +TargetFx -down-> TargetInit : delegates to +TargetInit -down-> Target : yields instance of +DockerPlugin -up--|> Target : implements +QemuPlugin -up--|> Target : implements + +DockerPlugin -down-> DockerInfra : manages lifecycle +QemuPlugin -down-> QemuInfra : manages lifecycle + +TestFn ..> DltPlugin : optional\nDltWindow usage + +PyItfTest -right-> PyItfPlugin : composes plugins +PyItfPlugin -up-> TargetInit : provides\ntarget_init fixture + +@enduml diff --git a/docs/how-to/get_started.md b/docs/how-to/get_started.md new file mode 100644 index 0000000..96addaa --- /dev/null +++ b/docs/how-to/get_started.md @@ -0,0 +1,146 @@ +# Get Started + +This guide walks you through setting up ITF in a new Bazel workspace from scratch. + +## Prerequisites + +- [Bazel](https://bazel.build/install) 7.x or later +- Docker (for Docker-based tests) +- Python 3.12+ + +## 1. Add ITF to your workspace + +In your `MODULE.bazel`, declare the dependency using the latest published +registry version: + +```starlark +bazel_dep(name = "score_itf", version = "0.2.0") +``` + +To get unreleased fixes from the main branch, add a `git_override` directly +after the `bazel_dep`: + +```starlark +git_override( + module_name = "score_itf", + remote = "https://github.com/eclipse-score/itf.git", + commit = "", +) +``` + +Replace `` with the full SHA of the desired commit from the +[score_itf repository](https://github.com/eclipse-score/itf). + +## 2. Configure `.bazelrc` + +Add the S-CORE Bazel registry so Bazel can resolve the `score_itf` module: + +``` +common --registry=https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/ +common --registry=https://bcr.bazel.build +``` + +If you also want to build the ITF documentation locally (optional), add the +Java configuration required by PlantUML: + +``` +build --java_language_version=17 +build --java_runtime_version=remotejdk_17 +build --tool_java_language_version=17 +build --tool_java_runtime_version=remotejdk_17 +``` + +## 3. Write your first test + +Create a test file `test_hello.py`: + +```python +def test_hello(target): + exit_code, output = target.execute("echo 'Hello from target!'") + assert exit_code == 0 + assert b"Hello from target!" in output +``` + +Create a `BUILD` file in the same directory: + +```starlark +load("@score_itf//:defs.bzl", "py_itf_test") + +py_itf_test( + name = "test_hello", + srcs = ["test_hello.py"], + args = ["--docker-image=ubuntu:24.04"], + plugins = ["@score_itf//score/itf/plugins:docker_plugin"], +) +``` + +> **Note:** All `load()` calls and plugin labels must use the `@score_itf` +> prefix. Using `//:defs.bzl` without the prefix would look for that file +> in your own workspace and fail with `no such file`. + +## 4. Run the test + +```bash +bazel test //path/to:test_hello +``` + +To see full test output: + +```bash +bazel test //path/to:test_hello --test_output=all +``` + +## 5. Link tests to requirements + +ITF integrates with the S-CORE docs-as-code traceability system. The +`@add_test_properties` decorator writes requirement links and test +classification metadata into the JUnit XML report, which Sphinx can then +pick up to create bidirectional traceability between test results and +requirements. + +```python +from attribute_plugin import add_test_properties + +@add_test_properties( + fully_verifies=["REQ-001", "REQ-002"], + test_type="requirements-based", + derivation_technique="requirements-analysis", +) +def test_hello(target): + exit_code, output = target.execute("echo 'Hello from target!'") + assert exit_code == 0 + assert b"Hello from target!" in output +``` + +Add `attribute_plugin` to the `plugins` list in your `BUILD` file: + +```starlark +py_itf_test( + name = "test_hello", + srcs = ["test_hello.py"], + args = ["--docker-image=ubuntu:24.04"], + plugins = [ + "@score_itf//score/itf/plugins:docker_plugin", + "@score_itf//score/itf/plugins:attribute_plugin", + ], +) +``` + +## Next steps + +- **Write more tests**: See [Write Tests](write_tests.md) for Docker, QEMU, + and DLT examples. +- **Use plugins**: See [Using Plugins](plugins.md) for configuring and + combining built-in plugins. +- **Understand the design**: See the [Concepts](../concepts/index.rst) + section for the architecture and plugin system. + +## For ITF contributors + +Build this documentation site locally: + +```bash +bazel run //:docs +``` + +Open `_build/index.html` in your browser. diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst new file mode 100644 index 0000000..d39945a --- /dev/null +++ b/docs/how-to/index.rst @@ -0,0 +1,27 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +.. _itf_how-to: + +How To +====== + +Practical guides for installing and using ITF. + +.. toctree:: + :maxdepth: 1 + + get_started + write_tests + plugins diff --git a/docs/how-to/plugins.md b/docs/how-to/plugins.md new file mode 100644 index 0000000..7244368 --- /dev/null +++ b/docs/how-to/plugins.md @@ -0,0 +1,164 @@ +# Using Plugins + +ITF's functionality is delivered through plugins. Each plugin activates a +target type or a supporting capability (such as DLT message capture). + +## Built-in plugins + +| Plugin label | Provides | Key CLI arg | +|---|---|---| +| `@score_itf//score/itf/plugins:docker_plugin` | Docker target | `--docker-image` | +| `@score_itf//score/itf/plugins:qemu_plugin` | QEMU target | `--qemu-image`, `--qemu-config` | +| `@score_itf//score/itf/plugins:dlt_plugin` | DLT capture | `--dlt-receive-path`, `--dlt-config` | +| `@score_itf//score/itf/plugins:attribute_plugin` | Requirement traceability | N/A | + +> **Plugin loading order:** The core plugin is always loaded first. Additional +> plugins are loaded in the order they appear in `plugins = [...]`. Plugins +> are designed to be independent — do not write tests that rely on one plugin +> having initialised before another. + +## Docker plugin + +```starlark +load("@score_itf//:defs.bzl", "py_itf_test") + +py_itf_test( + name = "test_docker", + srcs = ["test_docker.py"], + args = ["--docker-image=ubuntu:24.04"], + plugins = ["@score_itf//score/itf/plugins:docker_plugin"], +) +``` + +Pass `--docker-image` as a Bazel test arg or hard-code it in `args`. + +## QEMU plugin + +```starlark +py_itf_test( + name = "test_qemu", + srcs = ["test_qemu.py"], + args = [ + "--qemu-image=$(location //path:qemu_image)", + "--qemu-config=$(location qemu_config.json)", + ], + data = [ + "//path:qemu_image", + "qemu_config.json", + ], + plugins = ["@score_itf//score/itf/plugins:qemu_plugin"], +) +``` + +## DLT plugin + +Combine the DLT plugin with a target plugin to enable log capture: + +```starlark +py_itf_test( + name = "test_with_dlt", + srcs = ["test_with_dlt.py"], + args = [ + "--docker-image=my-app:latest", + "--dlt-config=$(location dlt_config.json)", + ], + data = ["dlt_config.json"], + plugins = [ + "@score_itf//score/itf/plugins:docker_plugin", + "@score_itf//score/itf/plugins:dlt_plugin", + ], +) +``` + +DLT configuration file (`dlt_config.json`): + +```json +{ + "target_ip": "192.168.122.76", + "host_ip": "192.168.122.1", + "multicast_ips": ["239.255.42.99"] +} +``` + +In the test, use `DltWindow` to capture and query DLT messages: + +```python +from score.itf.plugins.dlt.dlt_window import DltWindow +from score.itf.plugins.dlt.dlt_receive import Protocol +import re + +def test_dlt_messages(target, dlt_config): + with DltWindow( + protocol=Protocol.UDP, + host_ip="127.0.0.1", + multicast_ips=["224.0.0.1"], + binary_path=dlt_config.dlt_receive_path, + ) as window: + with target.ssh() as ssh: + ssh.execute_command("my_application") + + record = window.record() + results = record.find(query={ + "apid": re.compile(r"APP1"), + "payload": re.compile(r".*Started successfully.*"), + }) + assert len(results) > 0 +``` + +## Creating a custom plugin + +Implement the `Target` abstract class and a `target_init` fixture: + +```python +# my_plugin.py +import pytest +from score.itf.plugins.core import Target, determine_target_scope + +MY_CAPABILITIES = ["custom_feature"] + +class MyTarget(Target): + def __init__(self): + super().__init__(capabilities=MY_CAPABILITIES) + + def execute(self, command): + pass # implementation + + def execute_async(self, binary_path, args=None, cwd="/"): + pass # implementation + + def upload(self, local_path, remote_path): + pass # implementation + + def download(self, remote_path, local_path): + pass # implementation + + def restart(self): + pass # implementation + +@pytest.fixture(scope=determine_target_scope) +def target_init(): + yield MyTarget() +``` + +Register the plugin with `py_itf_plugin`: + +```starlark +load("@score_itf//bazel:py_itf_plugin.bzl", "py_itf_plugin") + +py_itf_plugin( + name = "my_plugin", + enabled_plugins = ["my_plugin"], + py_library = "//path/to:my_plugin_lib", + visibility = ["//visibility:public"], +) +``` + +Use it in tests: + +```starlark +py_itf_test( + name = "test_custom", + srcs = ["test.py"], + plugins = ["//path/to:my_plugin"], +) +``` diff --git a/docs/how-to/write_tests.md b/docs/how-to/write_tests.md new file mode 100644 index 0000000..6cb504c --- /dev/null +++ b/docs/how-to/write_tests.md @@ -0,0 +1,174 @@ +# Write Tests + +This guide covers how to write tests for the three main target types: Docker, +QEMU, and capability-based tests. + +## Docker tests + +Docker tests use `target.execute()` which runs commands via the Docker native +exec API. Any Docker image works — no SSH server required. + +```python +def test_exec(target): + exit_code, output = target.execute("uname -a") + assert exit_code == 0 + assert b"Linux" in output +``` + +`BUILD`: + +```starlark +load("@score_itf//:defs.bzl", "py_itf_test") + +py_itf_test( + name = "test_exec", + srcs = ["test_exec.py"], + args = ["--docker-image=ubuntu:24.04"], + plugins = ["@score_itf//score/itf/plugins:docker_plugin"], +) +``` + +### SSH on Docker targets + +> **`ubuntu:24.04` does not work with `target.ssh()`.** +> `target.ssh()` makes a real SSH connection to port 2222 of the container +> with username/password authentication. A plain Ubuntu image has no SSH +> server, so the connection will always fail. Use `target.execute()` instead +> (shown above) unless you specifically need SSH. + +If you do need SSH, use an image that runs an SSH server +(e.g. `linuxserver/openssh-server`) and supply credentials via the +`docker_configuration` fixture: + +```python +import pytest + +@pytest.fixture(scope="session") +def docker_configuration(): + return { + "environment": { + "PASSWORD_ACCESS": "true", + "USER_NAME": "score", + "USER_PASSWORD": "score", + }, + "command": None, + "init": False, + } + +def test_ssh(target): + with target.ssh() as ssh: + # execute_command is a method on the Ssh object — no separate import needed + exit_code = ssh.execute_command("whoami") + assert exit_code == 0 +``` + +`BUILD`: + +```starlark +py_itf_test( + name = "test_ssh", + srcs = ["test_ssh.py"], + args = ["--docker-image=linuxserver/openssh-server:version-10.2_p1-r0"], + plugins = ["@score_itf//score/itf/plugins:docker_plugin"], +) +``` + +### Custom Docker configuration + +Override Docker settings per test by implementing `docker_configuration`. +Supported keys: `environment`, `command`, `init`, `shm_size`, `volumes`. + +```python +import pytest + +@pytest.fixture +def docker_configuration(): + return { + "environment": {"MY_VAR": "hello"}, + "shm_size": "2G", + "volumes": {"/host/path": {"bind": "/container/path", "mode": "rw"}}, + } +``` + +## QEMU tests + +QEMU targets expose SSH and SFTP capabilities, and optionally network testing +via `target.ping()`. + +```python +def test_qemu_ssh(target): + with target.ssh(username="root", password="") as ssh: + exit_code = ssh.execute_command("uname -a") + assert exit_code == 0 + +def test_file_transfer(target): + with target.sftp() as sftp: + sftp.upload("local.txt", "/tmp/remote.txt") + sftp.download("/tmp/remote.txt", "downloaded.txt") +``` + +`BUILD`: + +```starlark +py_itf_test( + name = "test_qemu", + srcs = ["test_qemu.py"], + args = [ + "--qemu-image=$(location //path:qemu_image)", + "--qemu-config=$(location qemu_config.json)", + ], + data = [ + "//path:qemu_image", + "qemu_config.json", + ], + plugins = ["@score_itf//score/itf/plugins:qemu_plugin"], +) +``` + +QEMU configuration file (`qemu_config.json`): + +```json +{ + "networks": [ + { + "name": "tap0", + "ip_address": "169.254.158.190", + "gateway": "169.254.21.88" + } + ], + "ssh_port": 22, + "qemu_num_cores": 2, + "qemu_ram_size": "1G" +} +``` + +## Capability-based tests + +The `@requires_capabilities` decorator automatically skips tests if the target +does not provide the listed capabilities. + +```python +from score.itf.plugins.core import requires_capabilities + +@requires_capabilities("exec") +def test_docker_only(target): + # Skipped automatically on targets without 'exec' + exit_code, output = target.execute("ls /tmp") + assert exit_code == 0 + +@requires_capabilities("ssh", "sftp") +def test_network_features(target): + # Only runs on targets that have both 'ssh' and 'sftp' + with target.ssh() as ssh: + ssh.execute_command("echo ok") +``` + +## Target lifecycle: `--keep-target` + +By default, ITF creates a fresh target for each test function. Pass +`--keep-target` to keep the same target running across all tests in a suite +(faster, but tests share state): + +```bash +bazel test //test:my_test --test_arg=--keep-target +``` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..ec37ae0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,56 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +Score ITF +========= + +Integration Test Framework for ECU testing in automotive domains. + +.. grid:: 1 1 3 3 + :class-container: score-grid + + .. grid-item-card:: + + :ref:`How to ` + ^^^ + Learn how to install and use ITF. + + .. grid-item-card:: + + :ref:`Reference ` + ^^^ + API and Bazel macro reference. + + .. grid-item-card:: + + :ref:`Concepts ` + ^^^ + Architecture, plugin system, and key design decisions. + + +.. dropdown:: Sitemap + + .. toctree:: + :maxdepth: 5 + :includehidden: + :titlesonly: + + how-to/index + reference/index + concepts/index + manual/index + release/index + safety_mgt/index + security_mgt/index + verification_report/index diff --git a/docs/manual/index.rst b/docs/manual/index.rst new file mode 100644 index 0000000..8a163ee --- /dev/null +++ b/docs/manual/index.rst @@ -0,0 +1,28 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + +.. _itf_manual: + +Manual +====== + +Integration and safety guidance for using ITF in a system context. + +.. note:: + This section is not yet written. Planned content: + + - **Integration manual** — how to integrate ITF into a larger CI/CD pipeline + or system-level test setup + - **Safety manual** — safety-related usage constraints and guidance for + applying ITF in safety-relevant projects diff --git a/docs/reference/bazel_macros.rst b/docs/reference/bazel_macros.rst new file mode 100644 index 0000000..9614d12 --- /dev/null +++ b/docs/reference/bazel_macros.rst @@ -0,0 +1,170 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +.. _itf_bazel-macros: + +Bazel Macros and Rules +====================== + +ITF provides two public Bazel build definitions: the ``py_itf_test`` macro +for declaring test targets and the ``py_itf_plugin`` rule for declaring +plugin targets. + +``py_itf_test`` +--------------- + +Defined in ``//bazel:py_itf_test.bzl``. Declare with: + +.. code-block:: starlark + + load("@score_itf//:defs.bzl", "py_itf_test") + +Minimal example: + +.. code-block:: starlark + + py_itf_test( + name = "test_example", + srcs = ["test_example.py"], + args = ["--docker-image=ubuntu:24.04"], + plugins = ["@score_itf//score/itf/plugins:docker_plugin"], + ) + +Attributes +^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + :widths: 20 15 10 55 + + * - Attribute + - Type + - Default + - Description + * - ``srcs`` + - label list + - *mandatory* + - Test source files (``.py``). Passed as positional arguments to pytest. + * - ``plugins`` + - label list + - ``[]`` + - List of ``py_itf_plugin`` targets. Each plugin activates a target type + or supporting capability and contributes CLI args and runfiles to the + test. + * - ``args`` + - string list + - ``[]`` + - Extra CLI arguments passed to pytest. Supports + ``$(location