""" test_ldf_handler.py — Tests for the LDF parsing module. Tests the ldf_handler adapter layer that converts ldfparser's output into our simplified data structures. We test: 1. Parsing a valid LDF file 2. Correct frame/signal extraction 3. Master vs slave frame classification 4. Schedule table extraction 5. Error handling for missing/invalid files """ import sys import pytest from pathlib import Path # Add src to path sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from ldf_handler import parse_ldf, LdfData, FrameInfo, SignalInfo, ScheduleEntryInfo # Path to the sample LDF file SAMPLE_LDF = str(Path(__file__).parent.parent.parent / "resources" / "sample.ldf") class TestParseLdf: """Test basic LDF parsing.""" def test_returns_ldf_data(self): result = parse_ldf(SAMPLE_LDF) assert isinstance(result, LdfData) def test_protocol_version(self): result = parse_ldf(SAMPLE_LDF) assert result.protocol_version == "2.1" def test_baudrate(self): result = parse_ldf(SAMPLE_LDF) assert result.baudrate == 19200 def test_master_name(self): result = parse_ldf(SAMPLE_LDF) assert result.master_name == "ECU_Master" def test_slave_names(self): result = parse_ldf(SAMPLE_LDF) assert "Motor_Control" in result.slave_names assert "Door_Module" in result.slave_names def test_file_path_stored(self): result = parse_ldf(SAMPLE_LDF) assert result.file_path == SAMPLE_LDF class TestFrameClassification: """Test that frames are correctly split into Tx and Rx.""" def test_tx_frame_count(self): """Master publishes 2 frames: Motor_Command, Door_Command.""" result = parse_ldf(SAMPLE_LDF) assert len(result.tx_frames) == 2 def test_rx_frame_count(self): """Slaves publish 2 frames: Motor_Status, Door_Status.""" result = parse_ldf(SAMPLE_LDF) assert len(result.rx_frames) == 2 def test_tx_frames_are_master(self): result = parse_ldf(SAMPLE_LDF) for frame in result.tx_frames: assert frame.is_master_tx is True def test_rx_frames_are_slave(self): result = parse_ldf(SAMPLE_LDF) for frame in result.rx_frames: assert frame.is_master_tx is False def test_tx_frame_names(self): result = parse_ldf(SAMPLE_LDF) names = [f.name for f in result.tx_frames] assert "Motor_Command" in names assert "Door_Command" in names def test_rx_frame_names(self): result = parse_ldf(SAMPLE_LDF) names = [f.name for f in result.rx_frames] assert "Motor_Status" in names assert "Door_Status" in names class TestFrameDetails: """Test frame metadata extraction.""" def test_motor_command_id(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") assert frame.frame_id == 0x10 def test_motor_command_length(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") assert frame.length == 2 def test_motor_command_publisher(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") assert frame.publisher == "ECU_Master" class TestSignalExtraction: """Test signal details within frames.""" def test_motor_command_signals(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") sig_names = [s.name for s in frame.signals] assert "MotorEnable" in sig_names assert "MotorDirection" in sig_names assert "MotorSpeed" in sig_names def test_signal_bit_offset(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") enable = next(s for s in frame.signals if s.name == "MotorEnable") assert enable.bit_offset == 0 def test_signal_width(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") speed = next(s for s in frame.signals if s.name == "MotorSpeed") assert speed.width == 8 def test_signal_init_value(self): result = parse_ldf(SAMPLE_LDF) frame = next(f for f in result.tx_frames if f.name == "Motor_Command") enable = next(s for s in frame.signals if s.name == "MotorEnable") assert enable.init_value == 0 class TestScheduleTables: """Test schedule table extraction.""" def test_schedule_count(self): result = parse_ldf(SAMPLE_LDF) assert len(result.schedule_tables) == 2 def test_schedule_names(self): result = parse_ldf(SAMPLE_LDF) names = [st.name for st in result.schedule_tables] assert "NormalSchedule" in names assert "FastSchedule" in names def test_normal_schedule_entries(self): result = parse_ldf(SAMPLE_LDF) normal = next(st for st in result.schedule_tables if st.name == "NormalSchedule") assert len(normal.entries) == 4 def test_normal_schedule_delay(self): result = parse_ldf(SAMPLE_LDF) normal = next(st for st in result.schedule_tables if st.name == "NormalSchedule") # All entries in NormalSchedule have 10ms delay for entry in normal.entries: assert entry.delay_ms == 10 def test_fast_schedule_delay(self): result = parse_ldf(SAMPLE_LDF) fast = next(st for st in result.schedule_tables if st.name == "FastSchedule") for entry in fast.entries: assert entry.delay_ms == 5 def test_frame_entries_have_no_data(self): """Regular frame entries should not have raw data.""" result = parse_ldf(SAMPLE_LDF) normal = next(st for st in result.schedule_tables if st.name == "NormalSchedule") for entry in normal.entries: assert entry.data is None class TestErrorHandling: """Test error cases.""" def test_file_not_found(self): with pytest.raises(FileNotFoundError): parse_ldf("/nonexistent/path/fake.ldf") def test_invalid_file(self, tmp_path): """A file that exists but isn't valid LDF should raise an error.""" bad_file = tmp_path / "bad.ldf" bad_file.write_text("this is not a valid LDF file") with pytest.raises(Exception): parse_ldf(str(bad_file))