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>
This commit is contained in:
Hosam-Eldin Mostafa 2026-05-15 01:24:12 +02:00
parent 08247f9321
commit 90be834102
6 changed files with 97 additions and 30 deletions

36
deprecated/README.md Normal file
View File

@ -0,0 +1,36 @@
# Deprecated
Historical artifacts kept for reference. Nothing in this directory is
imported by the live framework or test suite.
## Contents
| Path | Was | Why retired |
|---|---|---|
| `gen_lin_api.py` | `scripts/gen_lin_api.py` — LDF → Python generator | Replaced by hand-maintained `AlmTester` in `tests/hardware/alm_helpers.py`. The generated layer had one consumer (`AlmTester`) and adding signals to the generator was less ergonomic than adding methods to the facade. |
| `_generated/lin_api.py` | `tests/hardware/_generated/lin_api.py` — auto-emitted typed frame classes + enum classes | Same — its enum classes were inlined into `alm_helpers.py` so test bodies need only one import. |
## If you want to bring this back
The design is documented in
[`../docs/22_generated_lin_api.md`](../docs/22_generated_lin_api.md).
Reasons it might be worth reviving:
- A second ECU joins the framework and adding `<ecu>_helpers.py`
facades by hand becomes painful.
- The LDF starts churning fast enough that hand-syncing
`alm_helpers.py` enums against it is missing changes.
- The team grows to where mypy-in-CI becomes worth it and the
generated dataclass shape (with typed signal attributes) starts
paying for itself.
The generator is self-contained and parses a single LDF — re-runnable
in place from this directory:
```
python deprecated/gen_lin_api.py vendor/4SEVEN_color_lib_test.ldf \
--out tests/hardware/_generated/lin_api.py
```
(Restoring also requires adding `tests/hardware/_generated/__init__.py`
back, which is currently inside this folder.)

View File

