Lin_Simulator/python/tests/test_integration.py
Mohamed Salem 251f5d327e Steps 7-8: Master scheduler and end-to-end integration
Step 7 - Master Scheduler (Python + C++):
- QTimer-based schedule execution with start/stop/pause/resume
- Frame sent callback with light-blue visual highlighting
- Mock Rx simulation with incrementing counter data
- Manual send button for individual frame injection
- Global rate spinbox with live update during run
- Schedule table switching

Step 8 - Integration (Python):
- BabyLinBackend wired into MainWindow
- Global rate spinbox live-updates scheduler
- End-to-end tests: load → edit signals → run → Rx arrives → stop
- Edge case tests: no LDF, no selection, double stop
- Full workflow verified with 182 Python tests

Tests: Python 182 | C++ 124

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

203 lines
6.2 KiB
Python

"""
test_integration.py — Step 8: End-to-end integration tests.
Tests the full workflow:
Load LDF → edit signals → start scheduler → see Rx → stop
Verifies all components work together correctly.
"""
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.QtTest import QTest
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()
return w
class TestFullWorkflow:
"""Test the complete user workflow from start to finish."""
def test_load_edit_run_stop(self, window):
"""
Complete flow:
1. Load LDF
2. Edit a signal value
3. Start scheduler
4. Verify Rx data arrives
5. Stop scheduler
"""
# 1. Load LDF
window._load_ldf_file(SAMPLE_LDF)
assert window._ldf_data is not None
assert window.tx_table.topLevelItemCount() == 2
assert window.rx_table.topLevelItemCount() == 2
# 2. Edit a signal — set MotorSpeed to 200
frame_item = window.tx_table.topLevelItem(0) # Motor_Command
speed_sig = frame_item.child(2) # MotorSpeed
speed_sig.setText(4, "200")
# Verify frame bytes updated
frame_data = frame_item.data(0, Qt.ItemDataRole.UserRole)
assert frame_data['bytes'][1] == 200
# 3. Start scheduler
window.combo_schedule.setCurrentIndex(0)
window._on_start_scheduler()
assert window._scheduler.is_running
# 4. Wait for Rx data
QTest.qWait(100)
# Verify Rx timestamps appeared
has_rx = False
for i in range(window.rx_table.topLevelItemCount()):
if window.rx_table.topLevelItem(i).text(0) != "":
has_rx = True
break
assert has_rx, "Should have received mock Rx data"
# 5. Stop
window._on_stop_scheduler()
assert not window._scheduler.is_running
def test_hex_dec_toggle_during_run(self, window):
"""Toggle hex/dec while scheduler is running."""
window._load_ldf_file(SAMPLE_LDF)
window.combo_schedule.setCurrentIndex(0)
window._on_start_scheduler()
QTest.qWait(50)
# Toggle to decimal
window.chk_hex_mode.setChecked(False)
QTest.qWait(50)
# Toggle back to hex
window.chk_hex_mode.setChecked(True)
QTest.qWait(50)
window._on_stop_scheduler()
# No crash = pass
def test_pause_resume_during_run(self, window):
"""Pause and resume the scheduler."""
window._load_ldf_file(SAMPLE_LDF)
window.combo_schedule.setCurrentIndex(0)
window._on_start_scheduler()
QTest.qWait(50)
# Pause
window._on_pause_scheduler()
assert window._scheduler.is_paused
QTest.qWait(50)
# Resume
window._on_pause_scheduler()
assert not window._scheduler.is_paused
QTest.qWait(50)
window._on_stop_scheduler()
def test_clear_rx_during_run(self, window):
"""Clear Rx data while scheduler is running."""
window._load_ldf_file(SAMPLE_LDF)
window.combo_schedule.setCurrentIndex(0)
window._on_start_scheduler()
QTest.qWait(100)
window._on_clear_rx()
# All timestamps should be reset
for i in range(window.rx_table.topLevelItemCount()):
assert window.rx_table.topLevelItem(i).text(0) == ""
# But scheduler should still be running
assert window._scheduler.is_running
window._on_stop_scheduler()
def test_change_global_rate_live(self, window):
"""Change global rate while scheduler is running."""
window._load_ldf_file(SAMPLE_LDF)
window.combo_schedule.setCurrentIndex(0)
window._on_start_scheduler()
QTest.qWait(50)
window.spin_global_rate.setValue(10)
QTest.qWait(50)
window.spin_global_rate.setValue(100)
QTest.qWait(50)
window._on_stop_scheduler()
def test_reload_ldf_stops_scheduler(self, window):
"""Reloading LDF while scheduler runs should stop first."""
window._load_ldf_file(SAMPLE_LDF)
window.combo_schedule.setCurrentIndex(0)
window._on_start_scheduler()
QTest.qWait(50)
# Reload — scheduler should handle this gracefully
window._load_ldf_file(SAMPLE_LDF)
# Tables should still be populated
assert window.tx_table.topLevelItemCount() == 2
class TestBackendIntegration:
"""Test BabyLinBackend integration (mock mode)."""
def test_backend_exists(self, window):
assert window._backend is not None
assert window._backend.is_mock_mode
def test_backend_scan_from_gui(self, window):
"""Backend scan should return mock device."""
devices = window._backend.scan_devices()
assert len(devices) >= 1
class TestEdgeCase:
"""Test edge cases and error conditions."""
def test_start_without_ldf(self, window, monkeypatch):
"""Starting scheduler without LDF loaded should not crash."""
from PyQt6.QtWidgets import QMessageBox
monkeypatch.setattr(QMessageBox, "warning", lambda *args: None)
window._on_start_scheduler()
assert not window._scheduler.is_running
def test_manual_send_without_selection(self, window, monkeypatch):
"""Manual send without selecting a frame should warn."""
from PyQt6.QtWidgets import QMessageBox
warned = []
monkeypatch.setattr(QMessageBox, "warning",
lambda *args: warned.append(True))
window._load_ldf_file(SAMPLE_LDF)
window._on_manual_send()
assert len(warned) > 0
def test_double_stop(self, window):
"""Stopping when already stopped should not crash."""
window._load_ldf_file(SAMPLE_LDF)
window._on_stop_scheduler()
window._on_stop_scheduler() # No crash