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
# ------------------------------
# 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",
)