1414import threading
1515import time
1616from concurrent .futures import ProcessPoolExecutor , ThreadPoolExecutor
17+ from unittest import mock
1718
1819import pytest
1920
@@ -595,8 +596,8 @@ def cb3():
595596
596597 loop ._add_reader (c_sock .fileno (), cb1 )
597598
598- _client_task = asyncio . ensure_future (client_coro ())
599- _server_task = asyncio . ensure_future (server_coro ())
599+ _client_task = loop . create_task (client_coro ())
600+ _server_task = loop . create_task (server_coro ())
600601
601602 both_done = asyncio .gather (client_done , server_done )
602603 loop .run_until_complete (asyncio .wait_for (both_done , timeout = 1.0 ))
@@ -640,8 +641,8 @@ async def client_coro():
640641 loop ._remove_reader (c_sock .fileno ())
641642 assert (await loop .sock_recv (c_sock , 3 )) == b"foo"
642643
643- client_done = asyncio . ensure_future (client_coro ())
644- server_done = asyncio . ensure_future (server_coro ())
644+ client_done = loop . create_task (client_coro ())
645+ server_done = loop . create_task (server_coro ())
645646
646647 both_done = asyncio .wait (
647648 [server_done , client_done ], return_when = asyncio .FIRST_EXCEPTION
@@ -758,7 +759,7 @@ def exct_handler(loop, data):
758759 handler_called = True
759760
760761 loop .set_exception_handler (exct_handler )
761- asyncio . ensure_future (future_except ())
762+ loop . create_task (future_except ())
762763 loop .run_forever ()
763764
764765 assert coro_run
@@ -775,7 +776,11 @@ def exct_handler(loop, data):
775776 loop .set_exception_handler (exct_handler )
776777 fut1 = asyncio .Future ()
777778 fut1 .set_exception (ExceptionTester ())
778- asyncio .ensure_future (fut1 )
779+
780+ async def coro (future ):
781+ await future
782+
783+ loop .create_task (coro (fut1 ))
779784 del fut1
780785 loop .call_later (0.1 , loop .stop )
781786 loop .run_forever ()
@@ -798,6 +803,8 @@ def test_async_slot(loop):
798803 no_args_called = asyncio .Event ()
799804 with_args_called = asyncio .Event ()
800805 trailing_args_called = asyncio .Event ()
806+ error_called = asyncio .Event ()
807+ cancel_called = asyncio .Event ()
801808
802809 async def slot_no_args ():
803810 no_args_called .set ()
@@ -812,6 +819,14 @@ async def slot_trailing_args(flag: bool):
812819
813820 async def slot_signature_mismatch (_ : bool ): ...
814821
822+ async def slot_with_error ():
823+ error_called .set ()
824+ raise ValueError ("Test" )
825+
826+ async def slot_with_cancel ():
827+ cancel_called .set ()
828+ raise asyncio .CancelledError ()
829+
815830 async def main ():
816831 # passing kwargs to the underlying Slot such as name, arguments, return
817832 sig = qasync ._make_signaller (qasync .QtCore )
@@ -836,6 +851,73 @@ async def main():
836851 )
837852 await asyncio .wait_for (all_done , timeout = 1.0 )
838853
854+ with mock .patch .object (sys , "excepthook" ) as excepthook :
855+ sig3 = qasync ._make_signaller (qasync .QtCore )
856+ sig3 .signal .connect (qasync .asyncSlot ()(slot_with_error ))
857+ sig3 .signal .emit ()
858+ await asyncio .wait_for (error_called .wait (), timeout = 1.0 )
859+ excepthook .assert_called_once ()
860+ assert isinstance (excepthook .call_args [0 ][1 ], ValueError )
861+
862+ with mock .patch .object (sys , "excepthook" ) as excepthook :
863+ sig4 = qasync ._make_signaller (qasync .QtCore )
864+ sig4 .signal .connect (qasync .asyncSlot ()(slot_with_cancel ))
865+ sig4 .signal .emit ()
866+ await asyncio .wait_for (cancel_called .wait (), timeout = 1.0 )
867+ excepthook .assert_not_called ()
868+
869+ loop .run_until_complete (main ())
870+
871+
872+ def test_async_close (loop , application ):
873+ close_called = asyncio .Event ()
874+ close_err_called = asyncio .Event ()
875+ close_hang_called = asyncio .Event ()
876+
877+ @qasync .asyncClose
878+ async def close ():
879+ close_called .set ()
880+ return 33
881+
882+ @qasync .asyncClose
883+ async def close_err ():
884+ close_err_called .set ()
885+ raise ValueError ("Test" )
886+
887+ @qasync .asyncClose
888+ async def close_hang ():
889+ # do an actual cancel instead of directly raising, for completeness.
890+ current = asyncio .current_task ()
891+ assert current is not None
892+
893+ async def killer ():
894+ await asyncio .sleep (0.001 )
895+ current .cancel ()
896+
897+ asyncio .create_task (killer ())
898+ close_hang_called .set ()
899+ await asyncio .Event ().wait ()
900+ assert False , "Should have been cancelled"
901+
902+ # need to run in async context to have a running event loop
903+ async def main ():
904+ # close() is a synchronous top level call, need
905+ # to wrap it to be able to enter event loop
906+
907+ # test that a regular close works
908+ assert await qasync .asyncWrap (close ) == 33
909+ assert close_called .is_set ()
910+
911+ # test that an exception in the async close is propagated
912+ with pytest .raises (ValueError ) as err :
913+ await qasync .asyncWrap (close_err )
914+ assert err .value .args [0 ] == "Test"
915+ assert close_err_called .is_set ()
916+
917+ # test that a CancelledError is not propagated
918+ assert await qasync .asyncWrap (close_hang ) is None
919+ assert close_hang_called .is_set ()
920+
839921 loop .run_until_complete (main ())
840922
841923
@@ -915,6 +997,14 @@ async def coro():
915997 assert loop .run_until_complete (asyncio .wait_for (coro (), timeout = 1 )) == 42
916998
917999
1000+ def test_run_until_complete_future (loop ):
1001+ """Test that run_until_complete accepts futures"""
1002+
1003+ fut = asyncio .Future ()
1004+ loop .call_soon (lambda : fut .set_result (42 ))
1005+ assert loop .run_until_complete (fut ) == 42
1006+
1007+
9181008def test_run_forever_custom_exit_code (loop , application ):
9191009 if hasattr (application , "exec" ):
9201010 orig_exec = application .exec
@@ -932,6 +1022,24 @@ def test_run_forever_custom_exit_code(loop, application):
9321022 application .exec_ = orig_exec
9331023
9341024
1025+ def test_loop_non_reentrant (loop ):
1026+ async def noop ():
1027+ pass
1028+
1029+ async def task ():
1030+ t = loop .create_task (noop ())
1031+ with pytest .raises (RuntimeError ):
1032+ loop .run_forever ()
1033+
1034+ with pytest .raises (RuntimeError ):
1035+ loop .run_until_complete (t )
1036+ return 43
1037+
1038+ t = loop .create_task (task ())
1039+ loop .run_until_complete (t )
1040+ assert t .result () == 43
1041+
1042+
9351043@pytest .mark .parametrize ("qtparent" , [False , True ])
9361044def test_qeventloop_in_qthread (qtparent ):
9371045 class CoroutineExecutorThread (qasync .QtCore .QThread ):
0 commit comments