Hosam-Eldin Mostafa a3c50eabf2 docs(architecture): add Duck typing section with FrameIO and lin-fixture examples
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>
2026-05-14 20:30:30 +02:00
2026-05-12 01:05:55 +02:00

ECU Tests Framework

Python-based ECU testing framework built on pytest, with a pluggable LIN communication layer (Mock and MUM, with the deprecated BabyLIN adapter retained for backward compatibility), configuration via YAML, and enhanced HTML/XML reporting with rich test metadata.

Heads-up: the BabyLIN adapter is deprecated. New tests and deployments should target MUM. BabyLIN is documented below only so existing setups can keep running while they migrate.

Highlights

  • MUM (Melexis Universal Master) adapter — current default for hardware tests; networked LIN master with built-in power control
  • Mock LIN adapter for fast, hardware-free development
  • BabyLIN adapter (DEPRECATED) using the vendor SDK's Python wrapper
  • Hex flashing scaffold you can wire to UDS
  • Rich pytest fixtures and example tests
  • Self-contained HTML report with Title, Requirements, Steps, and Expected Results extracted from test docstrings
  • JUnit XML report for CI/CD
  • Using the framework (common runs, markers, CI, Pi): docs/12_using_the_framework.md
  • Plugin overview (reporting, hooks, artifacts): docs/11_conftest_plugin_overview.md
  • Power supply (Owon) usage and troubleshooting: docs/14_power_supply.md
  • Report properties cheatsheet (standard keys): docs/15_report_properties_cheatsheet.md
  • MUM source scripts (vendor reference): vendor/automated_lin_test/README.md

TL;DR quick start (copy/paste)

Mock (no hardware):

python -m venv .venv; .\.venv\Scripts\Activate.ps1; pip install -r requirements.txt; pytest -m "not hardware" -v

Hardware via MUM (current default):

# 1. Install Melexis 'pylin' and 'pymumclient' (see vendor/automated_lin_test/install_packages.sh)
# 2. Make sure the MUM is reachable (default IP 192.168.7.2)
$env:ECU_TESTS_CONFIG = ".\config\mum.example.yaml"; pytest -m "hardware and mum" -v

Hardware via BabyLIN (DEPRECATED — kept for existing rigs only):

# Place BabyLIN_library.py and native libs under .\vendor per vendor/README.md first
$env:ECU_TESTS_CONFIG = ".\config\babylin.example.yaml"; pytest -m "hardware and babylin" -v

Quick start (Windows PowerShell)

  1. Create a virtual environment and install dependencies
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
  1. Run the mock test suite (default interface)
python.exe -m pytest -m "not hardware" -v
  1. View the reports
  • HTML: reports/report.html
  • JUnit XML: reports/junit.xml

Tip: You can change output via --html and --junitxml CLI options.

Quick start (WSL on Windows)

Use this approach when running from Windows Subsystem for Linux instead of PowerShell.

1. Open a WSL terminal and navigate to the project

Clone or access the repo from within WSL. If the project lives on the Windows filesystem (e.g. C:\Users\you\ecu-tests), it is available at:

cd /mnt/c/Users/<your-username>/ecu-tests

2. Create a virtual environment and install dependencies

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

3. Run the mock test suite (no hardware needed)

python -m pytest -m "not hardware" -v

4. Install Melexis packages into the venv (required for hardware tests)

pylin, pymumclient, and pylinframe are not on PyPI — they ship with the Melexis IDE.
On Windows they live at:

C:\Program Files\Melexis\Melexis IDE\plugins\com.melexis.mlxide.python_1.2.0.202408130945\python\Lib\site-packages

which WSL exposes at /mnt/c/Program Files/Melexis/Melexis IDE/....

With your venv already activated, copy the packages directly into it:

source .venv/bin/activate   # skip if already active

MELEXIS_SITE="/mnt/c/Program Files/Melexis/Melexis IDE/plugins/com.melexis.mlxide.python_1.2.0.202408130945/python/Lib/site-packages"
VENV_SITE=$(python -c "import site; print(site.getsitepackages()[0])")

