Files
radar_system/python_app/tests/test_control_button_controller.py
T
2026-10-06 12:32:26 +03:00

85 lines
3.5 KiB
Python

"""GPIO startup failures are optional; queued presses cannot survive shutdown."""
import threading
import os
import unittest
from unittest import mock
from PyQt6.QtCore import QObject, pyqtSignal
from PyQt6.QtWidgets import QApplication
from python_app.gui.controllers.control_button_controller import ControlButtonController
from python_app.models.run_config_schema import ControlButtonModel
from python_app.tests.gui_test_support import isolated_window
class _Watcher(QObject):
pressed = pyqtSignal()
failed = pyqtSignal(str)
def __init__(self, *, parent: QObject, **_kwargs: object) -> None:
super().__init__(parent)
self.stopped = False
def start(self) -> None:
pass
def stop(self) -> None:
self.stopped = True
self.pressed.emit()
class ControlButtonControllerTest(unittest.TestCase):
def setUp(self) -> None:
self.app = QApplication.instance() or QApplication([])
self.config = ControlButtonModel(enabled=True, pin=17)
self.capture = mock.Mock()
self.parent = QObject()
self.controller = ControlButtonController(self.parent, lambda: self.config, self.capture, mock.MagicMock())
self.addCleanup(self.controller.close)
def test_close_rejects_already_queued_and_stop_time_presses(self) -> None:
with mock.patch("python_app.gui.controllers.control_button_controller.ControlButtonWatcher", _Watcher):
self.controller.start()
watcher = self.controller._watcher
assert isinstance(watcher, _Watcher)
watcher.pressed.emit()
self.capture.assert_called_once()
worker = threading.Thread(target=watcher.pressed.emit)
worker.start()
worker.join(timeout=1)
self.controller.close()
self.app.processEvents()
self.assertTrue(watcher.stopped)
self.capture.assert_called_once()
self.assertIsNone(self.controller._watcher)
def test_disabled_invalid_and_unavailable_gpio_do_not_start_capture(self) -> None:
with mock.patch("python_app.gui.controllers.control_button_controller.ControlButtonWatcher") as create:
self.config.enabled = False
self.controller.start()
self.config.enabled, self.config.pin = True, -1
self.controller.start()
create.assert_not_called()
self.config.pin = 17
create.side_effect = OSError("GPIO unavailable")
self.controller.start()
self.assertIsNone(self.controller._watcher)
self.capture.assert_not_called()
def test_gpio_capture_failure_is_logged_without_a_modal_dialog(self) -> None:
window = isolated_window(self.addCleanup)
window._profile.state.config.control_button.enabled = True
window._profile.state.config.control_button.pin = 17
window._control_button._capture_reference = lambda: window._log.show_error("GPIO capture failed")
with mock.patch("python_app.gui.controllers.control_button_controller.ControlButtonWatcher", _Watcher), \
mock.patch.dict(os.environ, {"RADAR_SYSTEM_HEADLESS": "0"}), \
mock.patch("python_app.gui.controllers.log_controller.QMessageBox") as dialog, \
self.assertLogs("python_app.gui", level="ERROR") as logs:
window._control_button.start()
watcher = window._control_button._watcher
assert isinstance(watcher, _Watcher)
watcher.pressed.emit()
dialog.assert_not_called()
self.assertTrue(any("GPIO capture failed" in line for line in logs.output))