From 431d3658fbe221937a1bf8defe08a10a7971a7af Mon Sep 17 00:00:00 2001 From: "Lasota, Adrian" Date: Tue, 26 May 2026 07:32:03 +0200 Subject: [PATCH] feat: Add support for EFI Shell OS and related tests Signed-off-by: Lasota, Adrian --- README.md | 1 + examples/simple_example.py | 10 +++ mfd_host/base.py | 4 +- mfd_host/efishell.py | 19 ++++++ pyproject.toml | 15 +++++ tests/unit/test_mfd_host/test_base.py | 96 ++++++++++++++++++++++++++- 6 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 mfd_host/efishell.py diff --git a/README.md b/README.md index d303e5b..4a034ad 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,7 @@ Here is a place to write what OSes support your MFD module: * WINDOWS * FREEBSD * ESXI +* EFI SHELL ## Issue reporting diff --git a/examples/simple_example.py b/examples/simple_example.py index 1a96efa..a7fa4e3 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -7,8 +7,11 @@ import logging +import mfd_connect from mfd_connect import RPyCConnection +from mfd_host import Host +from mfd_host.efishell import EFIShellHost from mfd_host.linux import LinuxHost from mfd_host.windows import WindowsHost from mfd_host.freebsd import FreeBSDHost @@ -34,3 +37,10 @@ def freebsd_driver_example(): connection = RPyCConnection(ip="20.20.20.20") host = FreeBSDHost(connection=connection) host.driver.load_module(module_path="test/test") + + +def efishell_simple_example(): + """Simple EFIShell host instantiation using RshellConnection.""" + connection = getattr(mfd_connect, "RshellConnection")(ip="30.30.30.30") + host = Host(connection=connection) + assert isinstance(host, EFIShellHost) diff --git a/mfd_host/base.py b/mfd_host/base.py index 5f9f9aa..33b2b9e 100644 --- a/mfd_host/base.py +++ b/mfd_host/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2025 Intel Corporation +# Copyright (C) 2025-2026 Intel Corporation # SPDX-License-Identifier: MIT """Module for Host.""" @@ -75,6 +75,7 @@ def __new__(cls, connection: "Connection", *args, **kwargs): from .windows import WindowsHost from .esxi import ESXiHost from .freebsd import FreeBSDHost + from .efishell import EFIShellHost os_name = connection.get_os_name() os_name_to_class = { @@ -82,6 +83,7 @@ def __new__(cls, connection: "Connection", *args, **kwargs): OSName.WINDOWS: WindowsHost, OSName.ESXI: ESXiHost, OSName.FREEBSD: FreeBSDHost, + OSName.EFISHELL: EFIShellHost, } if os_name not in os_name_to_class.keys(): diff --git a/mfd_host/efishell.py b/mfd_host/efishell.py new file mode 100644 index 0000000..f2767fb --- /dev/null +++ b/mfd_host/efishell.py @@ -0,0 +1,19 @@ +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: MIT +"""Module for Host for UEFI Shell OS.""" + +import logging + +from mfd_common_libs import add_logging_level, log_levels, os_supported +from mfd_typing import OSName + +from .base import Host + +logger = logging.getLogger(__name__) +add_logging_level(level_name="MODULE_DEBUG", level_value=log_levels.MODULE_DEBUG) + + +class EFIShellHost(Host): + """Class for EFI Shell host.""" + + __init__ = os_supported(OSName.EFISHELL)(Host.__init__) diff --git a/pyproject.toml b/pyproject.toml index 5d10286..e1469e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,5 +8,20 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] dependencies = { file = ["requirements.txt"] } +[project] +name = "mfd-host" +description = "MFD Host provides a unified Python interface for managing and interacting with Systems Under Tests (SUTs) AKA Hosts and their capabilities across multiple operating systems." +requires-python = ">=3.10, <3.14" +version = "2.3.0" +dynamic = ["dependencies"] +license-files = ["LICENSE.md", "AUTHORS.md"] +readme = {file = "README.md", content-type = "text/markdown"} + +[project.urls] +Homepage = "https://github.com/intel/mfd" +Repository = "https://github.com/intel/mfd-host" +Issues = "https://github.com/intel/mfd-host/issues" +Changelog = "https://github.com/intel/mfd-host/blob/main/CHANGELOG.md" + [tool.setuptools.packages.find] exclude = ["examples", "tests*", "sphinx-doc"] \ No newline at end of file diff --git a/tests/unit/test_mfd_host/test_base.py b/tests/unit/test_mfd_host/test_base.py index fa1967d..5c3eb73 100644 --- a/tests/unit/test_mfd_host/test_base.py +++ b/tests/unit/test_mfd_host/test_base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2025 Intel Corporation +# Copyright (C) 2025-2026 Intel Corporation # SPDX-License-Identifier: MIT """Tests for `mfd_host` package.""" @@ -15,8 +15,9 @@ from mfd_host import Host +from mfd_host.efishell import EFIShellHost from mfd_host.esxi import ESXiHost -from mfd_host.exceptions import HostConnectedOSNotSupported +from mfd_host.exceptions import HostConnectedOSNotSupported, HostConnectionTypeNotSupported from mfd_host.freebsd import FreeBSDHost from mfd_host.linux import LinuxHost from mfd_host.windows import WindowsHost @@ -57,10 +58,16 @@ def test_esxi_owner_created(self, mocker): assert isinstance(Host(connection=conn), ESXiHost) - def test_unsupported_os(self, mocker): + def test_efishell_host_created(self, mocker): conn = mocker.create_autospec(RPyCConnection) conn.get_os_name.return_value = OSName.EFISHELL + assert isinstance(Host(connection=conn), EFIShellHost) + + def test_unsupported_os(self, mocker): + conn = mocker.create_autospec(RPyCConnection) + conn.get_os_name.return_value = OSName.MELLANOX + with pytest.raises(HostConnectedOSNotSupported): Host(connection=conn) @@ -364,3 +371,86 @@ def test__are_interfaces_same(self, host): interface_info_0 = LinuxInterfaceInfo(name="eth0", pci_address=pci_address_0) assert Host._are_interfaces_same(interface, interface_info_0) assert not Host._are_interfaces_same(interface, interface_info_1) + + +class TestEFIShellHostProperties: + @pytest.fixture + def efishell_host(self, mocker): + connection = mocker.create_autospec(RPyCConnection) + connection.get_os_name.return_value = OSName.EFISHELL + host = Host(connection=connection) + + assert isinstance(host, EFIShellHost) + return host + + def test_network_property(self, efishell_host, mocker): + expected_network = object() + mocked_owner = mocker.patch("mfd_network_adapter.NetworkAdapterOwner", return_value=expected_network) + + assert efishell_host.network is expected_network + assert efishell_host.network is expected_network + mocked_owner.assert_called_once_with(connection=efishell_host.connection, cli_client=efishell_host.cli_client) + + def test_driver_property(self, efishell_host, mocker): + expected_driver = object() + mocked_driver = mocker.patch("mfd_package_manager.PackageManager", return_value=expected_driver) + + assert efishell_host.driver is expected_driver + assert efishell_host.driver is expected_driver + mocked_driver.assert_called_once_with(connection=efishell_host.connection) + + def test_event_property_unsupported_for_efishell(self, efishell_host): + with pytest.raises(HostConnectionTypeNotSupported): + _ = efishell_host.event + + def test_virtualization_property_unsupported_for_efishell(self, efishell_host): + with pytest.raises(HostConnectedOSNotSupported): + _ = efishell_host.virtualization + + def test_utils_property(self, efishell_host, mocker): + expected_utils = object() + mocked_utils = mocker.patch("mfd_host.feature.utils.BaseFeatureUtils", return_value=expected_utils) + + assert efishell_host.utils is expected_utils + assert efishell_host.utils is expected_utils + mocked_utils.assert_called_once_with(connection=efishell_host.connection, host=efishell_host) + + def test_memory_property(self, efishell_host, mocker): + expected_memory = object() + mocked_memory = mocker.patch("mfd_host.feature.memory.BaseFeatureMemory", return_value=expected_memory) + + assert efishell_host.memory is expected_memory + assert efishell_host.memory is expected_memory + mocked_memory.assert_called_once_with(connection=efishell_host.connection, host=efishell_host) + + def test_stats_property(self, efishell_host, mocker): + expected_stats = object() + mocked_stats = mocker.patch("mfd_host.base.BaseFeatureStats", return_value=expected_stats) + + assert efishell_host.stats is expected_stats + assert efishell_host.stats is expected_stats + mocked_stats.assert_called_once_with(connection=efishell_host.connection, host=efishell_host) + + def test_cpu_property(self, efishell_host, mocker): + expected_cpu = object() + mocked_cpu = mocker.patch("mfd_host.feature.cpu.BaseFeatureCPU", return_value=expected_cpu) + + assert efishell_host.cpu is expected_cpu + assert efishell_host.cpu is expected_cpu + mocked_cpu.assert_called_once_with(connection=efishell_host.connection, host=efishell_host) + + def test_service_property(self, efishell_host, mocker): + expected_service = object() + mocked_service = mocker.patch("mfd_host.feature.service.BaseFeatureService", return_value=expected_service) + + assert efishell_host.service is expected_service + assert efishell_host.service is expected_service + mocked_service.assert_called_once_with(connection=efishell_host.connection, host=efishell_host) + + def test_device_property(self, efishell_host, mocker): + expected_device = object() + mocked_device = mocker.patch("mfd_host.feature.device.BaseFeatureDevice", return_value=expected_device) + + assert efishell_host.device is expected_device + assert efishell_host.device is expected_device + mocked_device.assert_called_once_with(connection=efishell_host.connection, host=efishell_host)