1414import threading
1515import time
1616from concurrent .futures import ProcessPoolExecutor , ThreadPoolExecutor
17+ from unittest import mock
1718
1819import pytest
1920
@@ -801,6 +802,8 @@ def test_async_slot(loop):
801802 no_args_called = asyncio .Event ()
802803 with_args_called = asyncio .Event ()
803804 trailing_args_called = asyncio .Event ()
805+ error_called = asyncio .Event ()
806+ cancel_called = asyncio .Event ()
804807
805808 async def slot_no_args ():
806809 no_args_called .set ()
@@ -815,6 +818,14 @@ async def slot_trailing_args(flag: bool):
815818
816819 async def slot_signature_mismatch (_ : bool ): ...
817820
821+ async def slot_with_error ():
822+ error_called .set ()
823+ raise ValueError ("Test" )
824+
825+ async def slot_with_cancel ():
826+ cancel_called .set ()
827+ raise asyncio .CancelledError ()
828+
818829 async def main ():
819830 # passing kwargs to the underlying Slot such as name, arguments, return
820831 sig = qasync ._make_signaller (qasync .QtCore )
@@ -839,6 +850,73 @@ async def main():
839850 )
840851 await asyncio .wait_for (all_done , timeout = 1.0 )
841852
853+ with mock .patch .object (sys , "excepthook" ) as excepthook :
854+ sig3 = qasync ._make_signaller (qasync .QtCore )
855+ sig3 .signal .connect (qasync .asyncSlot ()(slot_with_error ))
856+ sig3 .signal .emit ()
857+ await asyncio .wait_for (error_called .wait (), timeout = 1.0 )
858+ excepthook .assert_called_once ()
859+ assert isinstance (excepthook .call_args [0 ][1 ], ValueError )
860+
861+ with mock .patch .object (sys , "excepthook" ) as excepthook :
862+ sig4 = qasync ._make_signaller (qasync .QtCore )
863+ sig4 .signal .connect (qasync .asyncSlot ()(slot_with_cancel ))
864+ sig4 .signal .emit ()
865+ await asyncio .wait_for (cancel_called .wait (), timeout = 1.0 )
866+ excepthook .assert_not_called ()
867+
868+ loop .run_until_complete (main ())
869+
870+
871+ def test_async_close (loop , application ):
872+ close_called = asyncio .Event ()
873+ close_err_called = asyncio .Event ()
874+ close_hang_called = asyncio .Event ()
875+
876+ @qasync .asyncClose
877+ async def close ():
878+ close_called .set ()
879+ return 33
880+
881+ @qasync .asyncClose
882+ async def close_err ():
883+ close_err_called .set ()
884+ raise ValueError ("Test" )
885+
886+ @qasync .asyncClose
887+ async def close_hang ():
888+ # do an actual cancel instead of directly raising, for completeness.
889+ current = asyncio .current_task ()
890+ assert current is not None
891+
892+ async def killer ():
893+ await asyncio .sleep (0.001 )
894+ current .cancel ()
895+
896+ asyncio .create_task (killer ())
897+ close_hang_called .set ()
898+ await asyncio .Event ().wait ()
899+ assert False , "Should have been cancelled"
900+
901+ # need to run in async context to have a running event loop
902+ async def main ():
903+ # close() is a synchronous top level call, need
904+ # to wrap it to be able to enter event loop
905+
906+ # test that a regular close works
907+ assert await qasync .asyncWrap (close ) == 33
908+ assert close_called .is_set ()
909+
910+ # test that an exception in the async close is propagated
911+ with pytest .raises (ValueError ) as err :
912+ await qasync .asyncWrap (close_err )
913+ assert err .value .args [0 ] == "Test"
914+ assert close_err_called .is_set ()
915+
916+ # test that a CancelledError is not propagated
917+ assert qasync .asyncWrap (close_hang ) is None
918+ assert close_hang_called .is_set ()
919+
842920 loop .run_until_complete (main ())
843921
844922
0 commit comments