The previous commit fixed the FrameIO/LDF diagram by labeling the
ldf-lookup edge as "duck-typed" without defining the term. This commit
adds a dedicated section explaining what duck typing means in this
codebase, why both architectural seams (FrameIO's ldf injection and the
lin fixture's adapter swap) rely on it, and the Python idioms behind it.
Content covers:
- The "walks like a duck" slogan and what it means in code: shape of
used methods is the contract, not the class.
- Example 1 — FrameIO and the untyped `ldf` parameter: shows the
contract (single .frame() call) and the absence of any
`from ecu_framework.lin.ldf import LdfDatabase`. Includes the
counter-example of what nominal typing would have meant for
module dependencies and testability.
- Example 2 — the lin fixture and adapter polymorphism: same idiom,
with LinInterface providing the nominal anchor.
- EAFP ("Easier to Ask Forgiveness than Permission") as the supporting
Python idiom, contrasted with LBYL.
- The trade-off section: implicit contracts and runtime-only errors,
and how the codebase mitigates them.
Cross-linked from 24_test_wiring.md's `lin` polymorphism-boundary
discussion so readers of either doc can navigate to the explanation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous ASCII pipeline implied a single linear stack from gen_lin_api
down through FrameIO down through ecu_framework/lin/ldf.py — and showed
a static dependency from FrameIO to that module. Both are wrong.
What the code actually says (tests/hardware/frame_io.py:34):
from ecu_framework.lin.base import LinFrame, LinInterface
That's the only ecu_framework import in FrameIO. The `ldf` constructor
parameter is duck-typed — FrameIO never imports LdfDatabase and would
work against any object exposing `.frame(name)`. So `frame_io → lin/ldf`
is an injected runtime call, not a module dependency.
Replace the linear ASCII diagram with a Mermaid parallel-paths diagram
that surfaces the three independent ways a tester can address a frame:
- gen_lin_api typed wrapper (compile-time name check)
- FrameIO stringly-typed I/O (with raw send_raw/receive_raw escape
hatches that don't touch the ldf object at all)
- LdfDatabase used directly (schema-only — pack to bytes, no I/O)
…all converging at LinInterface. The prose around the diagram is
rewritten to match: each path's affordance, and what concrete capability
is lost by removing any of the three.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
- Replace ecu_framework/config.py with ecu_framework/config/ package
(loader.py + __init__.py re-exports). Public surface unchanged — every
call site already uses 'from ecu_framework.config import ...' which
works identically for a module and a package. Brings config into the
same shape as lin/, power/, flashing/.
- Enrich loader.py with module-level design notes (pipeline diagram,
precedence rationale, "known wart" callout) and inline "why" comments:
the EcuTestConfig forward-reference quirk, the int(k, 0) hex-key trick,
_deep_update's mutate-in-place semantics, and the reason the in-memory
overrides are applied last despite being precedence #1.
- Add docs/23_config_loader_internals.md covering the merge semantics,
type-coercion philosophy, dataclass ordering quirks, PSU side-channel,
and the test-surface checklist (four places to touch when adding a
new config field).
- Fix the now-stale ecu_framework/config.py path in 01_run_sequence.md
and DEVELOPER_COMMIT_GUIDE.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a deeper "Contract (base)" section to 04_lin_interface_call_flow.md:
LinFrame field validation, LinInterface abstract vs default methods, the
list of concrete adapters / consumers, and a "How __post_init__ runs"
subsection explaining the dataclass-generated __init__ hook chain and the
inheritance caveat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous version described the pre-refactor flow only — no
hardware-suite conftest, no helper layer, no PSU resolver, no
settle-then-validate pattern, no junit_family note. Rewritten so it
reflects the current architecture without losing the original
sequence-diagram + text-flow shape.
What's new in the doc:
- Two-layer fixture model (project-wide vs hardware-suite) called
out at the top.
- Mermaid sequence diagram now shows the session-scoped autouse PSU
power-up, the helper layer (FrameIO / AlmTester / psu_helpers),
and the safe-off-on-close at session teardown.
- Text-flow split into PROJECT-WIDE / HARDWARE-SUITE / TEST-BODIES
sections; describes resolve_port's fallback chain and the
settle-then-validate behaviour of apply_voltage_and_settle.
- "Where information is fetched from" gains the LDF, rgb_to_pwm,
and per-machine PSU override paths.
- "Key components" split into project-wide / hardware-suite, listing
every helper and template file.
- Edge cases gain PSU-side entries: cross-platform port resolution,
the must-not list (no set_output(False), no close()),
apply_voltage_and_settle's timeout behaviour, and the
junit_family=legacy requirement for record_property round-trips.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the new layers introduced over the past several commits.
- docs/19_frame_io_and_alm_helpers.md (new): full reference for the
FrameIO and AlmTester helpers — three access levels (high/mid/low),
full API tables, fixture wiring, cookbook patterns, and §7
describing the four-phase SETUP/PROCEDURE/ASSERT/TEARDOWN test
pattern with the three template flavors plus a §7.4 link to the
PSU+LIN template.
- docs/14_power_supply.md: rewritten and expanded.
§3 cross-platform port resolution (Windows / WSL1 / WSL2 +
usbipd-win / Linux native compatibility table)
§4 auto-detection via idn_substr
§5 session-managed power: contract for tests, must-not list,
what changed in the existing tests
§6 the settle-then-validate pattern: two-delays table (PSU
bench-dependent vs ECU firmware-dependent), copy-paste
example, tuning guidance for ECU_VALIDATION_TIME_S
§6 PSU settling characterization (-m psu_settling)
§7 library API reference table + safe_off_on_close
§9 troubleshooting expanded with WSL2 usbipd-win + dialout
- docs/18_test_catalog.md: voltage-tolerance section refreshed for
the settle-then-validate shape, new "Hardware – PSU settling
(opt-in)" category, new §8 "Hardware-test infrastructure"
documenting conftest.py, frame_io.py, alm_helpers.py,
psu_helpers.py, and both templates.
- docs/05_architecture_overview.md: components list split into
framework core / hardware test layer / artifacts. Mermaid diagram
gained a Hardware-test helpers subgraph showing FrameIO,
AlmTester, rgb_to_pwm, and the templates. Data/control flow
summary describes the session-managed PSU and the helper layer.
- docs/15_report_properties_cheatsheet.md: PSU section split into
per-test (function-scoped rp) and module-scoped (testsuite
property) blocks; added psu_resolved_port, psu_resolved_idn,
psu_settled_s, validation_time_s.
- docs/README.md: links to the new doc 19.
- README.md, TESTING_FRAMEWORK_GUIDE.md: project-structure trees
expanded to show the full current layout — every file and
directory under tests/hardware/ (conftest, helpers, templates,
tests), tests/unit/, config/, docs/, scripts/, and vendor/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>