Restructures tests/hardware/ so that fixture access is controlled by
directory layout — pytest only walks upward through conftest.py files,
so a PSU test physically cannot request fio/alm/nad.
Layout:
- tests/hardware/conftest.py (unchanged: PSU fixtures)
- tests/hardware/mum/conftest.py NEW: _require_mum (session autouse),
fio (session), nad (session),
alm (session), _reset_to_off
(function autouse)
- tests/hardware/mum/** MUM tests + swe5/ + swe6/
- tests/hardware/psu/** PSU-only tests
- tests/hardware/babylin/** deprecated BabyLIN E2E
What this removes (was duplicated before):
- 7 verbatim copies of the `fio` fixture
- 6 copies of the `alm` fixture
- 6 copies of the `_reset_to_off` autouse
- 9 inline `if config.interface.type != "mum": pytest.skip(...)` gates
What this changes by design:
- fio / alm / nad scope: module → session. NAD discovery happens once
per run instead of once per module. The helpers are immutable beyond
their constructor args, so sharing them is safe; per-test state is
reset by the autouse `_reset_to_off`.
- test_overvolt.py: `_park_at_nominal` is now `_reset_to_off`, which
cleanly overrides the conftest's LED-only version (PSU + LED reset).
- test_mum_alm_animation_generated.py keeps a local `_reset_to_off` +
`_force_off` so its "no AlmTester anywhere" demonstration is preserved
via fixture override; the local `nad` is also retained because it
uses the typed `AlmStatus.receive` API.
Docs:
- docs/24_test_wiring.md NEW — describes the three-layer fixture
topology, lifecycle sequence diagram, helper class wiring, and the
playbook for adding a new framework component.
- docs/05_architecture_overview.md: add MCF (mum conftest) node to the
Mermaid diagram + mention it in the components list.
- docs/19_frame_io_and_alm_helpers.md: replace the per-module
fixture-wiring example with a request-fixtures-by-name snippet plus
the override pattern.
- Path references swept across docs/02, docs/14, docs/18, docs/20,
docs/README to point at the new locations.
Verified: pytest --collect-only collects 93 tests with no errors;
30 unit tests and 10 mock-only smoke tests pass; fixture-per-test
output shows PSU tests cannot see fio/alm/nad.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
5.4 KiB
Python
144 lines
5.4 KiB
Python
"""Shared fixtures for the MUM hardware test suite.
|
|
|
|
WHY THIS FILE EXISTS
|
|
--------------------
|
|
Every test under ``tests/hardware/mum/**`` needs the same three things:
|
|
|
|
1. The session to be a MUM session (``config.interface.type == "mum"``).
|
|
2. A live ``FrameIO`` bound to the session ``lin`` + ``ldf``.
|
|
3. The ECU's live NAD, discovered by reading ``ALM_Status``.
|
|
|
|
Before this conftest existed, each test module repeated those fixtures
|
|
verbatim — 9 copies of ``fio``, 8 of ``alm``, 8 of ``_reset_to_off``,
|
|
and 8 inline ``if config.interface.type != "mum": pytest.skip(...)``
|
|
gates. They are all consolidated here.
|
|
|
|
SCOPE STRATEGY
|
|
--------------
|
|
``FrameIO``, ``AlmTester``, and the discovered ``nad`` are immutable
|
|
relative to a session connection. Keeping them at ``scope="session"``
|
|
means one NAD discovery per run instead of one per module, and a single
|
|
shared cache of LDF frame lookups across the whole suite. The only
|
|
function-scoped fixture is ``_reset_to_off`` — it MUST be per-test so
|
|
each test starts with the LED in a known state.
|
|
|
|
ACCESS CONTROL
|
|
--------------
|
|
This conftest is at ``tests/hardware/mum/`` deliberately: tests under
|
|
``tests/hardware/psu/`` and ``tests/hardware/babylin/`` cannot see
|
|
``fio``/``alm``/``nad`` because pytest only walks **upward** through
|
|
``conftest.py`` files. A PSU-only test that accidentally requests
|
|
``fio`` will fail at collection with "fixture not found" — that is
|
|
the access-control mechanism.
|
|
|
|
OVERRIDE NOTES
|
|
--------------
|
|
Two files override fixtures here for documented reasons:
|
|
|
|
- ``test_mum_alm_animation_generated.py`` keeps a local ``_reset_to_off``
|
|
+ ``_force_off`` so its "no AlmTester anywhere" demonstration stays
|
|
true. The local ``_reset_to_off`` shadows this conftest's.
|
|
|
|
- ``test_overvolt.py`` defines its own ``_reset_to_off`` that ALSO
|
|
parks the PSU at the nominal voltage. Its override is necessary —
|
|
without it, both autouse fixtures would run and the LED would be
|
|
toggled twice per test (harmless but wasteful).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from ecu_framework.config import EcuTestConfig
|
|
from ecu_framework.lin.base import LinInterface
|
|
|
|
from frame_io import FrameIO
|
|
from alm_helpers import AlmTester
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Session-wide gate
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def _require_mum(config: EcuTestConfig) -> None:
|
|
"""Single skip point for the whole MUM suite.
|
|
|
|
Replaces the inline ``if config.interface.type != "mum": pytest.skip(...)``
|
|
that used to live inside every ``fio`` fixture. ``autouse=True`` means
|
|
every test under ``tests/hardware/mum/**`` honors this without having
|
|
to opt in.
|
|
"""
|
|
if config.interface.type != "mum":
|
|
pytest.skip("interface.type must be 'mum' for tests under tests/hardware/mum/")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Shared MUM-suite fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.fixture(scope="session")
|
|
def fio(lin: LinInterface, ldf) -> FrameIO:
|
|
"""LDF-driven I/O over the session LIN connection.
|
|
|
|
Session-scoped because ``FrameIO`` only holds ``(lin, ldf)`` and caches
|
|
frame lookups — sharing it across the whole suite is a feature, not a
|
|
risk. Tests that need a fresh cache can build their own ``FrameIO(lin, ldf)``
|
|
inside the test body.
|
|
"""
|
|
return FrameIO(lin, ldf)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def nad(fio: FrameIO) -> int:
|
|
"""Live NAD reported by the ECU's ALM_Status frame.
|
|
|
|
Used as ``LIDFrom`` / ``LIDTo`` in unicast sends and as the slave
|
|
address bound into ``AlmTester``. Discovered once per session because
|
|
the address doesn't change while the ECU is powered.
|
|
|
|
Skips cleanly when:
|
|
- The ECU isn't responding (no ``ALM_Status`` within 1 s) — likely
|
|
a wiring or power problem.
|
|
- The reported NAD is outside the valid 0x01-0xFE range — usually
|
|
means auto-addressing hasn't been performed yet.
|
|
"""
|
|
decoded = fio.receive("ALM_Status", timeout=1.0)
|
|
if decoded is None:
|
|
pytest.skip("ECU not responding on ALM_Status — check wiring/power")
|
|
n = int(decoded["ALMNadNo"])
|
|
if not (0x01 <= n <= 0xFE):
|
|
pytest.skip(f"ECU reports invalid NAD {n:#x} — auto-addressing first")
|
|
return n
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def alm(fio: FrameIO, nad: int) -> AlmTester:
|
|
"""ALM_Node domain helper bound to the live NAD.
|
|
|
|
Session-scoped because ``AlmTester`` is stateless beyond ``(fio, nad)``;
|
|
per-test state hygiene is handled by ``_reset_to_off`` below, not by
|
|
rebuilding the helper.
|
|
"""
|
|
return AlmTester(fio, nad)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Per-test state reset
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_to_off(alm: AlmTester):
|
|
"""Drive the LED to OFF before AND after every test.
|
|
|
|
Function-scoped + autouse so that state cannot leak between tests.
|
|
The post-test ``force_off()`` runs even when the test body fails —
|
|
that is the contract: regardless of how the test exits, the next
|
|
one starts on a known baseline.
|
|
|
|
Override this fixture locally in a test module to change the reset
|
|
semantics (see the OVERRIDE NOTES in the module docstring).
|
|
"""
|
|
alm.force_off()
|
|
yield
|
|
alm.force_off()
|