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) - 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) - 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. - 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. - 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`).
- 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` + 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).
- 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). - 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) - 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_*`) - 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` - 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 end
subgraph Hardware_Helpers [Hardware-test helpers] subgraph Hardware_Helpers [Hardware-test helpers]
FIO[tests/hardware/frame_io.py<br/>FrameIO &#40;stringly-typed&#41;] ALM[tests/hardware/alm_helpers.py<br/>AlmTester + typed IntEnums<br/>&#40;contributor-facing API&#41;]
GEN[tests/hardware/_generated/lin_api.py<br/>AlmReqA, AlmStatus, ... &#40;typed&#41;<br/>LedState, Mode, Update IntEnums] FIO[tests/hardware/frame_io.py<br/>FrameIO &#40;low-level, rarely used by tests&#41;]
ALM[tests/hardware/alm_helpers.py<br/>AlmTester]
RGB[vendor/rgb_to_pwm.py] RGB[vendor/rgb_to_pwm.py]
TPL[tests/hardware/_test_case_template*.py<br/>not collected] TPL[tests/hardware/_test_case_template*.py<br/>not collected]
end end
subgraph Build_Time [Build-time tooling &#40;not run during tests&#41;] subgraph Retired [Retired &#40;deprecated/&#41;]
GENSCRIPT[scripts/gen_lin_api.py] GEN[deprecated/_generated/lin_api.py]
GENSCRIPT[deprecated/gen_lin_api.py]
end end
subgraph Framework subgraph Framework
@ -94,14 +94,11 @@ flowchart TB
CF --> BABY CF --> BABY
CF --> FLASH CF --> FLASH
HCF --> POWER HCF --> POWER
T --> FIO
T --> GEN
T --> ALM T --> ALM
T -.rare, low-level.-> FIO
ALM --> FIO ALM --> FIO
ALM --> GEN GENSCRIPT -.was: read LDF.-> LDFFILE
GEN -.calls at runtime.-> FIO GENSCRIPT -.was: emit source.-> GEN
GENSCRIPT -.reads LDF once.-> LDFFILE
GENSCRIPT -.emits source.-> GEN
ALM --> RGB ALM --> RGB
TPL -.copy & edit.-> T TPL -.copy & edit.-> T
@ -146,6 +143,15 @@ flowchart TB
## LDF Database vs Generated LIN API: two layers, one purpose ## 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 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: "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 loaded LDF — and worse, the test bench would happily ship bytes encoded
against a *stale* LDF baked into the generator's last run. 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 > **Updated.** This section originally described three parallel paths
`LinInterface`. They are **parallel paths**, not a single nested stack — > including the generated typed-wrapper layer. With the generator
`FrameIO` deliberately has no static dependency on `ecu_framework/lin/ldf.py` > retired, the active picture is simpler: tests reach for `AlmTester`
(its only `ecu_framework` import is `LinInterface` + `LinFrame` from > by default, and drop down to `FrameIO` only for schema introspection
`lin/base.py`), so the `ldf` it receives can be any object with a > or other low-level needs. The diagram below is preserved for
`.frame(name)` method. > 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 ```mermaid
flowchart TB 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 bus adapters by implementing `LinInterface`
- Add new ECU-domain helpers next to `AlmTester` (e.g. `BcmTester`) - Add new ECU-domain helpers next to `AlmTester` (e.g. `BcmTester`)
on top of `FrameIO` and the generated `lin_api.py`; share fixtures via on top of `FrameIO`; share fixtures via
`tests/hardware/conftest.py` `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): - 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 add or update the corresponding `read_*` / `send_*` method (and, if
`tests/hardware/_generated/lin_api.py` alongside the LDF change. The needed, a new `IntEnum`) in `tests/hardware/alm_helpers.py`. This is
in-sync unit test in `tests/unit/test_generated_lin_api_in_sync.py` the maintenance pact that replaced the retired generator
fails CI if the two ever drift
- Add new bench instrument controllers next to `OwonPSU` under - Add new bench instrument controllers next to `OwonPSU` under
`ecu_framework/power/` or a new `ecu_framework/instruments/` package, `ecu_framework/power/` or a new `ecu_framework/instruments/` package,
expose them as session-scoped fixtures expose them as session-scoped fixtures

View File

@ -1,14 +1,33 @@
# Generated LIN API: One Helper per Frame, Enums per Encoding Type # 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`, 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 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 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 single regenerated module that tests, helpers, and future ECU domains can
import from. 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 ## Why have a generated layer at all
`tests/hardware/frame_io.py` is already domain-agnostic: it takes a frame `tests/hardware/frame_io.py` is already domain-agnostic: it takes a frame