cp -r "$MELEXIS_SITE/pylin"       "$VENV_SITE/"
cp -r "$MELEXIS_SITE/pymumclient" "$VENV_SITE/"
cp -r "$MELEXIS_SITE/pylinframe"  "$VENV_SITE/"

Verify the installation:

python -c "import pylin; import pymumclient; print('OK')"

Alternative: You can also run bash vendor/automated_lin_test/install_packages.sh after updating the MELEXIS_SITE_PACKAGES path in that script — but the commands above are simpler and target the venv directly.

5. Run hardware tests via MUM

export ECU_TESTS_CONFIG=./config/mum.example.yaml
python -m pytest -m "hardware and mum" -v

6. Run hardware tests via BabyLIN (DEPRECATED)

Deprecated. The BabyLIN adapter is kept for backward compatibility only; new work should target MUM (step 5). The BabyLIN SDK also ships Windows-only native libraries (.dll), so these tests cannot run under WSL unless you have a Linux-compatible .so build of the SDK.

export ECU_TESTS_CONFIG=./config/babylin.example.yaml
python -m pytest -m "hardware and babylin" -v

7. View reports

Open the HTML report directly in Windows from the WSL terminal:

explorer.exe reports/report.html

Or from PowerShell/CMD:

start .\reports\report.html

Reporting: Metadata in HTML

We extract these fields from each tests docstring and render them in the HTML report:

  • Title
  • Description
  • Requirements (e.g., REQ-001)
  • Test Steps
  • Expected Result

Markers like smoke, hardware, and req_00x are also displayed.

Example docstring format used by the plugin:

"""
Title: Mock LIN Interface - Send/Receive Echo Test

Description: Validates basic send/receive functionality using the mock LIN interface with echo behavior.

Requirements: REQ-001, REQ-003

Test Steps:
1. Connect to mock interface
2. Send frame ID 0x01 with data [0x55]
3. Receive the echo within 100ms
4. Assert ID and data integrity

Expected Result:
- Echoed frame matches sent frame
"""

Configuration

Default config is config/test_config.yaml. Override via the ECU_TESTS_CONFIG environment variable.

$env:ECU_TESTS_CONFIG = (Resolve-Path .\config\test_config.yaml)

MUM configuration (default for hardware)

Template: config/mum.example.yaml

interface:
  type: mum
  host: 192.168.7.2          # MUM IP (USB-RNDIS default)
  lin_device: lin0           # MUM LIN device name
  power_device: power_out0   # MUM power-control device (built-in PSU)
  bitrate: 19200             # LIN baudrate
  boot_settle_seconds: 0.5   # Wait after power-up before sending the first frame
  frame_lengths:
    0x0A: 8                  # ALM_Req_A
    0x11: 4                  # ALM_Status

The MUM has its own power output, so power_supply.enabled: false is the typical setting when using MUM. The Owon PSU support remains for over/under- voltage scenarios but is independent of the LIN interface.

BabyLIN configuration (DEPRECATED)

Retained for backward compatibility. Prefer the MUM configuration above.

Template: config/babylin.example.yaml

interface:
  type: babylin          # deprecated; prefer "mum" or "mock"
  channel: 0             # Channel index used by the SDK wrapper
  bitrate: 19200         # Usually determined by SDF
  sdf_path: ./vendor/Example.sdf
  schedule_nr: 0         # Start this schedule on connect (-1 to skip)

LIN adapter capabilities

Adapter Power control Diagnostic frames (Classic checksum) Passive listen
mock n/a n/a yes (queue-based)
mum yes (power_out0) yes (MumLinInterface.send_raw()ld_put_raw) no — receive(id) triggers a slave read
babylin (deprecated) external (Owon PSU) via SDF / BLC_sendCommand yes (frame queue)

Switch to hardware profile and run only hardware tests (MUM example):

$env:ECU_TESTS_CONFIG = (Resolve-Path .\config\mum.example.yaml)
python.exe -m pytest -m hardware -v

