ecu-tests/tests/unit/test_ldf_database.py

159 lines
4.9 KiB
Python

"""Unit tests for LdfDatabase / Frame using the 4SEVEN LDF as fixture data."""
from __future__ import annotations
import pathlib
import pytest
# Skip the whole module if ldfparser isn't installed.
pytest.importorskip("ldfparser", reason="ldfparser is required for LDF unit tests")
from ecu_framework.lin.ldf import Frame, FrameNotFound, LdfDatabase
WORKSPACE_ROOT = pathlib.Path(__file__).resolve().parents[2]
LDF_PATH = WORKSPACE_ROOT / "vendor" / "4SEVEN_color_lib_test.ldf"
@pytest.fixture(scope="module")
def db() -> LdfDatabase:
return LdfDatabase(LDF_PATH)
@pytest.mark.unit
def test_loads_metadata(db: LdfDatabase):
assert db.protocol_version in ("2.1", "2.0", "1.3")
assert db.baudrate == 19200
@pytest.mark.unit
def test_lookup_by_name_and_id(db: LdfDatabase):
by_name = db.frame("ALM_Req_A")
by_id = db.frame(0x0A)
assert by_name.id == 0x0A == by_id.id
assert by_name.name == "ALM_Req_A" == by_id.name
assert by_name.length == 8
@pytest.mark.unit
def test_unknown_frame_raises(db: LdfDatabase):
with pytest.raises(FrameNotFound):
db.frame("not_a_real_frame")
@pytest.mark.unit
def test_signal_layout_matches_ldf(db: LdfDatabase):
layout = db.frame("ALM_Req_A").signal_layout()
# spot-check a couple of entries from the LDF Frames block
assert (0, "AmbLightColourRed", 8) in layout
assert (32, "AmbLightUpdate", 2) in layout
assert (34, "AmbLightMode", 6) in layout
assert (56, "AmbLightLIDTo", 8) in layout
@pytest.mark.unit
def test_pack_kwargs_full_payload(db: LdfDatabase):
frame = db.frame("ALM_Req_A")
payload = frame.pack(
AmbLightColourRed=0xFF,
AmbLightColourGreen=0xFF,
AmbLightColourBlue=0xFF,
AmbLightIntensity=0xFF,
AmbLightUpdate=0,
AmbLightMode=0,
AmbLightDuration=0,
AmbLightLIDFrom=0x01,
AmbLightLIDTo=0x01,
)
assert isinstance(payload, bytes)
assert len(payload) == 8
assert payload == bytes.fromhex("ffffffff00000101")
@pytest.mark.unit
def test_pack_unspecified_signals_use_init_value(db: LdfDatabase):
"""LDF defines non-zero init_values for ColorConfigFrameRed signals;
pack() with no kwargs should fall back to those defaults."""
frame = db.frame("ColorConfigFrameRed")
payload = frame.pack()
decoded = frame.unpack(payload)
# ColorConfigFrameRed_X init_value is 5665, _Y is 2396, _Z is 0, _Vf_Cal is 2031
assert decoded["ColorConfigFrameRed_X"] == 5665
assert decoded["ColorConfigFrameRed_Y"] == 2396
assert decoded["ColorConfigFrameRed_Z"] == 0
assert decoded["ColorConfigFrameRed_Vf_Cal"] == 2031
@pytest.mark.unit
def test_pack_dict_argument(db: LdfDatabase):
frame = db.frame("ALM_Req_A")
a = frame.pack(AmbLightColourRed=0x12, AmbLightColourBlue=0x34)
b = frame.pack({"AmbLightColourRed": 0x12, "AmbLightColourBlue": 0x34})
assert a == b
@pytest.mark.unit
def test_pack_rejects_args_and_kwargs_together(db: LdfDatabase):
frame = db.frame("ALM_Req_A")
with pytest.raises(TypeError):
frame.pack({"AmbLightColourRed": 1}, AmbLightColourGreen=2)
@pytest.mark.unit
def test_unpack_round_trip(db: LdfDatabase):
frame = db.frame("ALM_Req_A")
values = {
"AmbLightColourRed": 0xAB,
"AmbLightColourGreen": 0xCD,
"AmbLightColourBlue": 0x12,
"AmbLightIntensity": 0x80,
"AmbLightUpdate": 2, # 2 bits
"AmbLightMode": 0x15, # 6 bits
"AmbLightDuration": 0x40,
"AmbLightLIDFrom": 0x01,
"AmbLightLIDTo": 0xFE,
}
payload = frame.pack(**values)
decoded = frame.unpack(payload)
for k, v in values.items():
assert decoded[k] == v, f"signal {k} mismatch: {decoded[k]} vs {v}"
@pytest.mark.unit
def test_alm_status_decode_real_payload(db: LdfDatabase):
"""ALM_Status: byte 0 carries ALMNadNo (8 bits at offset 0)."""
frame = db.frame("ALM_Status")
assert frame.length == 4
decoded = frame.unpack(b"\x07\x00\x00\x00")
assert decoded["ALMNadNo"] == 7
@pytest.mark.unit
def test_frame_lengths_includes_all_unconditional_frames(db: LdfDatabase):
lengths = db.frame_lengths()
assert lengths[0x0A] == 8 # ALM_Req_A
assert lengths[0x11] == 4 # ALM_Status
assert lengths[0x06] == 3 # ConfigFrame
# Every entry should map to a positive length
assert all(l >= 1 for l in lengths.values())
@pytest.mark.unit
def test_frames_returns_wrapped_frame_objects(db: LdfDatabase):
frames = db.frames()
assert all(isinstance(f, Frame) for f in frames)
names = {f.name for f in frames}
assert {"ALM_Req_A", "ALM_Status", "ConfigFrame"}.issubset(names)
@pytest.mark.unit
def test_ldf_repr_does_not_explode(db: LdfDatabase):
s = repr(db)
assert "LdfDatabase" in s
@pytest.mark.unit
def test_missing_file_raises_filenotfounderror(tmp_path):
with pytest.raises(FileNotFoundError):
LdfDatabase(tmp_path / "nope.ldf")