0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 11:11:49 +02:00

fix: mock QTimer, improve timeout message

This commit is contained in:
2025-02-06 14:37:03 +01:00
parent 8aba3d975f
commit fb051865d5
3 changed files with 64 additions and 26 deletions

46
tests/conftest.py Normal file
View File

@ -0,0 +1,46 @@
import pytest
import qtpy.QtCore
from pytestqt.exceptions import TimeoutError as QtBotTimeoutError
from qtpy.QtCore import QTimer
class TestableQTimer(QTimer):
_instances: list[tuple[QTimer, str]] = []
_current_test_name: str = ""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
TestableQTimer._instances.append((self, TestableQTimer._current_test_name))
@classmethod
def check_all_stopped(cls, qtbot):
def _is_done_or_deleted(t: QTimer):
try:
return not t.isActive()
except RuntimeError as e:
return "already deleted" in e.args[0]
try:
qtbot.waitUntil(lambda: all(_is_done_or_deleted(timer) for timer, _ in cls._instances))
except QtBotTimeoutError as exc:
active_timers = list(filter(lambda t: t[0].isActive(), cls._instances))
(t.stop() for t, _ in cls._instances)
raise TimeoutError(f"Failed to stop all timers: {active_timers}") from exc
cls._instances = []
# To support 'from qtpy.QtCore import QTimer' syntax we just replace this completely for the test session
# see: https://docs.python.org/3/library/unittest.mock.html#where-to-patch
qtpy.QtCore.QTimer = TestableQTimer
@pytest.fixture(autouse=True)
def _capture_test_name_in_qtimer(request):
TestableQTimer._current_test_name = request.node.name
yield
TestableQTimer._current_test_name = ""
@pytest.fixture
def testable_qtimer_class():
return TestableQTimer

View File

@ -2,7 +2,6 @@ from unittest import mock
import pytest
from pytestqt.exceptions import TimeoutError as QtBotTimeoutError
from qtpy.QtCore import QTimer
from qtpy.QtWidgets import QApplication
from bec_widgets.cli.rpc.rpc_register import RPCRegister
@ -10,22 +9,6 @@ from bec_widgets.qt_utils import error_popups
from bec_widgets.utils import bec_dispatcher as bec_dispatcher_module
class TestableQTimer(QTimer):
_instances = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
TestableQTimer._instances.append(self)
@classmethod
def check_all_stopped(cls, qtbot):
try:
qtbot.waitUntil(lambda: all(not timer.isActive() for timer in cls._instances))
except QtBotTimeoutError as exc:
raise TimeoutError("Failed to stop all timers") from exc
cls._instances = []
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
@ -36,8 +19,7 @@ def pytest_runtest_makereport(item, call):
@pytest.fixture(autouse=True)
def qapplication(qtbot, request): # pylint: disable=unused-argument
with mock.patch("qtpy.QtCore.QTimer", new=TestableQTimer):
def qapplication(qtbot, request, testable_qtimer_class): # pylint: disable=unused-argument
yield
# if the test failed, we don't want to check for open widgets as
@ -46,7 +28,7 @@ def qapplication(qtbot, request): # pylint: disable=unused-argument
print("Test failed, skipping cleanup checks")
return
TestableQTimer.check_all_stopped(qtbot)
testable_qtimer_class.check_all_stopped(qtbot)
qapp = QApplication.instance()
qapp.processEvents()

View File

@ -0,0 +1,10 @@
from unittest.mock import MagicMock
from bec_widgets.widgets.services.bec_status_box.bec_status_box import BECServiceStatusMixin
def test_qtimer_uses_testable_qtimer():
service_status = BECServiceStatusMixin(None, MagicMock())
assert service_status._service_update_timer.__class__.__name__ != "QTimer"
assert service_status._service_update_timer.__class__.__name__ == "TestableQTimer"
service_status.cleanup()