Project structure

ecu_tests/
├── ecu_framework/                  # Core framework package
│   ├── config.py                   # YAML config loader → typed dataclasses
│   ├── lin/
│   │   ├── base.py                 # LinInterface + LinFrame contract
│   │   ├── mock.py                 # Mock LIN adapter (no hardware)
│   │   ├── mum.py                  # MUM adapter (current default; Melexis pylin/pymumclient)
│   │   ├── ldf.py                  # LdfDatabase wrapper around ldfparser
│   │   └── babylin.py              # DEPRECATED BabyLIN SDK-wrapper adapter
│   ├── power/
│   │   └── owon_psu.py             # Owon PSU SCPI controller + cross-platform port resolver
│   └── flashing/
│       └── hex_flasher.py          # Hex flashing scaffold
│
├── tests/
│   ├── conftest.py                 # Project-wide fixtures: config, lin, ldf, flash_ecu, rp
│   │
│   ├── unit/                       # Pure-logic tests (no hardware)
│   │   ├── test_config_loader.py
│   │   ├── test_linframe.py
│   │   ├── test_ldf_database.py
│   │   ├── test_hex_flasher.py
│   │   ├── test_mum_adapter_mocked.py
│   │   └── test_babylin_adapter_mocked.py     # deprecated path
│   │
│   ├── plugin/
│   │   └── test_conftest_plugin_artifacts.py   # reporting plugin self-test
│   │
│   ├── hardware/                   # Real-bench tests (MUM / PSU / ECU)
│   │   ├── conftest.py             # Session-scoped autouse PSU fixture (powers the ECU)
│   │   ├── frame_io.py             # FrameIO — generic LDF-driven send/receive/pack/unpack
│   │   ├── alm_helpers.py          # AlmTester — ALM_Node domain helpers + constants
│   │   ├── psu_helpers.py          # apply_voltage_and_settle — measure-rail-then-validate
│   │   ├── _test_case_template.py            # ALM-only test starting point (not collected)
│   │   ├── _test_case_template_psu_lin.py    # PSU + LIN test starting point (not collected)
│   │   ├── test_mum_alm_animation.py         # ALM mode/update/LID checks via MUM
│   │   ├── test_mum_auto_addressing.py       # BSM auto-addressing (NAD)
│   │   ├── test_e2e_mum_led_activate.py      # MUM end-to-end power+activate
│   │   ├── test_overvolt.py                  # Voltage-tolerance (over/under/sweep)
│   │   ├── test_psu_voltage_settling.py      # PSU settling-time characterization (-m psu_settling)
│   │   ├── test_owon_psu.py                  # PSU IDN + measurements (read-only)
│   │   └── test_e2e_power_on_lin_smoke.py    # DEPRECATED BabyLIN E2E
│   │
│   ├── test_smoke_mock.py                    # Mock interface smoke + boundary
│   ├── test_babylin_hardware_smoke.py        # DEPRECATED BabyLIN hardware
│   ├── test_babylin_hardware_schedule_smoke.py # DEPRECATED BabyLIN schedule flow
│   ├── test_babylin_wrapper_mock.py          # DEPRECATED BabyLIN adapter w/ mock wrapper
│   └── test_hardware_placeholder.py
│
├── config/
│   ├── test_config.yaml            # Default config (MUM by default)
│   ├── mum.example.yaml            # MUM hardware profile
│   ├── owon_psu.example.yaml       # PSU profile (copy to owon_psu.yaml)
│   ├── owon_psu.yaml               # Optional per-machine PSU override
│   ├── examples.yaml               # Combined mock/babylin profiles
│   └── babylin.example.yaml        # DEPRECATED BabyLIN profile
│
├── docs/
│   ├── README.md                   # Documentation index
│   ├── 01_run_sequence.md          # End-to-end run sequence
│   ├── 02_configuration_resolution.md
│   ├── 03_reporting_and_metadata.md
│   ├── 04_lin_interface_call_flow.md
│   ├── 05_architecture_overview.md
│   ├── 06_requirement_traceability.md
│   ├── 07_flash_sequence.md
│   ├── 08_babylin_internals.md     # DEPRECATED
│   ├── 09_raspberry_pi_deployment.md
│   ├── 10_build_custom_image.md
│   ├── 11_conftest_plugin_overview.md
│   ├── 12_using_the_framework.md
│   ├── 13_unit_testing_guide.md
│   ├── 14_power_supply.md          # PSU controller, resolver, session-managed power
│   ├── 15_report_properties_cheatsheet.md
│   ├── 16_mum_internals.md
│   ├── 17_ldf_parser.md
│   ├── 18_test_catalog.md
│   ├── 19_frame_io_and_alm_helpers.md   # Hardware test helpers + four-phase pattern
│   └── DEVELOPER_COMMIT_GUIDE.md
│
├── vendor/                         # Third-party + project assets
│   ├── 4SEVEN_color_lib_test.ldf   # LDF used by the LIN tests
│   ├── 4SEVEN_color_lib_test.sdf   # SDF for the deprecated BabyLIN path
│   ├── rgb_to_pwm.py               # RGB → PWM calculator (used by ALM PWM assertions)
│   ├── led_platform.py             # Platform-specific LED helpers
│   ├── Owon/
│   │   └── owon_psu_quick_demo.py  # Standalone PSU demo
│   ├── automated_lin_test/         # Reference scripts (test_animation.py etc.)
│   │   ├── README.md
│   │   ├── install_packages.sh     # Installs Melexis pylin/pymumclient into the venv
│   │   └── (test_*.py reference scripts)
│   ├── BabyLIN_library.py          # DEPRECATED official BabyLIN SDK Python wrapper
│   ├── BLCInterfaceExample.py      # DEPRECATED vendor example
│   └── BabyLIN library/            # DEPRECATED platform binaries (DLL/.so)
│
├── reports/                        # Generated per-run (HTML, JUnit, summary, coverage)
│   ├── report.html
│   ├── junit.xml
│   ├── summary.md
│   └── requirements_coverage.json
│
├── scripts/
│   ├── pi_install.sh               # Raspberry Pi installer
│   ├── ecu-tests.service           # systemd unit
│   ├── ecu-tests.timer             # systemd timer
│   ├── run_tests.sh                # Convenience runner
│   ├── run_two_reports.ps1         # Split unit/non-unit report runs (Windows)
│   └── 99-babylin.rules            # DEPRECATED udev rule
│
├── conftest_plugin.py              # HTML metadata extraction + report customization
├── pytest.ini                      # Markers, addopts, junit_family=legacy
├── requirements.txt
├── README.md                       # ← you are here
└── TESTING_FRAMEWORK_GUIDE.md      # Deep dive companion to this README

