15 Commits

Author SHA1 Message Date
90be834102 refactor: retire LIN API generator (move to deprecated/)
With AlmTester now the single contributor-facing API, the generator at
``scripts/gen_lin_api.py`` and its output at
``tests/hardware/_generated/`` have no live consumer — the previous
commit inlined the enum classes they used to provide into
``tests/hardware/alm_helpers.py``.

Moves both to ``deprecated/`` rather than deleting outright. The
deprecated layout is self-describing:

    deprecated/
      README.md          — retirement rationale + revival instructions
      gen_lin_api.py     — was scripts/gen_lin_api.py
      _generated/
        __init__.py
        lin_api.py       — last-emitted typed frame classes + IntEnums

A note in deprecated/README.md spells out the conditions that would
make reviving the generator worthwhile (a second ECU joins, the LDF
churns fast enough to make hand-syncing miss changes, mypy-in-CI gets
adopted) and the exact command to regenerate.

Docs:

- 22_generated_lin_api.md now leads with a retired-layer banner. The
  body is preserved as the design-of-record for the historical layer.
- 05_architecture_overview.md gets a refreshed "Test-side layering"
  Mermaid (AlmTester → FrameIO → LinInterface) plus a "retired layer"
  bullet pointing at deprecated/. The "Three independent entry points"
  section is annotated rather than removed — the gen_lin_api path
  there is now historical reference.

Verified: pytest --collect-only collects 87 tests; 40 unit + mock
tests still pass. The retirement is invisible to the live framework.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:24:12 +02:00
08247f9321 refactor(tests): AlmTester as the single contributor-facing API
Extends ``tests/hardware/alm_helpers.py`` into the full surface that
hardware tests use, so contributors write intent (``alm.send_color``,
``alm.read_led_state``, ``alm.wait_for_led_on``) and never touch
``fio.send("ALM_Req_A", AmbLight…=…)`` or LDF schema details.

What landed:

- AlmTester gains ~16 methods:
    read_nad, read_voltage_status, read_thermal_status, read_nvm_status,
    read_sig_comm_err, read_ntc_kelvin, read_ntc_celsius, read_pwm,
    read_pwm_wo_comp, send_color, send_color_broadcast, save_color,
    apply_saved_color, discard_saved_color, send_config, plus
    wait_for_led_on / wait_for_led_off / wait_for_animating wrappers.
- The six IntEnum classes that ALM tests need (LedState, Mode, Update,
  NVMStatus, VoltageStatus, ThermalStatus) are defined directly in
  alm_helpers.py — tests get them via `from alm_helpers import …`.
- All ALM test files migrated:
    test_mum_alm_animation.py, test_mum_alm_cases.py, test_overvolt.py,
    swe5/test_anm_management.py, swe5/test_com_management.py
    each now go through AlmTester for every common pattern.
- swe6/test_com_management.py: stays on `fio` (these tests probe
  schema features not in the current production LDF and skip when
  the LDF doesn't declare them) — change limited to LedState enum.
- test_mum_alm_animation_generated.py deleted — its "no-AlmTester"
  demonstration loses its point now that AlmTester is the
  recommended path.
- docs/19_frame_io_and_alm_helpers.md reframed: AlmTester is the
  contributor surface; FrameIO is implementation detail. New API
  reference + Cookbook examples + a note that the maintenance pact
  is "LDF changes → AlmTester updates".

Verified: pytest --collect-only collects 87 tests cleanly; 40 unit
+ mock smoke tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:23:52 +02:00
7cf74312d6 feat(tests): add build-time generated LIN API + design doc
Introduces a typed layer between the LDF and hardware tests so frame /
signal / enum-value typos become import errors instead of runtime
KeyErrors. This complements the runtime ``LdfDatabase`` in
``ecu_framework/lin/ldf.py`` rather than replacing it.

- scripts/gen_lin_api.py: LDF → Python generator. Reads an LDF via
  ldfparser and emits one ``IntEnum`` per logical-valued
  Signal_encoding_types block, one class per pure-physical encoding
  type, and one class per frame with NAME / FRAME_ID / LENGTH /
  PUBLISHER / SIGNALS / SIGNAL_LAYOUT plus ``send`` / ``receive`` /
  ``read_signal`` classmethods that delegate to a caller-supplied
  ``FrameIO``. Output starts with a "DO NOT EDIT — re-run" header and
  the source-LDF SHA-256 prefix for traceability.
- tests/hardware/_generated/__init__.py + lin_api.py: the generated
  output for vendor/4SEVEN_color_lib_test.ldf. Already consumed by
  tests/hardware/mum/test_mum_alm_animation_generated.py to demonstrate
  the "no AlmTester anywhere" pattern.
- docs/22_generated_lin_api.md: design doc covering the generation
  rules, the build-time-vs-runtime layering with LdfDatabase, the
  rationale for keeping AlmTester-style helpers above this layer, and
  worked before/after examples.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:48:12 +02:00
8fa4cf0be1 refactor(tests): layer fixtures by adapter type (mum/psu/babylin)
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>
2026-05-14 19:43:09 +02:00
53f27faa31 Migrate tests from the excel sheets 2026-05-12 01:05:55 +02:00
11b5402b14 tests/hardware: add copyable, heavily-commented test templates
Two starting-point files for new hardware tests. Leading underscore
in the filenames keeps pytest from collecting them.

- _test_case_template.py — for ALM_Node-touching MUM tests.
  Three flavors with full SETUP / PROCEDURE / ASSERT / TEARDOWN
  section markers:
    A) minimal: relies on the autouse _reset_to_off (LED OFF
       baseline) — no per-test setup/teardown
    B) with isolation: try/finally pattern for tests that mutate
       persistent ECU state (e.g. ConfigFrame)
    C) single-signal probe: fio.read_signal one-shot

  Inline comments explain pytest fundamentals (fixture, scope,
  autouse, yield, rp), the four-phase pattern, and the
  must/must-not contract.

