Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ common --registry=https://bcr.bazel.build

test --test_output=errors

coverage --combined_report=lcov
coverage --instrumentation_filter="//score/itf[/:]"

build:x86_64-qnx --incompatible_strict_action_env
build:x86_64-qnx --platforms=@score_bazel_platforms//:x86_64-qnx8_0
build:x86_64-qnx --sandbox_writable_path=/var/tmp
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
is_default = True,
python_version = PYTHON_VERSION,
configure_coverage_tool = True,
)

###############################################################################
Expand Down
51 changes: 51 additions & 0 deletions bazel/py_itf_unittest.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# *******************************************************************************
# 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
# *******************************************************************************

"""Lightweight macro for running unit tests via pytest without ITF plugin infrastructure."""

load("@rules_python//python:defs.bzl", "py_test")

def py_itf_unittest(name, srcs, deps = [], data = [], env = {}, pytest_config = None, **kwargs):
"""Thin py_test wrapper for unit tests that do not need ITF plugin machinery.

Unlike py_itf_test, this macro creates a direct py_test with no launcher
script or plugin infrastructure, so Bazel coverage works out of the box.

Args:
name: Target name.
srcs: Python test source files.
deps: Additional Python dependencies.
data: Data files available at runtime.
env: Environment variables for the test.
pytest_config: Optional pytest config file. Defaults to @score_itf//:pytest.ini.
**kwargs: Forwarded to py_test (e.g. size, timeout, tags).
"""
pytest_bootstrap = Label("@score_itf//:main.py")
if not pytest_config:
pytest_config = Label("@score_itf//:pytest.ini")

py_test(
name = name,
srcs = [pytest_bootstrap] + srcs,
main = pytest_bootstrap,
args = [
"-c $(location %s)" % pytest_config,
"-p no:cacheprovider",
"--show-capture=no",
"--junitxml=$$XML_OUTPUT_FILE",
] + ["$(location %s)" % x for x in srcs],
deps = ["@score_itf//:itf", "@itf_pip//pytest_mock"] + deps,
data = [pytest_config] + data,
env = {"PYTHONDONOTWRITEBYTECODE": "1"} | env,
**kwargs
)
2 changes: 2 additions & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@
"""ITF public Bazel interface"""

load("@score_itf//bazel:py_itf_test.bzl", local_py_itf_test = "py_itf_test")
load("@score_itf//bazel:py_itf_unittest.bzl", local_py_itf_unittest = "py_itf_unittest")

py_itf_test = local_py_itf_test
py_itf_unittest = local_py_itf_unittest
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pytest==9.0.3
paramiko==4.0.0
typing-extensions==4.15.0
pydantic==2.10.6
pytest-mock==3.14.0
6 changes: 6 additions & 0 deletions requirements_lock.txt
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,12 @@ pynacl==1.6.2 \
pytest==9.0.3 \
--hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \
--hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c
# via
# -r requirements.in
# pytest-mock
pytest-mock==3.14.0 \
--hash=sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f \
--hash=sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0
# via -r requirements.in
requests==2.33.0 \
--hash=sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b \
Expand Down
11 changes: 9 additions & 2 deletions score/itf/plugins/qemu/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,28 @@
load("@itf_pip//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_library")

py_library(
name = "config",
srcs = ["config.py"],
imports = ["."],
visibility = ["//visibility:public"],
deps = [requirement("pydantic")],
)

py_library(
name = "qemu",
srcs = [
"__init__.py",
"checks.py",
"config.py",
"qemu.py",
"qemu_process.py",
"qemu_target.py",
],
imports = ["."],
visibility = ["//visibility:public"],
deps = [
":config",
"//score/itf/core/process",
"//score/itf/core/utils",
requirement("pydantic"),
],
)
51 changes: 19 additions & 32 deletions test/BUILD → test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@
# *******************************************************************************
load("//:defs.bzl", "py_itf_test")

py_itf_test(
name = "test_attribute_plugin",
srcs = ["test_attribute_plugin.py"],
deps = ["//score/itf/plugins:attribute_plugin"],
)

py_itf_test(
name = "test_qemu_config_schema",
srcs = ["test_qemu_config_schema.py"],
data = [
"//test/resources:qemu_bridge_config",
"//test/resources:qemu_port_forwarding_config",
],
deps = [
"//score/itf/plugins/qemu",
"@rules_python//python/runfiles",
],
)