@ -24,9 +24,9 @@ This document provides a high-level view of the frameworks components and how
- Project-wide fixtures — `tests/conftest.py` (config, lin, ldf, flash_ecu, rp)
- Hardware-suite fixtures — `tests/hardware/conftest.py` (session-scoped, autouse PSU; the bench is powered up once at session start and stays on for every test in the suite)
- MUM-suite fixtures — `tests/hardware/mum/conftest.py` (session-scoped `fio`, `nad`, `alm`; autouse `_require_mum` gate and `_reset_to_off` per-test reset). Tests outside `tests/hardware/mum/` cannot see these — that's how PSU-only and BabyLIN-only tests are kept from accidentally requesting MUM fixtures.
- Generic LDF I/O — `tests/hardware/frame_io.py` (`FrameIO` — send/receive/pack/unpack for any LDF frame plus raw-bus escape hatches). Stringly-typed at this layer (`fio.send("ALM_Req_A", …)`); typed wrappers live one level up.
- Generated LIN API — `tests/hardware/_generated/lin_api.py` (auto-emitted from an LDF by `scripts/gen_lin_api.py`; one class per frame, one `IntEnum` per encoding type with logical values). **Build-time, static.** Provides typed names so frame/signal typos become import errors. Design + generation rules in `docs/22_generated_lin_api.md`; relationship to `ecu_framework/lin/ldf.py` covered in [LDF Database vs Generated LIN API](#ldf-database-vs-generated-lin-api-two-layers-one-purpose).
- ALM domain helpers — `tests/hardware/alm_helpers.py` (`AlmTester` — force_off / wait_for_state / measure_animating_window / assert_pwm_*). Imports typed frames + enums from the generated layer; keeps the non-generatable semantics (polling cadences, PWM tolerances, cross-frame test patterns).
- Generic LDF I/O — `tests/hardware/frame_io.py` (`FrameIO` — send/receive/pack/unpack for any LDF frame plus raw-bus escape hatches). Stringly-typed at this layer (`fio.send("ALM_Req_A", …)`); tests use this **only** for cases the AlmTester facade doesn't model (schema introspection, raw-frame escape hatches, MUM-only `send_raw`).
- ALM domain helpers — `tests/hardware/alm_helpers.py` (`AlmTester` + hand-maintained `IntEnum` classes). The **single contributor-facing API** for ALM tests: per-signal readers (`read_led_state`, `read_voltage_status`, …), per-action senders (`send_color`, `send_config`, …), wait helpers, and cross-frame patterns (`assert_pwm_matches_rgb`). See [`19_frame_io_and_alm_helpers.md`](19_frame_io_and_alm_helpers.md).
- Retired layer (kept under [`deprecated/`](../deprecated/)) — `deprecated/gen_lin_api.py` + `deprecated/_generated/lin_api.py`. Was an LDF→Python generator that emitted typed frame/encoding classes; replaced by the hand-maintained surface in `alm_helpers.py`. See [`22_generated_lin_api.md`](22_generated_lin_api.md) for the historical design.
- PSU settle helpers — `tests/hardware/psu_helpers.py` (`wait_until_settled`, `apply_voltage_and_settle` — measured-rail-then-validation pattern shared by all voltage-changing tests)
- RGB→PWM calculator — `vendor/rgb_to_pwm.py` (consumed by `AlmTester.assert_pwm_*`)
- Test templates (not collected) — `tests/hardware/_test_case_template.py`, `tests/hardware/_test_case_template_psu_lin.py`
@ -49,15 +49,15 @@ flowchart TB
end
subgraph Hardware_Helpers [Hardware-test helpers]
FIO[tests/hardware/frame_io.py<br/>FrameIO &#40;stringly-typed&#41;]
GEN[tests/hardware/_generated/lin_api.py<br/>AlmReqA, AlmStatus, ... &#40;typed&#41;<br/>LedState, Mode, Update IntEnums]
ALM[tests/hardware/alm_helpers.py<br/>AlmTester]
ALM[tests/hardware/alm_helpers.py<br/>AlmTester + typed IntEnums<br/>&#40;contributor-facing API&#41;]
FIO[tests/hardware/frame_io.py<br/>FrameIO &#40;low-level, rarely used by tests&#41;]
RGB[vendor/rgb_to_pwm.py]
TPL[tests/hardware/_test_case_template*.py<br/>not collected]
end
subgraph Build_Time [Build-time tooling &#40;not run during tests&#41;]
GENSCRIPT[scripts/gen_lin_api.py]
subgraph Retired [Retired &#40;deprecated/&#41;]
GEN[deprecated/_generated/lin_api.py]
GENSCRIPT[deprecated/gen_lin_api.py]
end
subgraph Framework
@ -94,14 +94,11 @@ flowchart TB
CF --> BABY
CF --> FLASH
HCF --> POWER
T --> FIO
T --> GEN
T --> ALM
T -.rare, low-level.-> FIO
ALM --> FIO
ALM --> GEN
GEN -.calls at runtime.-> FIO
GENSCRIPT -.reads LDF once.-> LDFFILE
GENSCRIPT -.emits source.-> GEN
GENSCRIPT -.was: read LDF.-> LDFFILE
GENSCRIPT -.was: emit source.-> GEN
ALM --> RGB
TPL -.copy & edit.-> T
@ -146,6 +143,15 @@ flowchart TB
## LDF Database vs Generated LIN API: two layers, one purpose
> **Historical.** The generated LIN API is retired — see the banner in
> [`22_generated_lin_api.md`](22_generated_lin_api.md). The comparison
> below is kept for traceability: the *runtime* `LdfDatabase` path is
> still active (it's what `FrameIO` calls into); the *generated* path
> column describes a code path that now lives under
> [`deprecated/`](../deprecated/). Today, the analog of the right
> column is the hand-maintained `IntEnum` classes and method surface
> in `tests/hardware/alm_helpers.py`.
There are two pieces of code in this repo whose names both sound like
"the LDF module", and a recurring question is why both exist:
@ -181,14 +187,20 @@ have no path from a frame name to the actual byte layout for the currently
loaded LDF — and worse, the test bench would happily ship bytes encoded
against a *stale* LDF baked into the generator's last run.
### Three independent entry points, one wire
### Test-side entry points
A tester has three legitimate ways to drive the bus, all converging at
`LinInterface`. They are **parallel paths**, not a single nested stack —
`FrameIO` deliberately has no static dependency on `ecu_framework/lin/ldf.py`
(its only `ecu_framework` import is `LinInterface` + `LinFrame` from
`lin/base.py`), so the `ldf` it receives can be any object with a
`.frame(name)` method.
> **Updated.** This section originally described three parallel paths
> including the generated typed-wrapper layer. With the generator
> retired, the active picture is simpler: tests reach for `AlmTester`
> by default, and drop down to `FrameIO` only for schema introspection
> or other low-level needs. The diagram below is preserved for
> reference but the `gen_lin_api` node represents the now-retired
> path — see [`deprecated/`](../deprecated/).
`FrameIO` deliberately has no static dependency on
`ecu_framework/lin/ldf.py` (its only `ecu_framework` import is
`LinInterface` + `LinFrame` from `lin/base.py`), so the `ldf` it
receives can be any object with a `.frame(name)` method.
```mermaid
flowchart TB
@ -395,13 +407,13 @@ slot is where the team chose to keep things light.
- Add new bus adapters by implementing `LinInterface`
- Add new ECU-domain helpers next to `AlmTester` (e.g. `BcmTester`)
on top of `FrameIO` and the generated `lin_api.py`; share fixtures via
`tests/hardware/conftest.py`
on top of `FrameIO`; share fixtures via
`tests/hardware/conftest.py` (or a per-adapter conftest like
`tests/hardware/mum/conftest.py`)
- When the LDF changes (new frame, renamed signal, new encoding-type row):
re-run `python scripts/gen_lin_api.py <ldf-path>`, commit the updated
`tests/hardware/_generated/lin_api.py` alongside the LDF change. The
in-sync unit test in `tests/unit/test_generated_lin_api_in_sync.py`
fails CI if the two ever drift
add or update the corresponding `read_*` / `send_*` method (and, if
needed, a new `IntEnum`) in `tests/hardware/alm_helpers.py`. This is
the maintenance pact that replaced the retired generator
- Add new bench instrument controllers next to `OwonPSU` under
`ecu_framework/power/` or a new `ecu_framework/instruments/` package,
expose them as session-scoped fixtures

View File

@ -1,14 +1,33 @@
# Generated LIN API: One Helper per Frame, Enums per Encoding Type
> # ⚠ Retired layer — historical reference only
>
> The generator described here was retired when `AlmTester`
> (`tests/hardware/alm_helpers.py`) became the single contributor-facing
> surface. The relevant `IntEnum` classes (`LedState`, `Mode`, `Update`,
> `NVMStatus`, `VoltageStatus`, `ThermalStatus`) are now defined directly
> in `alm_helpers.py` and updated by hand when the LDF changes. The
> generator script and its last-emitted output live under
> [`../deprecated/`](../deprecated/) for reference; see
> [`../deprecated/README.md`](../deprecated/README.md) for the retirement
> rationale and the conditions under which reviving it would be worth it.
>
> Test bodies should reach for `AlmTester` methods (`alm.send_color`,
> `alm.read_led_state`, etc.) and the enums it exposes — see
> [`19_frame_io_and_alm_helpers.md`](19_frame_io_and_alm_helpers.md) for
> the active API.
>
> Everything below this banner describes the **previous** design, kept
> for traceability. Paths reference the original locations
> (`scripts/gen_lin_api.py`, `tests/hardware/_generated/lin_api.py`) —
> both files now live under `deprecated/`.
This document describes the design for `tests/hardware/_generated/lin_api.py`,
a file produced by `scripts/gen_lin_api.py` from an LDF. The goal is to push
every frame/signal/encoding-type fact out of hand-written test code and into a
single regenerated module that tests, helpers, and future ECU domains can
import from.
Nothing in this document has been committed yet — it is the design that the
generator will follow once approved.
## Why have a generated layer at all
`tests/hardware/frame_io.py` is already domain-agnostic: it takes a frame