4
4
5
5
import pytest
6
6
7
- import trio .lowlevel
7
+ import trio
8
+ from trio .lowlevel import (
9
+ add_parking_lot_breaker ,
10
+ current_task ,
11
+ remove_parking_lot_breaker ,
12
+ )
8
13
from trio .testing import Matcher , RaisesGroup
9
14
10
15
from ... import _core
@@ -220,27 +225,34 @@ async def test_parking_lot_repark_with_count() -> None:
220
225
lot1 .unpark_all ()
221
226
222
227
228
+ async def dummy_task (
229
+ task_status : _core .TaskStatus [_core .Task ] = trio .TASK_STATUS_IGNORED ,
230
+ ) -> None :
231
+ task_status .started (_core .current_task ())
232
+ await trio .sleep_forever ()
233
+
234
+
223
235
async def test_parking_lot_breaker_basic () -> None :
224
236
lot = ParkingLot ()
225
- task = trio . lowlevel . current_task ()
237
+ task = current_task ()
226
238
227
239
with pytest .raises (
228
240
RuntimeError ,
229
241
match = "Attempted to remove task as breaker for a lot it is not registered for" ,
230
242
):
231
- trio . lowlevel . remove_parking_lot_breaker (task , lot )
243
+ remove_parking_lot_breaker (task , lot )
232
244
233
245
# check that a task can be registered as breaker for the same lot multiple times
234
- trio . lowlevel . add_parking_lot_breaker (task , lot )
235
- trio . lowlevel . add_parking_lot_breaker (task , lot )
236
- trio . lowlevel . remove_parking_lot_breaker (task , lot )
237
- trio . lowlevel . remove_parking_lot_breaker (task , lot )
246
+ add_parking_lot_breaker (task , lot )
247
+ add_parking_lot_breaker (task , lot )
248
+ remove_parking_lot_breaker (task , lot )
249
+ remove_parking_lot_breaker (task , lot )
238
250
239
251
with pytest .raises (
240
252
RuntimeError ,
241
253
match = "Attempted to remove task as breaker for a lot it is not registered for" ,
242
254
):
243
- trio . lowlevel . remove_parking_lot_breaker (task , lot )
255
+ remove_parking_lot_breaker (task , lot )
244
256
245
257
# defaults to current task
246
258
lot .break_lot ()
@@ -249,30 +261,81 @@ async def test_parking_lot_breaker_basic() -> None:
249
261
# breaking the lot again with the same task is a no-op
250
262
lot .break_lot ()
251
263
252
- # but with a different task it gives a warning
253
- async def dummy_task (
254
- task_status : _core .TaskStatus [_core .Task ] = trio .TASK_STATUS_IGNORED ,
255
- ) -> None :
256
- task_status .started (_core .current_task ())
264
+ child_task = None
265
+ with pytest .warns (RuntimeWarning ):
266
+ async with trio .open_nursery () as nursery :
267
+ child_task = await nursery .start (dummy_task )
268
+ # registering a task as breaker on an already broken lot is fine... though it
269
+ # maybe shouldn't be as it will always cause a RuntimeWarning???
270
+ # Or is this a sign that we shouldn't raise a warning?
271
+ add_parking_lot_breaker (child_task , lot )
272
+ nursery .cancel_scope .cancel ()
273
+
274
+ # manually breaking a lot with an already exited task is fine
275
+ lot = ParkingLot ()
276
+ lot .break_lot (child_task )
277
+
278
+
279
+ async def test_parking_lot_breaker_warnings () -> None :
280
+ lot = ParkingLot ()
281
+ task = current_task ()
282
+ lot .break_lot ()
257
283
284
+ warn_str = "attempted to break parking .* already broken by .*"
285
+ # breaking an already broken lot with a different task gives a warning
258
286
# The nursery is only to create a task we can pass to lot.break_lot
259
- # and has no effect on the test otherwise.
260
287
async with trio .open_nursery () as nursery :
261
288
child_task = await nursery .start (dummy_task )
262
289
with pytest .warns (
263
290
RuntimeWarning ,
264
- match = "attempted to break parking .* already broken by .*" ,
291
+ match = warn_str ,
265
292
):
266
293
lot .break_lot (child_task )
267
294
nursery .cancel_scope .cancel ()
268
295
296
+ # note that this get put into an exceptiongroup if inside a nursery, making any
297
+ # stacklevel arguments irrelevant
298
+ with RaisesGroup (Matcher (RuntimeWarning , match = warn_str )):
299
+ async with trio .open_nursery () as nursery :
300
+ child_task = await nursery .start (dummy_task )
301
+ lot .break_lot (child_task )
302
+ nursery .cancel_scope .cancel ()
303
+
269
304
# and doesn't change broken_by
270
305
assert lot .broken_by == task
271
306
307
+ # register multiple tasks as lot breakers, then have them all exit
308
+ lot = ParkingLot ()
309
+ child_task = None
310
+ # This does not give an exception group, as the warning is raised by the nursery
311
+ # exiting, and not any of the tasks inside the nursery.
312
+ # And we only get a single warning because... of the default warning filter? and the
313
+ # location being the same? (because I haven't figured out why stacklevel makes no
314
+ # difference)
315
+ with pytest .warns (
316
+ RuntimeWarning ,
317
+ match = warn_str ,
318
+ ):
319
+ async with trio .open_nursery () as nursery :
320
+ child_task = await nursery .start (dummy_task )
321
+ child_task2 = await nursery .start (dummy_task )
322
+ child_task3 = await nursery .start (dummy_task )
323
+ add_parking_lot_breaker (child_task , lot )
324
+ add_parking_lot_breaker (child_task2 , lot )
325
+ add_parking_lot_breaker (child_task3 , lot )
326
+ nursery .cancel_scope .cancel ()
327
+
328
+ # trying to register an exited task as lot breaker errors
329
+ with pytest .raises (
330
+ trio .BrokenResourceError ,
331
+ match = "^Attempted to add already exited task as lot breaker.$" ,
332
+ ):
333
+ add_parking_lot_breaker (child_task , lot )
334
+
272
335
273
336
async def test_parking_lot_breaker () -> None :
274
337
async def bad_parker (lot : ParkingLot , scope : _core .CancelScope ) -> None :
275
- trio . lowlevel . add_parking_lot_breaker (trio . lowlevel . current_task (), lot )
338
+ add_parking_lot_breaker (current_task (), lot )
276
339
with scope :
277
340
await trio .sleep_forever ()
278
341
@@ -298,8 +361,9 @@ async def bad_parker(lot: ParkingLot, scope: _core.CancelScope) -> None:
298
361
299
362
300
363
async def test_parking_lot_weird () -> None :
301
- """break a parking lot, where the breakee is parked. Doing this is weird, but should probably be supported??
302
- Although the message makes less sense"""
364
+ """Break a parking lot, where the breakee is parked.
365
+ Doing this is weird, but should probably be supported.
366
+ """
303
367
304
368
async def return_me_and_park (
305
369
lot : ParkingLot ,
0 commit comments