From 90be834102f036e359e6bc612c314f655871526a Mon Sep 17 00:00:00 2001 From: Hosam-Eldin Mostafa Date: Fri, 15 May 2026 01:24:12 +0200 Subject: [PATCH] refactor: retire LIN API generator (move to deprecated/) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- deprecated/README.md | 36 ++++++++++ .../_generated/__init__.py | 0 .../_generated/lin_api.py | 0 {scripts => deprecated}/gen_lin_api.py | 0 docs/05_architecture_overview.md | 66 +++++++++++-------- docs/22_generated_lin_api.md | 25 ++++++- 6 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 deprecated/README.md rename {tests/hardware => deprecated}/_generated/__init__.py (100%) rename {tests/hardware => deprecated}/_generated/lin_api.py (100%) rename {scripts => deprecated}/gen_lin_api.py (100%) diff --git a/deprecated/README.md b/deprecated/README.md new file mode 100644 index 0000000..9969586 --- /dev/null +++ b/deprecated/README.md @@ -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 `_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.) diff --git a/tests/hardware/_generated/__init__.py b/deprecated/_generated/__init__.py similarity index 100% rename from tests/hardware/_generated/__init__.py rename to deprecated/_generated/__init__.py diff --git a/tests/hardware/_generated/lin_api.py b/deprecated/_generated/lin_api.py similarity index 100% rename from tests/hardware/_generated/lin_api.py rename to deprecated/_generated/lin_api.py diff --git a/scripts/gen_lin_api.py b/deprecated/gen_lin_api.py similarity index 100% rename from scripts/gen_lin_api.py rename to deprecated/gen_lin_api.py diff --git a/docs/05_architecture_overview.md b/docs/05_architecture_overview.md index ddc6352..354d2f3 100644 --- a/docs/05_architecture_overview.md +++ b/docs/05_architecture_overview.md @@ -24,9 +24,9 @@ This document provides a high-level view of the framework’s 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
FrameIO (stringly-typed)] - GEN[tests/hardware/_generated/lin_api.py
AlmReqA, AlmStatus, ... (typed)
LedState, Mode, Update IntEnums] - ALM[tests/hardware/alm_helpers.py
AlmTester] + ALM[tests/hardware/alm_helpers.py
AlmTester + typed IntEnums
(contributor-facing API)] + FIO[tests/hardware/frame_io.py
FrameIO (low-level, rarely used by tests)] RGB[vendor/rgb_to_pwm.py] TPL[tests/hardware/_test_case_template*.py
not collected] end - subgraph Build_Time [Build-time tooling (not run during tests)] - GENSCRIPT[scripts/gen_lin_api.py] + subgraph Retired [Retired (deprecated/)] + 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 `, 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 diff --git a/docs/22_generated_lin_api.md b/docs/22_generated_lin_api.md index 9b2b027..1f143b4 100644 --- a/docs/22_generated_lin_api.md +++ b/docs/22_generated_lin_api.md @@ -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