Source code for tests.conftest

# conftest.py
#
# SPDX-FileCopyrightText: <text>Copyright 2025 Arm Limited and/or its
# affiliates <open-source-office@arm.com></text>
#
# SPDX-License-Identifier: MIT

import os
import logging
import re
from pathlib import Path
import pytest
from datetime import datetime

from importlib import import_module, resources

from test_automation.utils.auto_platform_base import AutoTestPlatformBase
from test_automation.configs.config import Config

from test_automation.targets.registry import get_factory, DriverBundle

[docs] logger = logging.getLogger(__name__)
# ------------------------------ # Initial configuration + conditional plugin load # ------------------------------
[docs] def pytest_configure(config: pytest.Config) -> None: """ Configure pytest logging and conditionally load target plugins. :param config: Pytest configuration object. :returns: None. """ if config.getoption("--debug-logs"): config.option.log_cli = True config.option.log_cli_level = "DEBUG" config.option.log_cli_format = ( "%(asctime)s %(levelname)s %(name)s:%(lineno)d - %(message)s" ) config.option.log_cli_date_format = "%H:%M:%S" # Per-run logs root creation platform_opt = str(config.getoption("--platform") or "unknown").lower() logs_root = Path("logs") run_id = datetime.now().strftime("%Y%m%d-%H%M%S") run_dir = logs_root / f"{platform_opt}_{run_id}" run_dir.mkdir(parents=True, exist_ok=True) # Save paths for later hooks/fixtures to re-use config._arm_run_paths = { "run_id": run_id, "platform": platform_opt, "log_dir": run_dir, "prefix": platform_opt or "fvp_boot", } # Dynamically load the correct target plugin (only when needed) try: cfg = Config( path=str(Path(config.getoption("--config")).expanduser()), build_dir=config.getoption("--build-dir"), fvp_binary=config.getoption("--fvp-binary"), ) plat_key = str(config.getoption("--platform")).lower() plat_dict = cfg.get_platform(plat_key) ptype = str(plat_dict.get("type", "")).lower() if ptype == "fvp": import_module("test_automation.targets.fvp.plugin") except Exception as e: logger.debug("Skipping dynamic target plugin load: %s", e)
# ------------------------------ # Shared config/platform loaders # ------------------------------ @pytest.fixture(scope="session")
[docs] def cfg(pytestconfig: pytest.Config) -> Config: """ Load the YAML configuration once per test session. :param pytestconfig: Pytest configuration object. :returns: Parsed :class:`Config` instance. """ config_path = Path(pytestconfig.getoption("--config")).expanduser() build_dir = pytestconfig.getoption("--build-dir") fvp_binary = pytestconfig.getoption("--fvp-binary") logger.debug( "Loading YAML config from %s (build-dir=%r, fvp-binary=%r)", config_path, build_dir, fvp_binary, ) try: return Config( path=str(config_path), build_dir=build_dir, fvp_binary=fvp_binary ) except FileNotFoundError: pytest.skip(f"Config file not found: {config_path}")
@pytest.fixture(scope="session")
[docs] def auto_platform_config_data( pytestconfig: pytest.Config, cfg: Config ) -> dict: """ Resolve and return the selected platform dictionary from the YAML config. :param pytestconfig: Pytest configuration object. :param cfg: Parsed configuration object. :returns: Platform configuration dictionary. """ platform = str(pytestconfig.getoption("--platform")).lower() try: plat = cfg.get_platform(platform) # <- YAML platform dict logger.debug("Loaded platform keys: %s", list(plat.keys())) return plat except KeyError: names = ( [p.get("name") for p in cfg.platforms.values()] if hasattr(cfg, "platforms") else [] ) pytest.skip(f"Platform '{platform}' not found. Available: {names}")
# ------------------------------ # Core bundle: build/start/login/teardown once # ------------------------------ @pytest.fixture(scope="session")
[docs] def platform_bundle(pytestconfig, cfg: Config, auto_platform_config_data): """ Build the platform bundle once from the config. :param pytestconfig: Pytest configuration object. :param cfg: Parsed configuration object. :param auto_platform_config_data: Platform configuration dictionary. :returns: Yields an initialized :class:`DriverBundle`. """ plat = auto_platform_config_data ptype = str(plat.get("type", "")).lower() factory = get_factory(ptype) if not factory: pytest.skip(f"No platform plugin registered for type={ptype!r}") bundle: DriverBundle = factory(plat, cfg) # type-specific env export (e.g., FVP_BINARY) if callable(getattr(bundle, "export_env", None)): bundle.export_env(pytestconfig, plat) # Wire log destinations (args-only) before power-on rp = getattr(pytestconfig, "_arm_run_paths", None) if rp: # Telnet manager path mgr = getattr(bundle, "manager", None) if mgr and hasattr(mgr, "set_log_dir"): mgr.set_log_dir(str(Path(rp["log_dir"]).expanduser().resolve())) # Driver paths/prefix drv = bundle.driver if hasattr(drv, "log_dir"): drv.log_dir = Path(rp["log_dir"]).expanduser().resolve() drv.log_dir.mkdir(parents=True, exist_ok=True) if hasattr(drv, "log_prefix"): drv.log_prefix = rp["prefix"] else: # Fallback: compute per-run paths if pytest_configure didn't set them platform_opt = str( getattr(pytestconfig, "getoption", lambda *_: "unknown")( "--platform", "unknown" ) ).lower() run_id = datetime.now().strftime("%Y%m%d-%H%M%S") log_dir = Path("logs") / f"{platform_opt}-{run_id}" log_dir.mkdir(parents=True, exist_ok=True) rp = { "run_id": run_id, "platform": platform_opt, "log_dir": log_dir, "prefix": platform_opt or "fvp_boot", } setattr(pytestconfig, "_arm_run_paths", rp) logger.info( "Powering on platform type=%s name=%s", ptype, plat.get("name") ) getattr(bundle.driver, "init", lambda: None)() bundle.driver.start() logger.info("Waiting for platform to become ready…") bundle.driver.wait_ready() logger.info("Platform ready") # optional: login primary console (if provided) if bundle.manager and callable(getattr(bundle, "login_primary", None)): bundle.login_primary(bundle.manager, plat) try: yield bundle finally: logger.info("Powering off platform") try: bundle.driver.stop() finally: mgr = getattr(bundle, "manager", None) if mgr: for method in ("close_sessions", "stop_all"): try: getattr(mgr, method)() break except Exception: pass
# ------------------------------ # Thin fixtures used by tests # ------------------------------ @pytest.fixture(scope="session")
[docs] def platform_driver(platform_bundle): """ Provide the platform driver object for tests. :param platform_bundle: Initialized driver bundle. :returns: Driver instance from the bundle. """ return platform_bundle.driver
@pytest.fixture(scope="session")
[docs] def session_manager(platform_bundle): """ Provide the session/console manager for tests. :param platform_bundle: Initialized driver bundle. :returns: Session manager object. """ if platform_bundle.manager is None: pytest.skip("This platform does not expose a session manager") return platform_bundle.manager
# ------------------------------ # Utility / metadata fixtures # ------------------------------ @pytest.fixture(scope="session")
[docs] def platform_name(platform_base_obj, cfg: Config) -> str: """ Normalize the selected platform name to a canonical short name. :param platform_base_obj: Initialized :class:`AutoTestPlatformBase`. :param cfg: Parsed configuration object. :returns: Canonical name like ``'aspne'``. """ available = list(cfg.platforms.keys()) logger.debug("Available platforms in YAML: %s", available) selected_platform = platform_base_obj.platform or "" logger.debug("Selected platform: %s", selected_platform) if selected_platform not in available: logger.warning( "Selected platform %s not found in available platforms", selected_platform, ) return "unknown" if re.search(r"rd_aspen", selected_platform, re.IGNORECASE): return "rd_aspen" raise RuntimeError( f"Platform '{selected_platform}' is defined in config but " f"does not match expected patterns (rd_aspen)." )
@pytest.fixture(scope="session")
[docs] def platform_base_obj( session_manager, auto_platform_config_data, pytestconfig ) -> AutoTestPlatformBase: """ Construct the base platform helper object once per session. :param session_manager: Session/console manager instance. :param auto_platform_config_data: Platform configuration dictionary. :param pytestconfig: Pytest configuration object. :returns: Initialized :class:`AutoTestPlatformBase` instance. """ cli_platform = (pytestconfig.getoption("--platform") or "").lower() return AutoTestPlatformBase( session_manager, auto_platform_config_data, cli_platform )
# ------------------------------ # CLI options # ------------------------------
[docs] def pytest_addoption(parser) -> None: """ Register custom CLI options used by the test framework. :param parser: Pytest argument parser. :returns: None. """ default_cfg = ( resources.files("test_automation.configs") / "standalone_config.yaml" ) parser.addoption( "--platform", action="store", required=True, help="Platform key in the config (e.g., fvp_rd_aspen, fpga_rd_aspen)", ) parser.addoption( "--config", action="store", default=str(default_cfg), help="Specify the configuration file to load)", ) parser.addoption( "--build-dir", action="store", default=None, help="Optional directory to substitute for BUILD_IMAGE_DIR.", ) parser.addoption( "--fvp-binary", action="store", default=None, help="Optional path to substitute for FVP_BINARY.", ) parser.addoption( "--debug-logs", action="store_true", help="Enable live DEBUG logging for this run", )