diff options
Diffstat (limited to 'Lib/test/test_exceptions.py')
-rw-r--r-- | Lib/test/test_exceptions.py | 103 |
1 files changed, 101 insertions, 2 deletions
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index cef8d44..8dceb5c 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -10,8 +10,8 @@ import errno from test.support import (TESTFN, captured_stderr, check_impl_detail, check_warnings, cpython_only, gc_collect, run_unittest, - no_tracing, unlink, import_module, script_helper) - + no_tracing, unlink, import_module, script_helper, + SuppressCrashReport) class NaiveException(Exception): def __init__(self, x): self.x = x @@ -936,6 +936,105 @@ class ExceptionTests(unittest.TestCase): self.assertIsInstance(v, RecursionError, type(v)) self.assertIn("maximum recursion depth exceeded", str(v)) + @cpython_only + def test_recursion_normalizing_exception(self): + # Issue #22898. + # Test that a RecursionError is raised when tstate->recursion_depth is + # equal to recursion_limit in PyErr_NormalizeException() and check + # that a ResourceWarning is printed. + # Prior to #22898, the recursivity of PyErr_NormalizeException() was + # controled by tstate->recursion_depth and a PyExc_RecursionErrorInst + # singleton was being used in that case, that held traceback data and + # locals indefinitely and would cause a segfault in _PyExc_Fini() upon + # finalization of these locals. + code = """if 1: + import sys + from _testcapi import get_recursion_depth + + class MyException(Exception): pass + + def setrecursionlimit(depth): + while 1: + try: + sys.setrecursionlimit(depth) + return depth + except RecursionError: + # sys.setrecursionlimit() raises a RecursionError if + # the new recursion limit is too low (issue #25274). + depth += 1 + + def recurse(cnt): + cnt -= 1 + if cnt: + recurse(cnt) + else: + generator.throw(MyException) + + def gen(): + f = open(%a, mode='rb', buffering=0) + yield + + generator = gen() + next(generator) + recursionlimit = sys.getrecursionlimit() + depth = get_recursion_depth() + try: + # Upon the last recursive invocation of recurse(), + # tstate->recursion_depth is equal to (recursion_limit - 1) + # and is equal to recursion_limit when _gen_throw() calls + # PyErr_NormalizeException(). + recurse(setrecursionlimit(depth + 2) - depth - 1) + finally: + sys.setrecursionlimit(recursionlimit) + print('Done.') + """ % __file__ + rc, out, err = script_helper.assert_python_failure("-Wd", "-c", code) + # Check that the program does not fail with SIGABRT. + self.assertEqual(rc, 1) + self.assertIn(b'RecursionError', err) + self.assertIn(b'ResourceWarning', err) + self.assertIn(b'Done.', out) + + @cpython_only + def test_recursion_normalizing_infinite_exception(self): + # Issue #30697. Test that a RecursionError is raised when + # PyErr_NormalizeException() maximum recursion depth has been + # exceeded. + code = """if 1: + import _testcapi + try: + raise _testcapi.RecursingInfinitelyError + finally: + print('Done.') + """ + rc, out, err = script_helper.assert_python_failure("-c", code) + self.assertEqual(rc, 1) + self.assertIn(b'RecursionError: maximum recursion depth exceeded ' + b'while normalizing an exception', err) + self.assertIn(b'Done.', out) + + @cpython_only + def test_recursion_normalizing_with_no_memory(self): + # Issue #30697. Test that in the abort that occurs when there is no + # memory left and the size of the Python frames stack is greater than + # the size of the list of preallocated MemoryError instances, the + # Fatal Python error message mentions MemoryError. + code = """if 1: + import _testcapi + class C(): pass + def recurse(cnt): + cnt -= 1 + if cnt: + recurse(cnt) + else: + _testcapi.set_nomemory(0) + C() + recurse(16) + """ + with SuppressCrashReport(): + rc, out, err = script_helper.assert_python_failure("-c", code) + self.assertIn(b'Fatal Python error: Cannot recover from ' + b'MemoryErrors while normalizing exceptions.', err) @cpython_only def test_MemoryError(self): |