For the hardware-test layer specifically, see docs/19_frame_io_and_alm_helpers.md (FrameIO + AlmTester + the four-phase test pattern) and docs/14_power_supply.md §5 (session-managed PSU lifecycle).

Usage recipes

  • Run everything (mock and any non-hardware tests):
python.exe -m pytest -v
  • Run by marker:
python.exe -m pytest -m "smoke" -v
python.exe -m pytest -m "req_001" -v
  • Run in parallel:
python.exe -m pytest -n auto -v
  • Run the plugin self-test (verifies reporting artifacts under reports/):
python -m pytest tests\plugin\test_conftest_plugin_artifacts.py -q
  • Generate separate HTML/JUnit reports for unit vs non-unit tests:
./scripts/run_two_reports.ps1

BabyLIN adapter notes (DEPRECATED)

Kept for backward compatibility. New work should target the MUM adapter.

The ecu_framework/lin/babylin.py implementation uses the official BabyLIN_library.py wrapper from the SDK. Put BabyLIN_library.py under vendor/ (or on PYTHONPATH) along with the SDK's platform-specific libraries. Configure sdf_path and schedule_nr to load an SDF and start a schedule during connect. The adapter sends frames via BLC_mon_set_xmit and receives via BLC_getNextFrameTimeout. Instantiating BabyLinInterface emits a DeprecationWarning.

