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:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user