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>
163 lines
5.0 KiB
Python
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
|