Lin_Simulator/python/tests/test_rx_realtime.py
Mohamed Salem c6f0d2fdde Add README, automotive dark theme, and polished GUI
- README.md with full project documentation, architecture, setup guide
- Automotive/industrial dark theme (theme.py) inspired by Vector CANoe
- Dark blue-black background with amber/orange accents
- Styled buttons (green Start, red Stop), orange hover effects
- Custom scrollbars, tooltips, group box titles, tree widget headers
- Updated highlight colors to match theme palette

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

190 lines
6.8 KiB
Python

"""
test_rx_realtime.py — Tests for Step 4: Rx panel real-time display.
Tests frame reception, timestamp updates, signal unpacking,
change highlighting, auto-scroll, and clear functionality.
"""
import sys
import pytest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QBrush, QColor
SAMPLE_LDF = str(Path(__file__).parent.parent.parent / "resources" / "sample.ldf")
@pytest.fixture(scope="session")
def app():
application = QApplication.instance() or QApplication(sys.argv)
yield application
@pytest.fixture
def window(app):
from main_window import MainWindow
w = MainWindow()
w._load_ldf_file(SAMPLE_LDF)
return w
class TestRxFrameReception:
"""Test that receive_rx_frame updates the Rx panel correctly."""
def test_timestamp_updates(self, window):
"""Receiving a frame should set the timestamp."""
window.receive_rx_frame(0x20, [0x03, 0xC8])
frame_item = window.rx_table.topLevelItem(0)
ts = frame_item.text(0)
assert ts != ""
assert ":" in ts # Should be HH:mm:ss.zzz format
def test_frame_bytes_stored(self, window):
"""Received bytes should be stored in the frame row."""
window.receive_rx_frame(0x20, [0x03, 0xC8])
frame_item = window.rx_table.topLevelItem(0)
stored = frame_item.data(4, Qt.ItemDataRole.UserRole)
assert stored == [0x03, 0xC8]
def test_frame_value_displayed(self, window):
"""Frame value column should show received bytes."""
window.receive_rx_frame(0x20, [0x03, 0xC8])
frame_item = window.rx_table.topLevelItem(0)
val = frame_item.text(4)
# Default hex mode
assert "03" in val
assert "C8" in val
def test_signal_values_unpacked(self, window):
"""Signal children should have unpacked values from received bytes."""
# Motor_Status: MotorStatus (bit 0, w2), MotorTemp (bit 8, w8)
window.receive_rx_frame(0x20, [0x03, 0xC8])
frame_item = window.rx_table.topLevelItem(0)
# MotorStatus (bit 0, width 2): byte0 & 0x03 = 3
status_val = frame_item.child(0).data(4, Qt.ItemDataRole.UserRole)
assert status_val == 3
# MotorTemp (bit 8, width 8): byte1 = 0xC8 = 200
temp_val = frame_item.child(1).data(4, Qt.ItemDataRole.UserRole)
assert temp_val == 200
def test_unknown_frame_id_ignored(self, window):
"""Receiving an unknown frame ID should not crash."""
window.receive_rx_frame(0xFF, [0x00])
# No assertion — just verify no crash
def test_update_same_frame_twice(self, window):
"""Second reception of same frame should update in-place."""
window.receive_rx_frame(0x20, [0x01, 0x10])
window.receive_rx_frame(0x20, [0x02, 0x20])
frame_item = window.rx_table.topLevelItem(0)
stored = frame_item.data(4, Qt.ItemDataRole.UserRole)
assert stored == [0x02, 0x20] # Updated, not appended
class TestChangeHighlighting:
"""Test that changed signal values get highlighted."""
def test_changed_signal_highlighted(self, window):
"""When a signal value changes, it should be highlighted yellow."""
# First reception
window.receive_rx_frame(0x20, [0x01, 0x10])
# Second reception with different MotorTemp
window.receive_rx_frame(0x20, [0x01, 0x20])
frame_item = window.rx_table.topLevelItem(0)
temp_sig = frame_item.child(1) # MotorTemp changed: 0x10 → 0x20
bg = temp_sig.background(4)
# Should be highlighted (amber/yellow with alpha)
assert bg.color().red() > 200
assert bg.color().green() > 150
def test_unchanged_signal_not_highlighted(self, window):
"""Signal that didn't change should not be highlighted."""
window.receive_rx_frame(0x20, [0x01, 0x10])
window.receive_rx_frame(0x20, [0x01, 0x20])
frame_item = window.rx_table.topLevelItem(0)
status_sig = frame_item.child(0) # MotorStatus unchanged: still 1
bg = status_sig.background(4)
# Should NOT be yellow — default/empty brush
assert bg.style() == Qt.BrushStyle.NoBrush
def test_first_reception_no_highlight(self, window):
"""First reception should not highlight anything (no previous value)."""
window.receive_rx_frame(0x20, [0x01, 0x10])
frame_item = window.rx_table.topLevelItem(0)
for j in range(frame_item.childCount()):
bg = frame_item.child(j).background(4)
assert bg.style() == Qt.BrushStyle.NoBrush
class TestAutoScroll:
"""Test auto-scroll behavior."""
def test_auto_scroll_default_on(self, window):
assert window.chk_auto_scroll.isChecked()
def test_auto_scroll_can_be_disabled(self, window):
window.chk_auto_scroll.setChecked(False)
assert not window.chk_auto_scroll.isChecked()
class TestClearRx:
"""Test the Clear button."""
def test_clear_resets_timestamps(self, window):
window.receive_rx_frame(0x20, [0x01, 0x10])
window._on_clear_rx()
for i in range(window.rx_table.topLevelItemCount()):
assert window.rx_table.topLevelItem(i).text(0) == ""
def test_clear_resets_values(self, window):
window.receive_rx_frame(0x20, [0x01, 0x10])
window._on_clear_rx()
for i in range(window.rx_table.topLevelItemCount()):
assert window.rx_table.topLevelItem(i).text(4) == ""
def test_clear_resets_signal_highlights(self, window):
window.receive_rx_frame(0x20, [0x01, 0x10])
window.receive_rx_frame(0x20, [0x02, 0x20])
window._on_clear_rx()
frame_item = window.rx_table.topLevelItem(0)
for j in range(frame_item.childCount()):
bg = frame_item.child(j).background(4)
assert bg.style() == Qt.BrushStyle.NoBrush
def test_clear_resets_last_values_tracking(self, window):
window.receive_rx_frame(0x20, [0x01, 0x10])
window._on_clear_rx()
assert len(window._rx_last_values) == 0
class TestRxHexDec:
"""Test hex/dec mode applies to received Rx data."""
def test_rx_hex_mode(self, window):
window.receive_rx_frame(0x20, [0x03, 0xC8])
frame_item = window.rx_table.topLevelItem(0)
# Hex mode (default)
assert "C8" in frame_item.text(4)
assert frame_item.child(1).text(4).startswith("0x")
def test_rx_dec_mode(self, window):
window.chk_hex_mode.setChecked(False)
window.receive_rx_frame(0x20, [0x03, 0xC8])
frame_item = window.rx_table.topLevelItem(0)
# Dec mode
assert "200" in frame_item.text(4)
assert not frame_item.child(1).text(4).startswith("0x")
window.chk_hex_mode.setChecked(True) # Restore