import inspect import os import time from concurrent.futures import Future, TimeoutError import pytest from qtpy.QtCore import QCoreApplication, QObject, QThread, Signal from superqt.utils import ensure_main_thread, ensure_object_thread skip_on_ci = pytest.mark.skipif(bool(os.getenv("CI")), reason="github hangs") class SampleObject(QObject): assigment_done = Signal() def __init__(self): super().__init__() self.main_thread_res = {} self.object_thread_res = {} self.main_thread_prop_val = None self.sample_thread_prop_val = None def long_wait(self): time.sleep(1) @property def sample_main_thread_property(self): return self.main_thread_prop_val @sample_main_thread_property.setter # type: ignore @ensure_main_thread() def sample_main_thread_property(self, value): if QThread.currentThread() is not QCoreApplication.instance().thread(): raise RuntimeError("Wrong thread") self.main_thread_prop_val = value self.assigment_done.emit() @property def sample_object_thread_property(self): return self.sample_thread_prop_val @sample_object_thread_property.setter # type: ignore @ensure_object_thread() def sample_object_thread_property(self, value): if QThread.currentThread() is not self.thread(): raise RuntimeError("Wrong thread") self.sample_thread_prop_val = value self.assigment_done.emit() @ensure_main_thread def check_main_thread(self, a, *, b=1): if QThread.currentThread() is not QCoreApplication.instance().thread(): raise RuntimeError("Wrong thread") self.main_thread_res = {"a": a, "b": b} self.assigment_done.emit() @ensure_object_thread def check_object_thread(self, a, *, b=1): if QThread.currentThread() is not self.thread(): raise RuntimeError("Wrong thread") self.object_thread_res = {"a": a, "b": b} self.assigment_done.emit() @ensure_object_thread(await_return=True) def check_object_thread_return(self, a): if QThread.currentThread() is not self.thread(): raise RuntimeError("Wrong thread") return a * 7 @ensure_object_thread(await_return=True, timeout=200) def check_object_thread_return_timeout(self, a): if QThread.currentThread() is not self.thread(): raise RuntimeError("Wrong thread") time.sleep(1) return a * 7 @ensure_object_thread(await_return=False) def check_object_thread_return_future(self, a: int): """sample docstring""" if QThread.currentThread() is not self.thread(): raise RuntimeError("Wrong thread") time.sleep(0.4) return a * 7 @ensure_main_thread(await_return=True) def check_main_thread_return(self, a): if QThread.currentThread() is not QCoreApplication.instance().thread(): raise RuntimeError("Wrong thread") return a * 8 class LocalThread(QThread): def __init__(self, ob): super().__init__() self.ob = ob def run(self): assert QThread.currentThread() is not QCoreApplication.instance().thread() self.ob.check_main_thread(5, b=8) self.ob.main_thread_prop_val = "text2" class LocalThread2(QThread): def __init__(self, ob): super().__init__() self.ob = ob self.executed = False def run(self): assert QThread.currentThread() is not QCoreApplication.instance().thread() assert self.ob.check_main_thread_return(5) == 40 self.executed = True def test_only_main_thread(qapp): ob = SampleObject() ob.check_main_thread(1, b=3) assert ob.main_thread_res == {"a": 1, "b": 3} ob.check_object_thread(2, b=4) assert ob.object_thread_res == {"a": 2, "b": 4} ob.sample_main_thread_property = 5 assert ob.sample_main_thread_property == 5 ob.sample_object_thread_property = 7 assert ob.sample_object_thread_property == 7 def test_main_thread(qtbot): ob = SampleObject() t = LocalThread(ob) with qtbot.waitSignal(t.finished): t.start() assert ob.main_thread_res == {"a": 5, "b": 8} assert ob.sample_main_thread_property == "text2" def test_main_thread_return(qtbot): ob = SampleObject() t = LocalThread2(ob) with qtbot.wait_signal(t.finished): t.start() assert t.executed def test_names(qapp): ob = SampleObject() assert ob.check_object_thread.__name__ == "check_object_thread" assert ob.check_object_thread_return.__name__ == "check_object_thread_return" assert ( ob.check_object_thread_return_timeout.__name__ == "check_object_thread_return_timeout" ) assert ( ob.check_object_thread_return_future.__name__ == "check_object_thread_return_future" ) assert ob.check_object_thread_return_future.__doc__ == "sample docstring" signature = inspect.signature(ob.check_object_thread_return_future) assert len(signature.parameters) == 1 assert list(signature.parameters.values())[0].name == "a" assert list(signature.parameters.values())[0].annotation == int assert ob.check_main_thread_return.__name__ == "check_main_thread_return" @skip_on_ci def test_object_thread_return(qtbot): ob = SampleObject() thread = QThread() thread.start() ob.moveToThread(thread) assert ob.check_object_thread_return(2) == 14 assert ob.thread() is thread with qtbot.waitSignal(thread.finished): thread.quit() @skip_on_ci def test_object_thread_return_timeout(qtbot): ob = SampleObject() thread = QThread() thread.start() ob.moveToThread(thread) with pytest.raises(TimeoutError): ob.check_object_thread_return_timeout(2) with qtbot.waitSignal(thread.finished): thread.quit() @skip_on_ci def test_object_thread_return_future(qtbot): ob = SampleObject() thread = QThread() thread.start() ob.moveToThread(thread) future = ob.check_object_thread_return_future(2) assert isinstance(future, Future) assert future.result() == 14 with qtbot.waitSignal(thread.finished): thread.quit() @skip_on_ci def test_object_thread(qtbot): ob = SampleObject() thread = QThread() thread.start() ob.moveToThread(thread) with qtbot.waitSignal(ob.assigment_done): ob.check_object_thread(2, b=4) assert ob.object_thread_res == {"a": 2, "b": 4} with qtbot.waitSignal(ob.assigment_done): ob.sample_object_thread_property = "text" assert ob.sample_object_thread_property == "text" assert ob.thread() is thread with qtbot.waitSignal(thread.finished): thread.quit()