1
0
Files
electrum/tests/qt_util.py
SomberNight 7e29214219 daemon error-handling: fix traceback.format_exception() on old python
The new API for traceback.format_exception was only added in python 3.10 (91e93794d5).
2024-10-14 15:00:10 +00:00

101 lines
3.2 KiB
Python

import threading
import traceback
import unittest
from functools import wraps, partial
from unittest import SkipTest
from PyQt6.QtCore import QCoreApplication, QMetaObject, Qt, pyqtSlot, QObject
from electrum.util import traceback_format_exception
class TestQCoreApplication(QCoreApplication):
@pyqtSlot()
def doInvoke(self):
getattr(self._instance, self._method)()
class QEventReceiver(QObject):
def __init__(self, *signals):
super().__init__()
self.received = []
self.signals = []
for signal in signals:
self.signals.append(signal)
signal.connect(partial(self.doReceive, signal))
# intentionally no pyqtSlot decorator, to catch all
def doReceive(self, signal, *args):
self.received.append((signal, args))
def receivedForSignal(self, signal):
return list(filter(lambda x: x[0] == signal, self.received))
def clear(self):
self.received.clear()
class QETestCase(unittest.TestCase):
def setUp(self):
super().setUp()
self.app = None
self._e = None
self._testcase_event = threading.Event()
self._app_ready_event = threading.Event()
def start_qt_task():
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._qt_thread = threading.Thread(target=start_qt_task)
self._qt_thread.start()
def tearDown(self):
self.app.exit()
if self._qt_thread.is_alive():
self._qt_thread.join()
def qt_test(func):
@wraps(func)
def decorator(self, *args):
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._method = func.__name__
QMetaObject.invokeMethod(self.app, 'doInvoke', Qt.ConnectionType.QueuedConnection)
res = self._testcase_event.wait(15)
if not res:
self._e = Exception('testcase timed out')
if self._e:
print("".join(traceback_format_exception(self._e)))
# deallocate stored exception from qt thread otherwise we SEGV garbage collector
# instead, re-create using the exception message, special casing AssertionError and SkipTest
e = None
if isinstance(self._e, AssertionError):
e = AssertionError(str(self._e))
elif isinstance(self._e, SkipTest):
e = SkipTest(str(self._e))
else:
e = Exception(str(self._e))
self._e = None
raise e
return
try:
func(self, *args)
except Exception as e:
self._e = e
finally:
self._testcase_event.set()
return decorator