py_itf_test(
name = "test_rules_are_working_correctly",
srcs = [
Expand Down Expand Up @@ -183,35 +202,3 @@ test_suite(
":test_ssh_configurable",
],
)

py_itf_test(
name = "test_ping",
srcs = [
"test_ping.py",
],
)

py_itf_test(
name = "test_qemu_config_schema",
srcs = [
"test_qemu_config_schema.py",
],
data = [
"//test/resources:qemu_bridge_config",
"//test/resources:qemu_port_forwarding_config",
],
deps = [
"//score/itf/plugins/qemu",
"@rules_python//python/runfiles",
],
)

py_itf_test(
name = "test_attribute_plugin",
srcs = [
"test_attribute_plugin.py",
],
plugins = [
"//score/itf/plugins:attribute_plugin",
],
)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
35 changes: 0 additions & 35 deletions test/test_ping.py

This file was deleted.

32 changes: 32 additions & 0 deletions test/unit/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
load("//:defs.bzl", "py_itf_unittest")

py_itf_unittest(
name = "test_ping",
srcs = ["test_ping.py"],
)

py_itf_unittest(
name = "test_qemu_config_schema",
srcs = ["test_qemu_config_schema.py"],
deps = ["//score/itf/plugins/qemu:config"],
)

test_suite(
name = "unit",
tests = [
":test_ping",
":test_qemu_config_schema",
],
)
34 changes: 34 additions & 0 deletions test/unit/test_ping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

import pytest

from score.itf.core.com.ping import ping


def test_ping_raises_when_ping_utility_is_missing(mocker):
mocker.patch("score.itf.core.com.ping.shutil.which", return_value=None)
with pytest.raises(RuntimeError, match="'ping' utility is not installed"):
ping("127.0.0.1")


def test_ping_returns_true_when_host_is_reachable(mocker):
mocker.patch("score.itf.core.com.ping.shutil.which", return_value="/usr/bin/ping")
mocker.patch("score.itf.core.com.ping.os.system", return_value=0)
assert ping("127.0.0.1") is True


def test_ping_returns_false_when_host_is_unreachable(mocker):
mocker.patch("score.itf.core.com.ping.shutil.which", return_value="/usr/bin/ping")
mocker.patch("score.itf.core.com.ping.os.system", return_value=1)
assert ping("192.0.2.1") is False
80 changes: 80 additions & 0 deletions test/unit/test_qemu_config_schema.py
Original file line number Diff line number Diff line change
@@ -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
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

import pytest

from score.itf.plugins.qemu.config import QemuConfigModel


_VALID_BRIDGE_CONFIG = {
"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",
}

_VALID_PORT_FORWARDING_CONFIG = {
"networks": [{"name": "lo", "ip_address": "127.0.0.1", "gateway": "127.0.0.1"}],
"ssh_port": 2222,
"qemu_num_cores": 2,
"qemu_ram_size": "1G",
"port_forwarding": [{"host_port": 2222, "guest_port": 22}],
}


def test_valid_bridge_config():
QemuConfigModel.model_validate(_VALID_BRIDGE_CONFIG)


def test_valid_port_forwarding_config():
QemuConfigModel.model_validate(_VALID_PORT_FORWARDING_CONFIG)


def test_missing_networks_is_rejected():
config = {**_VALID_BRIDGE_CONFIG}
del config["networks"]
with pytest.raises(Exception):
QemuConfigModel.model_validate(config)


def test_empty_networks_is_rejected():
config = {**_VALID_BRIDGE_CONFIG, "networks": []}
with pytest.raises(Exception):
QemuConfigModel.model_validate(config)


def test_invalid_ip_address_is_rejected():
config = {
**_VALID_BRIDGE_CONFIG,
"networks": [{"name": "tap0", "ip_address": "not-an-ip", "gateway": "169.254.21.88"}],
}
with pytest.raises(Exception):
QemuConfigModel.model_validate(config)


def test_invalid_ssh_port_is_rejected():
config = {**_VALID_BRIDGE_CONFIG, "ssh_port": 0}
with pytest.raises(Exception):
QemuConfigModel.model_validate(config)


def test_invalid_ram_size_is_rejected():
config = {**_VALID_BRIDGE_CONFIG, "qemu_ram_size": "1GB"}
with pytest.raises(Exception):
QemuConfigModel.model_validate(config)


def test_unknown_keys_are_rejected():
config = {**_VALID_BRIDGE_CONFIG, "unknown_key": "value"}
with pytest.raises(Exception):
QemuConfigModel.model_validate(config)