Docs and references

  • Guide: TESTING_FRAMEWORK_GUIDE.md (deep dive with examples and step-by-step flows)
  • Reports: reports/report.html and reports/junit.xml (generated on each run)
  • CI summary: reports/summary.md (machine-friendly run summary)
  • Requirements coverage: reports/requirements_coverage.json (requirement → tests mapping)
    • Tip: Open the HTML report on Windows with: start .\reports\report.html
  • Configs: config/test_config.yaml, config/mum.example.yaml, config/babylin.example.yaml (deprecated) — copy and modify for your environment
  • BabyLIN SDK placement and notes: vendor/README.md (deprecated; only relevant for legacy BabyLIN rigs)
  • Docs index: docs/README.md (run sequence, config resolution, reporting, call flows)
  • Raspberry Pi deployment: docs/09_raspberry_pi_deployment.md
  • Build custom Pi image: docs/10_build_custom_image.md
  • Pi scripts: scripts/pi_install.sh, scripts/ecu-tests.service, scripts/ecu-tests.timer, scripts/run_tests.sh

Troubleshooting

  • HTML report missing columns: ensure pytest.ini includes -p conftest_plugin in addopts.
  • ImportError for BabyLIN_library (DEPRECATED path): verify vendor/BabyLIN_library.py placement and that required native libraries (DLL/.so) from the SDK are available on PATH/LD_LIBRARY_PATH. Consider migrating to the MUM adapter, which avoids vendor DLLs entirely.
  • Permission errors in PowerShell: run the venv's full Python path or adjust ExecutionPolicy for scripts.
  • Import errors: activate the venv and reinstall requirements.txt.

Owon Power Supply (SCPI) — library, config, tests, and quick demo

We provide a reusable pyserial-based library, a hardware test integrated with the central config, and a minimal quick demo script.

  • Library: ecu_framework/power/owon_psu.py (class OwonPSU, SerialParams, scan_ports)
  • Central config: config/test_config.yaml (power_supply section)
    • Optionally merge config/owon_psu.yaml or set OWON_PSU_CONFIG to a YAML path
  • Hardware test: tests/hardware/test_owon_psu.py (skips unless power_supply.enabled is true)
  • quick demo: vendor/Owon/owon_psu_quick_demo.py (reads OWON_PSU_CONFIG or config/owon_psu.yaml)

Quick setup (Windows PowerShell):

# Ensure dependencies
pip install -r .\requirements.txt

# Option A: configure centrally in test_config.yaml
#   Edit config\test_config.yaml and set:
#   power_supply.enabled: true
#   power_supply.port: COM4

# Option B: use a separate machine-specific YAML
copy .\config\owon_psu.example.yaml .\config\owon_psu.yaml
# edit COM port and options in .\config\owon_psu.yaml

# Run the hardware PSU test (skips if disabled or missing port)
pytest -k test_owon_psu_idn_and_optional_set -m hardware -q

# Run the quick demo script
python .\vendor\Owon\owon_psu_quick_demo.py

YAML keys supported by power_supply:

power_supply:
  enabled: true
  port: COM4           # or /dev/ttyUSB0
  baudrate: 115200
  timeout: 1.0
  eol: "\n"          # or "\r\n"
  parity: N            # N|E|O
  stopbits: 1          # 1|2
  xonxoff: false
  rtscts: false
  dsrdtr: false
  idn_substr: OWON
  do_set: false
  set_voltage: 5.0
  set_current: 0.1

Troubleshooting:

  • If *IDN? is empty, confirm port, parity/stopbits, and eol (try \r\n).
  • On Windows, if COM>9, use \\.\COM10 style in some tools; here plain COM10 usually works.
  • Ensure only one program opens the COM port at a time.

Next steps

  • Replace HexFlasher with a production flashing routine (UDS)
  • Expand tests for end-to-end ECU workflows and requirement coverage
Description
Automation test
Readme 8.6 MiB