import os import pathlib import sys import typing as t import pytest from ecu_framework.config import load_config, EcuTestConfig from ecu_framework.lin.base import LinInterface from ecu_framework.lin.mock import MockBabyLinInterface try: from ecu_framework.lin.babylin import BabyLinInterface # type: ignore except Exception: BabyLinInterface = None # type: ignore try: from ecu_framework.lin.mum import MumLinInterface # type: ignore except Exception: MumLinInterface = None # type: ignore WORKSPACE_ROOT = pathlib.Path(__file__).resolve().parents[1] @pytest.fixture(scope="session") def config() -> EcuTestConfig: cfg = load_config(str(WORKSPACE_ROOT)) return cfg @pytest.fixture(scope="session") def lin(config: EcuTestConfig) -> t.Iterator[LinInterface]: iface_type = config.interface.type if iface_type == "mock": lin = MockBabyLinInterface(bitrate=config.interface.bitrate, channel=config.interface.channel) elif iface_type == "babylin": if BabyLinInterface is None: pytest.skip("BabyLin interface not available in this environment") lin = BabyLinInterface( dll_path=config.interface.dll_path, bitrate=config.interface.bitrate, channel=config.interface.channel, node_name=config.interface.node_name, func_names=config.interface.func_names, sdf_path=config.interface.sdf_path, schedule_nr=config.interface.schedule_nr, ) elif iface_type == "mum": if MumLinInterface is None: pytest.skip("MUM interface not available in this environment") if not config.interface.host: pytest.skip("interface.host is required when interface.type == 'mum'") # Merge frame lengths: LDF (if any) provides defaults; YAML # `frame_lengths` overrides on a per-id basis. merged_lengths: dict = {} if config.interface.ldf_path: try: from ecu_framework.lin.ldf import LdfDatabase merged_lengths.update(LdfDatabase(config.interface.ldf_path).frame_lengths()) except Exception as e: # Don't fail connect just because the LDF couldn't be parsed — # the `ldf` fixture will surface the real error if a test asks. sys.stderr.write(f"[lin fixture] LDF load failed, ignoring: {e!r}\n") if config.interface.frame_lengths: merged_lengths.update(config.interface.frame_lengths) lin = MumLinInterface( host=config.interface.host, lin_device=config.interface.lin_device, power_device=config.interface.power_device, baudrate=config.interface.bitrate, boot_settle_seconds=config.interface.boot_settle_seconds, frame_lengths=merged_lengths or None, ) else: raise RuntimeError(f"Unknown interface type: {iface_type}") lin.connect() yield lin lin.disconnect() @pytest.fixture(scope="session") def ldf(config: EcuTestConfig): """Session-scoped LDF database loaded from `interface.ldf_path`. Tests that depend on LDF-defined frames request this fixture; tests that don't need it can ignore it. Skips with a clear message if `ldf_path` isn't set or the file isn't parseable. """ if not config.interface.ldf_path: pytest.skip("interface.ldf_path is not set in config") # Resolve relative paths against the workspace root for convenience. p = pathlib.Path(config.interface.ldf_path) if not p.is_absolute(): p = (WORKSPACE_ROOT / p).resolve() if not p.is_file(): pytest.skip(f"LDF file not found: {p}") try: from ecu_framework.lin.ldf import LdfDatabase except Exception as e: pytest.skip(f"ldfparser not available: {e!r}") return LdfDatabase(p) @pytest.fixture(scope="session", autouse=False) def flash_ecu(config: EcuTestConfig, lin: LinInterface) -> None: if not config.flash.enabled: pytest.skip("Flashing disabled in config") # Lazy import to avoid dependency during mock-only runs from ecu_framework.flashing import HexFlasher if not config.flash.hex_path: pytest.skip("No HEX path provided in config") flasher = HexFlasher(lin) ok = flasher.flash_hex(config.flash.hex_path) if not ok: pytest.fail("ECU flashing failed") @pytest.fixture def rp(record_property: "pytest.RecordProperty"): """Convenience reporter: attaches a key/value as a test property and echoes to captured output. Usage in tests: def test_something(rp): rp("key", value) """ def _rp(key: str, value): # Attach property (pytest-html will show in Properties table) record_property(str(key), value) # Echo to captured output for quick scanning in report details try: print(f"[prop] {key}={value}") except Exception: pass return _rp