diff options
author | Victor Stinner <vstinner@redhat.com> | 2019-05-22 09:28:22 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-22 09:28:22 (GMT) |
commit | ef9d9b63129a2f243591db70e9a2dd53fab95d86 (patch) | |
tree | 3ecd9bb04fba6c9d360b8db5d8b1e78cda50d49b /Lib/test/test_sys.py | |
parent | 2725cb01d7cbf5caecb51cc20d97ba324b09ce96 (diff) | |
download | cpython-ef9d9b63129a2f243591db70e9a2dd53fab95d86.zip cpython-ef9d9b63129a2f243591db70e9a2dd53fab95d86.tar.gz cpython-ef9d9b63129a2f243591db70e9a2dd53fab95d86.tar.bz2 |
bpo-36829: Add sys.unraisablehook() (GH-13187)
Add new sys.unraisablehook() function which can be overridden to
control how "unraisable exceptions" are handled. It is called when an
exception has occurred but there is no way for Python to handle it.
For example, when a destructor raises an exception or during garbage
collection (gc.collect()).
Changes:
* Add an internal UnraisableHookArgs type used to pass arguments to
sys.unraisablehook.
* Add _PyErr_WriteUnraisableDefaultHook().
* The default hook now ignores exception on writing the traceback.
* test_sys now uses unittest.main() to automatically discover tests:
remove test_main().
* Add _PyErr_Init().
* Fix PyErr_WriteUnraisable(): hold a strong reference to sys.stderr
while using it
Diffstat (limited to 'Lib/test/test_sys.py')
-rw-r--r-- | Lib/test/test_sys.py | 80 |
1 files changed, 76 insertions, 4 deletions
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index d1c7daa..2b358ca 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -877,6 +877,81 @@ class SysModuleTest(unittest.TestCase): @test.support.cpython_only +class UnraisableHookTest(unittest.TestCase): + def write_unraisable_exc(self, exc, obj): + import _testcapi + import types + try: + # raise the exception to get a traceback in the except block + try: + raise exc + except Exception as exc2: + _testcapi.write_unraisable_exc(exc2, obj) + return types.SimpleNamespace(exc_type=type(exc2), + exc_value=exc2, + exc_traceback=exc2.__traceback__, + object=obj) + finally: + # Explicitly break any reference cycle + exc = None + exc2 = None + + def test_original_unraisablehook(self): + obj = "an object" + + with test.support.captured_output("stderr") as stderr: + with test.support.swap_attr(sys, 'unraisablehook', + sys.__unraisablehook__): + self.write_unraisable_exc(ValueError(42), obj) + + err = stderr.getvalue() + self.assertIn(f'Exception ignored in: {obj!r}\n', err) + self.assertIn('Traceback (most recent call last):\n', err) + self.assertIn('ValueError: 42\n', err) + + def test_original_unraisablehook_wrong_type(self): + exc = ValueError(42) + with test.support.swap_attr(sys, 'unraisablehook', + sys.__unraisablehook__): + with self.assertRaises(TypeError): + sys.unraisablehook(exc) + + def test_custom_unraisablehook(self): + hook_args = None + + def hook_func(args): + nonlocal hook_args + hook_args = args + + obj = object() + try: + with test.support.swap_attr(sys, 'unraisablehook', hook_func): + expected = self.write_unraisable_exc(ValueError(42), obj) + for attr in "exc_type exc_value exc_traceback object".split(): + self.assertEqual(getattr(hook_args, attr), + getattr(expected, attr), + (hook_args, expected)) + finally: + # expected and hook_args contain an exception: break reference cycle + expected = None + hook_args = None + + def test_custom_unraisablehook_fail(self): + def hook_func(*args): + raise Exception("hook_func failed") + + with test.support.captured_output("stderr") as stderr: + with test.support.swap_attr(sys, 'unraisablehook', hook_func): + self.write_unraisable_exc(ValueError(42), None) + + err = stderr.getvalue() + self.assertIn(f'Exception ignored in: {hook_func!r}\n', + err) + self.assertIn('Traceback (most recent call last):\n', err) + self.assertIn('Exception: hook_func failed\n', err) + + +@test.support.cpython_only class SizeofTest(unittest.TestCase): def setUp(self): @@ -1277,8 +1352,5 @@ class SizeofTest(unittest.TestCase): self.assertIsNone(cur.finalizer) -def test_main(): - test.support.run_unittest(SysModuleTest, SizeofTest) - if __name__ == "__main__": - test_main() + unittest.main() |