159 lines
4.9 KiB
Python
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")
|