- _test_case_template_psu_lin.py — for tests that drive the PSU
  AND observe the LIN bus (over/undervoltage tolerance, brown-out,
  supply transients). Three flavors:
    A) overvoltage: apply OV via apply_voltage_and_settle, single
       status read after validation hold, assert OverVoltage
    B) undervoltage: symmetric for UV
    C) parametrized voltage sweep
  Documents the three-layer safety guarantee (session
  safe_off_on_close / autouse _park_at_nominal / per-test
  try/finally) and the rule that tests never call set_output(False)
  or close() — the session fixture owns the PSU lifecycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:02:13 +02:00
29a7a44c8b tests/hardware: settle-then-validate PSU helpers + voltage-tolerance tests
Voltage-changing tests can't sleep a fixed amount and assume the
rail is there — Owon settling is bench-dependent and typically
asymmetric (up-step ≠ down-step). New shared helpers and tests use
the rail's measured value to drive timing.

- tests/hardware/psu_helpers.py:
    wait_until_settled(psu, target_v, ...)
        polls measure_voltage_v() until within tol, returns
        (elapsed_s, trace) or (None, trace) on timeout
    apply_voltage_and_settle(psu, target_v, validation_time, ...)
        composite: set setpoint → wait until measured matches →
        sleep validation_time so the firmware-side observer can
        detect and republish status. Raises on settle timeout.
    downsample_trace, plus DEFAULT_VOLTAGE_TOL_V (0.10),
    DEFAULT_POLL_INTERVAL_S (0.05), DEFAULT_SETTLE_TIMEOUT_S (10.0),
    DEFAULT_VALIDATION_TIME_S (1.0).

- test_overvolt.py: voltage-tolerance suite. Each test (over,
  under, parametrized sweep) uses apply_voltage_and_settle for the
  procedure, the autouse _park_at_nominal fixture (also via the
  helper), and a single deterministic ALM_Status read after the
  validation hold instead of polling-the-bus.

- test_psu_voltage_settling.py: characterization test, opt-in via
  the new psu_settling marker. Walks four (start_v, target_v)
  transitions and records settling_time_s + voltage_trace per case.
  Values feed directly into test_overvolt's ECU_VALIDATION_TIME_S
  budgeting.

