@@ -285,3 +285,95 @@ def shutdown_handler(listener: socket.socket) -> None:
285
285
sock .sendall (sample_request ())
286
286
response = consume_socket (sock )
287
287
validate_response (response )
288
+
289
+ import sys
290
+ import traceback
291
+ import warnings
292
+ from types import TracebackType
293
+ from typing import Any
294
+ from typing import Callable
295
+ from typing import Generator
296
+ from typing import Optional
297
+ from typing import Type
298
+
299
+ import contextlib
300
+
301
+
302
+
303
+ # Copied from cpython/Lib/test/support/__init__.py, with modifications.
304
+ class catch_unraisable_exception :
305
+ """Context manager catching unraisable exception using sys.unraisablehook.
306
+
307
+ Storing the exception value (cm.unraisable.exc_value) creates a reference
308
+ cycle. The reference cycle is broken explicitly when the context manager
309
+ exits.
310
+
311
+ Storing the object (cm.unraisable.object) can resurrect it if it is set to
312
+ an object which is being finalized. Exiting the context manager clears the
313
+ stored object.
314
+
315
+ Usage:
316
+ with catch_unraisable_exception() as cm:
317
+ # code creating an "unraisable exception"
318
+ ...
319
+ # check the unraisable exception: use cm.unraisable
320
+ ...
321
+ # cm.unraisable attribute no longer exists at this point
322
+ # (to break a reference cycle)
323
+ """
324
+
325
+ def __init__ (self ) -> None :
326
+ self .unraisable : Optional ["sys.UnraisableHookArgs" ] = None
327
+ self ._old_hook : Optional [Callable [["sys.UnraisableHookArgs" ], Any ]] = None
328
+
329
+ def _hook (self , unraisable : "sys.UnraisableHookArgs" ) -> None :
330
+ # Storing unraisable.object can resurrect an object which is being
331
+ # finalized. Storing unraisable.exc_value creates a reference cycle.
332
+ self .unraisable = unraisable
333
+
334
+ def __enter__ (self ) -> "catch_unraisable_exception" :
335
+ self ._old_hook = sys .unraisablehook
336
+ sys .unraisablehook = self ._hook
337
+ return self
338
+
339
+ def __exit__ (
340
+ self ,
341
+ exc_type : Optional [Type [BaseException ]],
342
+ exc_val : Optional [BaseException ],
343
+ exc_tb : Optional [TracebackType ],
344
+ ) -> None :
345
+ assert self ._old_hook is not None
346
+ sys .unraisablehook = self ._old_hook
347
+ self ._old_hook = None
348
+ del self .unraisable
349
+
350
+
351
+ @contextlib .contextmanager
352
+ def unraisable_exception_runtest_hook () -> Generator [None , None , None ]:
353
+ with catch_unraisable_exception () as cm :
354
+ yield
355
+ if cm .unraisable :
356
+ if cm .unraisable .err_msg is not None :
357
+ err_msg = cm .unraisable .err_msg
358
+ else :
359
+ err_msg = "Exception ignored in"
360
+ msg = f"{ err_msg } : { cm .unraisable .object !r} \n \n "
361
+ msg += "" .join (
362
+ traceback .format_exception (
363
+ cm .unraisable .exc_type ,
364
+ cm .unraisable .exc_value ,
365
+ cm .unraisable .exc_traceback ,
366
+ )
367
+ )
368
+ warnings .warn (msg )
369
+
370
+ def main ():
371
+ with unraisable_exception_runtest_hook ():
372
+ t = SingleTLSLayerTestCase ()
373
+ t .setup_class ()
374
+ t .test_unwrap_existing_socket ()
375
+ t .teardown_class ()
376
+
377
+
378
+ if __name__ == "__main__" :
379
+ sys .exit (main ())
0 commit comments