Lin_Simulator/python/tests/test_babylin_backend.py
Mohamed Salem cb60c2ad5d Steps 2-7: LDF loading, signal editing, Rx display, connection, BabyLIN backend, scheduler
Step 2 - LDF Loading:
- ldfparser integration (Python) / custom regex parser (C++)
- QTreeWidget with expandable signal rows, merged Value column
- Hex/Dec toggle, FreeFormat schedule entries, auto-reload
- Baud rate auto-detection from LDF

Step 3 - Signal Editing:
- Bit packing/unpacking (signal value ↔ frame bytes)
- ReadOnlyColumnDelegate for per-column editability
- Value clamping to signal width, recursion guard

Step 4 - Rx Panel:
- receive_rx_frame() API with timestamp, signal unpacking
- Change highlighting (yellow), auto-scroll toggle, clear button
- Dashboard view (in-place update per frame_id)

Step 5 - Connection Panel:
- ConnectionManager with state machine (Disconnected/Connecting/Connected/Error)
- Port scanning (pyserial / QSerialPort), connect/disconnect with UI mapping

Step 6 - BabyLIN Backend:
- BabyLinBackend wrapping Lipowsky BabyLIN_library.py DLL
- Mock mode for macOS/CI, device scan, SDF loading, signal access
- Frame callbacks, raw command access

Step 7 - Master Scheduler:
- QTimer-based schedule execution with start/stop/pause
- Frame sent callback with visual highlighting
- Mock Rx simulation, manual send, global rate override

Tests: Python 171 | C++ 124 (Steps 1-5 parity, Steps 6-7 Python-first)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:21:24 +02:00

163 lines
5.0 KiB
Python

"""
test_babylin_backend.py — Tests for Step 6: BabyLIN communication backend.
All tests run in MOCK MODE since we don't have hardware in CI.
The mock mode verifies the state machine and API surface.
When real hardware is connected, the same API calls go to the DLL.
"""
import sys
import pytest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from babylin_backend import BabyLinBackend, BackendState, DeviceInfo
@pytest.fixture
def backend():
"""Create a fresh BabyLinBackend in mock mode."""
b = BabyLinBackend()
assert b.is_mock_mode # Should be mock on macOS / CI
return b
class TestBackendInit:
"""Test initial state."""
def test_initial_state_idle(self, backend):
assert backend.state == BackendState.IDLE
def test_is_mock_mode(self, backend):
assert backend.is_mock_mode
def test_no_device_initially(self, backend):
assert backend.device_info is None
def test_sdf_not_loaded_initially(self, backend):
assert not backend.sdf_loaded
class TestDeviceScanning:
"""Test device discovery in mock mode."""
def test_scan_returns_mock_device(self, backend):
devices = backend.scan_devices()
assert len(devices) >= 1
assert "Mock" in devices[0].hardware_type
def test_scan_returns_device_info(self, backend):
devices = backend.scan_devices()
assert isinstance(devices[0], DeviceInfo)
assert devices[0].port_name != ""
class TestConnection:
"""Test connect/disconnect flow in mock mode."""
def test_connect_success(self, backend):
result = backend.connect("MOCK-BABYLIN")
assert result is True
assert backend.state == BackendState.IDLE
assert backend.device_info is not None
assert backend.device_info.port_name == "MOCK-BABYLIN"
def test_disconnect(self, backend):
backend.connect("MOCK-BABYLIN")
backend.disconnect()
assert backend.device_info is None
assert backend.state == BackendState.IDLE
class TestSdfLoading:
"""Test SDF file loading in mock mode."""
def test_load_existing_sdf(self, backend, tmp_path):
backend.connect("MOCK-BABYLIN")
sdf_file = tmp_path / "test.sdf"
sdf_file.write_text("mock sdf content")
result = backend.load_sdf(str(sdf_file))
assert result is True
assert backend.sdf_loaded
def test_load_nonexistent_sdf(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.load_sdf("/nonexistent/path.sdf")
assert result is False
assert not backend.sdf_loaded
class TestBusControl:
"""Test start/stop in mock mode."""
def test_start(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.start(schedule_index=0)
assert result is True
assert backend.state == BackendState.RUNNING
def test_stop(self, backend):
backend.connect("MOCK-BABYLIN")
backend.start()
result = backend.stop()
assert result is True
assert backend.state == BackendState.STOPPED
def test_start_with_schedule(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.start(schedule_index=2)
assert result is True
assert backend.state == BackendState.RUNNING
class TestSignalAccess:
"""Test signal read/write in mock mode."""
def test_set_signal_by_name(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.set_signal_by_name("MotorSpeed", 128)
assert result is True
def test_set_signal_by_index(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.set_signal_by_index(0, 1)
assert result is True
def test_get_signal_value(self, backend):
backend.connect("MOCK-BABYLIN")
value = backend.get_signal_value(0)
assert value == 0 # Mock returns 0
class TestFrameCallback:
"""Test frame callback registration in mock mode."""
def test_register_callback(self, backend):
received = []
backend.connect("MOCK-BABYLIN")
backend.register_frame_callback(lambda fid, data: received.append((fid, data)))
# In mock mode, callback is stored but not called automatically
assert backend._frame_callback is not None
def test_unregister_callback(self, backend):
backend.connect("MOCK-BABYLIN")
backend.register_frame_callback(lambda fid, data: None)
backend.register_frame_callback(None)
assert backend._frame_callback is None
class TestRawAccess:
"""Test raw frame and command sending in mock mode."""
def test_send_raw_master_request(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.send_raw_master_request(bytes([0x3C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
assert result is True
def test_send_command(self, backend):
backend.connect("MOCK-BABYLIN")
result = backend.send_command("start;")
assert result is True