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>
123 lines
5.1 KiB
Python
123 lines
5.1 KiB
Python
"""Hardware test for the Owon serial PSU.
|
|
|
|
Validates basic SCPI control via :class:`OwonPSU` against the
|
|
**session-managed** PSU (see :mod:`tests.hardware.conftest`):
|
|
|
|
- identification (`*IDN?`)
|
|
- decoded output state (`output?`)
|
|
- parsed measurement queries (`MEAS:VOLT?`, `MEAS:CURR?`)
|
|
|
|
The session-scoped autouse fixture in ``conftest.py`` opens the PSU
|
|
once at session start, parks it at the configured nominal voltage,
|
|
enables output, and leaves it that way for the whole session. This
|
|
test therefore does **not** toggle the output — calling
|
|
``set_output(False)`` would brown out the ECU and break every MUM
|
|
test that runs afterwards.
|
|
|
|
The four-phase template (SETUP / PROCEDURE / ASSERT / TEARDOWN) still
|
|
applies, but TEARDOWN is empty: the test reads-only and leaves the
|
|
bench exactly as it found it.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from ecu_framework.config import EcuTestConfig
|
|
from ecu_framework.power import OwonPSU
|
|
|
|
|
|
pytestmark = [pytest.mark.hardware]
|
|
|
|
|
|
def test_owon_psu_idn_and_measurements(config: EcuTestConfig, psu: OwonPSU, rp):
|
|
"""
|
|
Title: Owon PSU — IDN, output state, and parsed measurements
|
|
|
|
Description:
|
|
Read-only smoke test for the Owon PSU controller. Confirms the
|
|
bench PSU responds to ``*IDN?``, reports an enabled output
|
|
(the session fixture parked it there), and returns parseable
|
|
floats for ``MEAS:VOLT?`` and ``MEAS:CURR?``. Optionally
|
|
verifies the IDN matches the configured substring.
|
|
|
|
Requirements: REQ-PSU-001
|
|
|
|
Test Steps:
|
|
1. SETUP: none — the session fixture opened the port,
|
|
parked the PSU at nominal, and enabled output
|
|
before any test in this run started
|
|
2. PROCEDURE: query *IDN?, output?, MEAS:VOLT?, MEAS:CURR?
|
|
3. ASSERT: IDN is non-empty (and contains ``idn_substr`` if
|
|
configured); output is reported ON; both
|
|
measurements parse to floats
|
|
4. TEARDOWN: none — this test does not mutate bench state
|
|
|
|
Expected Result:
|
|
- IDN is non-empty (and contains ``idn_substr`` when set)
|
|
- ``output_is_on()`` returns True (bench is powered)
|
|
- ``measure_voltage_v()`` returns a float close to nominal
|
|
- ``measure_current_a()`` returns a float ≥ 0
|
|
"""
|
|
psu_cfg = config.power_supply
|
|
want_substr = psu_cfg.idn_substr
|
|
expected_v = float(psu_cfg.set_voltage) if psu_cfg.set_voltage else None
|
|
|
|
# ── PROCEDURE ─────────────────────────────────────────────────────
|
|
# All four queries are reads — they don't change the bench.
|
|
idn = psu.idn()
|
|
is_on = psu.output_is_on()
|
|
measured_v = psu.measure_voltage_v()
|
|
measured_i = psu.measure_current_a()
|
|
|
|
print(f"PSU IDN: {idn}")
|
|
print(f"Output ON: {is_on}")
|
|
print(f"Measured: V={measured_v}V, I={measured_i}A "
|
|
f"(nominal setpoint: {expected_v}V)")
|
|
|
|
# ── ASSERT ────────────────────────────────────────────────────────
|
|
# Record diagnostics before assertions so failure investigations
|
|
# have the captured values.
|
|
rp("psu_idn", idn)
|
|
rp("output_is_on", bool(is_on))
|
|
rp("measured_voltage_v", measured_v)
|
|
rp("measured_current_a", measured_i)
|
|
rp("expected_voltage_v", expected_v)
|
|
|
|
assert isinstance(idn, str) and idn, "*IDN? returned empty response"
|
|
if want_substr:
|
|
assert str(want_substr).lower() in idn.lower(), (
|
|
f"IDN does not contain expected substring: {want_substr!r}. "
|
|
f"Got: {idn!r}"
|
|
)
|
|
|
|
# The session fixture parked the PSU with output enabled. If this
|
|
# comes back False the bench is in an unexpected state — likely
|
|
# something in a preceding test mistakenly turned the output off.
|
|
assert is_on is True, (
|
|
f"PSU output is not ON ({is_on=!r}). The session fixture parks "
|
|
f"output=ON at start; some earlier test or the fixture itself "
|
|
f"may have disabled it. Tests must NOT call psu.set_output(False)."
|
|
)
|
|
|
|
# Measurements must parse — surfaces firmware-level response
|
|
# format mismatches as a clear failure.
|
|
assert measured_v is not None, (
|
|
"measure_voltage_v() returned no number; "
|
|
"check the firmware's MEAS:VOLT? response format"
|
|
)
|
|
assert measured_i is not None, (
|
|
"measure_current_a() returned no number; "
|
|
"check the firmware's MEAS:CURR? response format"
|
|
)
|
|
|
|
# Sanity: measured voltage should be within ±10% of the nominal
|
|
# setpoint when the bench is steady. Loose tolerance because PSU
|
|
# accuracy + meter noise + cable drop all stack up.
|
|
if expected_v is not None:
|
|
tol = 0.10 * expected_v
|
|
assert abs(measured_v - expected_v) <= tol, (
|
|
f"Measured {measured_v}V is outside ±10% of nominal {expected_v}V "
|
|
f"(tolerance ±{tol:.2f}V). Bench supply may be drifting or the "
|
|
f"PSU isn't connected to its measure points."
|
|
)
|