- pytest.ini:
    junit_family = legacy  → record_property() entries now actually
        appear in reports/junit.xml (the default xunit2 silently
        dropped them with a collect-time warning, breaking the
        conftest plugin's metadata round-trip)
    psu_settling marker registered

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:01:49 +02:00
eac662b139 tests/hardware: session-scoped PSU fixture so the bench stays powered
On benches where the Owon PSU powers the ECU, every per-file PSU
fixture that closed the port (sending 'output 0' on close) browned
out the bench between modules — every MUM test that ran after a
closed PSU connection failed with "ECU not responding".

New tests/hardware/conftest.py provides three session-scoped
fixtures:

- _psu_or_none: tolerant. Opens the Owon PSU once via resolve_port,
  parks at config.power_supply.set_voltage / set_current, enables
  output. Yields the live OwonPSU or None. Closes (with
  safe_off_on_close=True) at session end — the bench ends safely
  de-energized.

- _psu_powers_bench: autouse=True. Realizes _psu_or_none so even
  tests that don't request `psu` by name benefit from the
  session-level power-up. No-op if PSU isn't configured.

- psu: public. Skips cleanly when the PSU isn't reachable.

Contract for tests:
  - request `psu` if you need to read measurements or change voltage
  - restore nominal voltage in your finally block
  - MUST NOT call psu.set_output(False) (would brown out the bench)
  - MUST NOT call psu.close() (the session fixture owns it)

test_owon_psu.py becomes read-only:
  - removed the local module-scoped psu fixture
  - removed the set_output toggle (would have killed the session)
  - now validates IDN, output_is_on(), and parsed measurements
    against the always-on PSU. Renamed to
    test_owon_psu_idn_and_measurements to reflect the new shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:01:01 +02:00
f5a4ba532b tests/hardware: add FrameIO + AlmTester helper layer
Splits hardware-test concerns into two reusable modules and rebuilds
test_mum_alm_animation.py on top of them.

- frame_io.py — generic LDF-driven I/O class. Knows nothing about
  ALM. Three access levels:
    high: send/receive/read_signal by frame and signal name
    mid:  pack/unpack — bytes ↔ signals without I/O
    low:  send_raw/receive_raw — bypass the LDF entirely
  Plus introspection: frame, frame_id, frame_length. Frame lookups
  are cached per FrameIO instance.

- alm_helpers.py — ALM_Node domain helpers built on FrameIO.
  AlmTester class bound to (fio, nad) exposes:
    force_off, read_led_state, wait_for_state,
    measure_animating_window, assert_pwm_matches_rgb,
    assert_pwm_wo_comp_matches_rgb
  Plus pure utilities (ntc_kelvin_to_celsius, pwm_within_tol) and
  the LED-state / pacing / PWM-tolerance constants. PWM assertions
  use vendor/rgb_to_pwm.py (compute_pwm) at the runtime
  Tj_Frame_NTC temperature.

- test_mum_alm_animation.py rewritten:
    * fio + alm fixtures replace the previous dict-based _ctx
    * SETUP / PROCEDURE / ASSERT / TEARDOWN section markers
    * test_mode1_fade now wraps its ConfigFrame change in
      try/finally so EnableCompensation is restored even on
      assertion failure (was leaking state into later tests)
    * test_disable_compensation_pwm_wo_comp uses the four-phase
      pattern explicitly

Sibling imports work because pytest's default rootdir mode puts the
test file's directory on sys.path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:00:36 +02:00
582764d410 Mark legacy BabyLIN adapter as deprecated across code and docs
The MUM (Melexis Universal Master) adapter is the current default; the
BabyLIN SDK adapter is retained only for backward compatibility with
existing rigs.

Code:
- Emit DeprecationWarning when BabyLinInterface is instantiated and
  when tests/conftest.py routes interface.type=='babylin' to it.
- Update module/class docstrings in ecu_framework/{__init__,config,
  lin/__init__,lin/babylin}.py to label BabyLIN-specific fields and
  paths as deprecated.

Config / scripts / pytest:
- pytest.ini: relabel the babylin marker as deprecated.
- config/{babylin.example,examples,test_config}.yaml: add deprecation
  banners and field comments.
- scripts/99-babylin.rules and scripts/pi_install.sh: annotate the
  udev-rule install block as legacy-only.

Documentation:
- TESTING_FRAMEWORK_GUIDE.md, docs/08_babylin_internals.md, and
  vendor/README.md: prepend explicit "DEPRECATED" banners.
- docs/{README,01,02,04,05,07,09,10,12,13,14,15,18,DEVELOPER_COMMIT_
  GUIDE}.md: relabel "legacy" to "deprecated" where babylin is
  mentioned, present MUM as the primary path, and steer new work
  toward the MUM examples.

No tests, configs, or modules were deleted; existing BabyLIN setups
keep working but now produce a clear DeprecationWarning at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:32:24 +02:00
d268d845ce add ldf parser 2026-04-29 00:56:07 +02:00
a10187844a add ldf parser 2026-04-29 00:55:53 +02:00
b8f52bea39 Add MUM support in the testing framework 2026-04-28 23:37:53 +02:00
e552e9a8e9 Add Owon power supply library, and test cases 2025-10-24 23:24:54 +02:00
17ae041792 ECU framework: docs, reporting plugin (HTML metadata + requirements JSON + CI summary), .gitignore updates 2025-10-20 20:21:05 +02:00