1
0

tests: explicit sync on self.app instance ready and make sure _testcase_event is cleared before

QMetaObject.invokeMethod as that can race if it gets inadvertently executed synchronously.
This commit is contained in:
Sander van Grieken
2024-02-16 16:53:39 +01:00
parent 73fee69f5c
commit 0faf6928c0

View File

@@ -4,7 +4,7 @@ import unittest
from functools import wraps, partial from functools import wraps, partial
from unittest import SkipTest from unittest import SkipTest
from PyQt6.QtCore import QCoreApplication, QTimer, QMetaObject, Qt, pyqtSlot, QObject from PyQt6.QtCore import QCoreApplication, QMetaObject, Qt, pyqtSlot, QObject
class TestQCoreApplication(QCoreApplication): class TestQCoreApplication(QCoreApplication):
@@ -33,42 +33,48 @@ class QEventReceiver(QObject):
self.received.clear() self.received.clear()
class QETestCase(unittest.IsolatedAsyncioTestCase): class QETestCase(unittest.TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.app = None self.app = None
self._e = None self._e = None
self._event = threading.Event() self._testcase_event = threading.Event()
self._app_ready_event = threading.Event()
def start_qt_task(): def start_qt_task():
self.app = TestQCoreApplication([]) try:
assert self.app is None
self.app = TestQCoreApplication([])
self._app_ready_event.set()
self.app.exec()
self.app = None
except Exception as e:
print(f'Problem starting QCoreApplication: {str(e)}')
# self.timer = QTimer(self.app) self._qt_thread = threading.Thread(target=start_qt_task)
# self.timer.setSingleShot(False) self._qt_thread.start()
# self.timer.setInterval(500) # msec
# self.timer.timeout.connect(lambda: None) # periodically enter python scope
self.app.exec()
self.app = None
self.qt_thread = threading.Thread(target=start_qt_task)
self.qt_thread.start()
def tearDown(self): def tearDown(self):
self.app.exit() self.app.exit()
if self.qt_thread.is_alive(): if self._qt_thread.is_alive():
self.qt_thread.join() self._qt_thread.join()
def qt_test(func): def qt_test(func):
@wraps(func) @wraps(func)
def decorator(self, *args): def decorator(self, *args):
if threading.current_thread().name == 'MainThread': if threading.current_thread().name == 'MainThread':
res = self._app_ready_event.wait(3)
if not res:
raise Exception('app not ready in time')
self._testcase_event.clear()
self.app._instance = self self.app._instance = self
self.app._method = func.__name__ self.app._method = func.__name__
QMetaObject.invokeMethod(self.app, 'doInvoke', Qt.ConnectionType.QueuedConnection) QMetaObject.invokeMethod(self.app, 'doInvoke', Qt.ConnectionType.QueuedConnection)
self._event.wait(15) res = self._testcase_event.wait(15)
if not res:
self._e = Exception('testcase timed out')
if self._e: if self._e:
print("".join(traceback.format_exception(self._e))) print("".join(traceback.format_exception(self._e)))
# deallocate stored exception from qt thread otherwise we SEGV garbage collector # deallocate stored exception from qt thread otherwise we SEGV garbage collector
@@ -88,6 +94,5 @@ def qt_test(func):
except Exception as e: except Exception as e:
self._e = e self._e = e
finally: finally:
self._event.set() self._testcase_event.set()
self._event.clear()
return decorator return decorator