3030_IS_SYNC = False
3131
3232
33- def _make_executor (interval = 30.0 , min_interval = 0.01 , target = None , name = "test" ):
34- if target is None :
33+ class TestAsyncPeriodicExecutor (AsyncUnitTest ):
34+ def _make_executor (self , interval = 30.0 , min_interval = 0.01 , target = None , name = "test" ):
35+ if target is None :
3536
36- async def target ():
37- return True
38-
39- return AsyncPeriodicExecutor (
40- interval = interval , min_interval = min_interval , target = target , name = name
41- )
42-
43-
44- class AsyncPeriodicExecutorTestBase (AsyncUnitTest ):
45- async def asyncSetUp (self ):
46- self .executor = None
47-
48- async def asyncTearDown (self ):
49- if self .executor is not None :
50- self .executor .close ()
51- await self .executor .join (timeout = 2 )
37+ async def target ():
38+ return True
5239
40+ executor = AsyncPeriodicExecutor (
41+ interval = interval , min_interval = min_interval , target = target , name = name
42+ )
43+ self .addAsyncCleanup (self ._close_executor , executor )
44+ return executor
5345
54- class TestAsyncPeriodicExecutor (AsyncPeriodicExecutorTestBase ):
55- async def test_repr_contains_class_and_name (self ):
56- executor = _make_executor (name = "exec" )
57- executor_repr = repr (executor )
58- self .assertIn ("AsyncPeriodicExecutor" , executor_repr )
59- self .assertIn ("exec" , executor_repr )
46+ async def _close_executor (self , executor ):
47+ executor .close ()
48+ await executor .join (timeout = 2 )
6049
6150 async def test_join_without_open_is_safe (self ):
62- self . executor = _make_executor ()
51+ executor = self . _make_executor ()
6352 try :
64- await self . executor .join (timeout = 0.01 )
53+ await executor .join (timeout = 0.01 )
6554 except Exception as e :
6655 self .fail (f"join() raised unexpected Exception { e } " )
6756
@@ -75,47 +64,31 @@ async def target():
7564 ran .set ()
7665 return False
7766
78- self . executor = _make_executor (target = target )
79- self . executor .open ()
80- await self . executor .join (timeout = 2 )
67+ executor = self . _make_executor (target = target )
68+ executor .open ()
69+ await executor .join (timeout = 2 )
8170 self .assertTrue (ran .is_set (), "target never ran" )
8271
8372 async def test_target_exception_stops_executor (self ):
84- if _IS_SYNC :
85- ran = threading .Event ()
86- captured_exc : list = []
87- orig_excepthook = threading .excepthook
88-
89- def _capture_excepthook (args ):
90- captured_exc .append (args .exc_value )
91-
92- threading .excepthook = _capture_excepthook
93- try :
94-
95- def target ():
96- ran .set ()
97- raise RuntimeError ("error" )
98-
99- self .executor = _make_executor (target = target )
100- self .executor .open ()
101- self .executor .join (timeout = 2 )
102- self .assertTrue (ran .is_set (), "target never ran" )
103- finally :
104- threading .excepthook = orig_excepthook
105- self .assertEqual (len (captured_exc ), 1 )
106- self .assertIsInstance (captured_exc [0 ], RuntimeError )
107- else :
108- call_count = 0
109-
110- async def target ():
111- nonlocal call_count
112- call_count += 1
113- raise RuntimeError ("error" )
73+ call_count = 0
11474
115- self .executor = _make_executor (target = target )
116- self .executor .open ()
117- await self .executor .join (timeout = 2 )
118- self .assertEqual (call_count , 1 , "target should stop after exception" )
75+ async def target ():
76+ nonlocal call_count
77+ call_count += 1
78+ raise RuntimeError ("error" )
79+
80+ executor = self ._make_executor (target = target )
81+ # Suppress threading.excepthook so the intentional RuntimeError raised
82+ # in the worker thread is not surfaced by pytest's threadexception
83+ # plugin (no-op for the async executor since no thread is involved).
84+ orig_excepthook = threading .excepthook
85+ threading .excepthook = lambda _args : None
86+ try :
87+ executor .open ()
88+ await executor .join (timeout = 2 )
89+ finally :
90+ threading .excepthook = orig_excepthook
91+ self .assertEqual (call_count , 1 , "target should stop after exception" )
11992
12093 async def test_skip_sleep_flag_skips_interval (self ):
12194 call_times = []
@@ -126,10 +99,10 @@ async def target():
12699 return False
127100 return True
128101
129- self . executor = _make_executor (interval = 30.0 , min_interval = 0.001 , target = target )
130- self . executor .skip_sleep ()
131- self . executor .open ()
132- await self . executor .join (timeout = 3 )
102+ executor = self . _make_executor (interval = 30.0 , min_interval = 0.001 , target = target )
103+ executor .skip_sleep ()
104+ executor .open ()
105+ await executor .join (timeout = 3 )
133106 self .assertGreaterEqual (len (call_times ), 2 )
134107 self .assertLess (call_times [1 ] - call_times [0 ], 5.0 )
135108
@@ -147,15 +120,15 @@ async def target():
147120 woken .set ()
148121 return call_count < 2
149122
150- self . executor = _make_executor (interval = 30.0 , min_interval = 0.01 , target = target )
151- self . executor .open ()
123+ executor = self . _make_executor (interval = 30.0 , min_interval = 0.01 , target = target )
124+ executor .open ()
152125 if _IS_SYNC :
153126 woken .wait (timeout = 2 )
154127 else :
155128 assert isinstance (woken , asyncio .Event )
156129 await asyncio .wait_for (woken .wait (), timeout = 2 )
157- self . executor .wake ()
158- await self . executor .join (timeout = 3 )
130+ executor .wake ()
131+ await executor .join (timeout = 3 )
159132 self .assertGreaterEqual (call_count , 2 )
160133
161134 async def test_open_after_target_returns_false (self ):
@@ -166,11 +139,11 @@ async def target():
166139 called += 1
167140 return False
168141
169- self . executor = _make_executor (target = target )
170- self . executor .open ()
171- await self . executor .join (timeout = 2 )
172- self . executor .open ()
173- await self . executor .join (timeout = 2 )
142+ executor = self . _make_executor (target = target )
143+ executor .open ()
144+ await executor .join (timeout = 2 )
145+ executor .open ()
146+ await executor .join (timeout = 2 )
174147 self .assertGreaterEqual (called , 2 )
175148
176149
0 commit comments