From ca62ffd1a5ef41401abceddfd171c76c68825a35 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 8 Apr 2024 14:45:25 +0200 Subject: [PATCH] gh-116303: Skip tests if C recursion limit is unavailable (GH-117368) The test suite fetches the C recursion limit from the _testcapi extension module. Test extension modules can be disabled using the --disable-test-modules configure option. --- Lib/test/list_tests.py | 4 ++-- Lib/test/mapping_tests.py | 4 ++-- Lib/test/support/__init__.py | 20 ++++++++------------ Lib/test/test_ast.py | 4 ++-- Lib/test/test_collections.py | 2 +- Lib/test/test_compile.py | 11 ++++++----- Lib/test/test_dict.py | 4 ++-- Lib/test/test_dictviews.py | 4 ++-- Lib/test/test_exception_group.py | 4 ++-- Lib/test/test_exceptions.py | 2 +- Lib/test/test_functools.py | 2 +- Lib/test/test_isinstance.py | 2 +- Lib/test/test_marshal.py | 2 +- Lib/test/test_sys_settrace.py | 2 +- 14 files changed, 32 insertions(+), 35 deletions(-) diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index 26118e14bb9..89cd10f76a3 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -6,7 +6,7 @@ import sys from functools import cmp_to_key from test import seq_tests -from test.support import ALWAYS_EQ, NEVER_EQ, Py_C_RECURSION_LIMIT +from test.support import ALWAYS_EQ, NEVER_EQ, get_c_recursion_limit class CommonTest(seq_tests.CommonTest): @@ -61,7 +61,7 @@ class CommonTest(seq_tests.CommonTest): def test_repr_deep(self): a = self.type2test([]) - for i in range(Py_C_RECURSION_LIMIT + 1): + for i in range(get_c_recursion_limit() + 1): a = self.type2test([a]) self.assertRaises(RecursionError, repr, a) diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py index b4cfce19a71..ed89a81a6ea 100644 --- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -1,7 +1,7 @@ # tests common to dict and UserDict import unittest import collections -from test.support import Py_C_RECURSION_LIMIT +from test.support import get_c_recursion_limit class BasicTestMappingProtocol(unittest.TestCase): @@ -624,7 +624,7 @@ class TestHashMappingProtocol(TestMappingProtocol): def test_repr_deep(self): d = self._empty_mapping() - for i in range(Py_C_RECURSION_LIMIT + 1): + for i in range(get_c_recursion_limit() + 1): d0 = d d = self._empty_mapping() d[1] = d0 diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 2be9cd099a6..4bf2d7b5142 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -56,7 +56,7 @@ __all__ = [ "run_with_tz", "PGO", "missing_compiler_executable", "ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST", "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT", - "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "Py_C_RECURSION_LIMIT", + "Py_DEBUG", "exceeds_recursion_limit", "get_c_recursion_limit", "skip_on_s390x", "without_optimizer", ] @@ -2490,22 +2490,18 @@ def adjust_int_max_str_digits(max_digits): sys.set_int_max_str_digits(current) -def _get_c_recursion_limit(): +def get_c_recursion_limit(): try: import _testcapi return _testcapi.Py_C_RECURSION_LIMIT - except (ImportError, AttributeError): - # Originally taken from Include/cpython/pystate.h . - if sys.platform == 'win32': - return 4000 - else: - return 10000 + except ImportError: + raise unittest.SkipTest('requires _testcapi') -# The default C recursion limit. -Py_C_RECURSION_LIMIT = _get_c_recursion_limit() -#For recursion tests, easily exceeds default recursion limit -EXCEEDS_RECURSION_LIMIT = Py_C_RECURSION_LIMIT * 3 +def exceeds_recursion_limit(): + """For recursion tests, easily exceeds default recursion limit.""" + return get_c_recursion_limit() * 3 + #Windows doesn't have os.uname() but it doesn't support s390x. skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 3929e4e00d5..5b47cdaafb0 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1153,9 +1153,9 @@ class AST_Tests(unittest.TestCase): @support.cpython_only def test_ast_recursion_limit(self): - fail_depth = support.EXCEEDS_RECURSION_LIMIT + fail_depth = support.exceeds_recursion_limit() crash_depth = 100_000 - success_depth = int(support.Py_C_RECURSION_LIMIT * 0.8) + success_depth = int(support.get_c_recursion_limit() * 0.8) if _testinternalcapi is not None: remaining = _testinternalcapi.get_c_recursion_remaining() success_depth = min(success_depth, remaining) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 1fb492ecebd..955323cae88 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -542,7 +542,7 @@ class TestNamedTuple(unittest.TestCase): self.assertEqual(Dot(1)._replace(d=999), (999,)) self.assertEqual(Dot(1)._fields, ('d',)) - n = support.EXCEEDS_RECURSION_LIMIT + n = support.exceeds_recursion_limit() names = list(set(''.join([choice(string.ascii_letters) for j in range(10)]) for i in range(n))) n = len(names) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 9d5f721806a..638b6e96b50 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -13,7 +13,7 @@ import textwrap import warnings from test import support from test.support import (script_helper, requires_debug_ranges, - requires_specialization, Py_C_RECURSION_LIMIT) + requires_specialization, get_c_recursion_limit) from test.support.bytecode_helper import instructions_with_positions from test.support.os_helper import FakePath @@ -114,7 +114,7 @@ class TestSpecifics(unittest.TestCase): @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") def test_extended_arg(self): - repeat = int(Py_C_RECURSION_LIMIT * 0.9) + repeat = int(get_c_recursion_limit() * 0.9) longexpr = 'x = x or ' + '-x' * repeat g = {} code = textwrap.dedent(''' @@ -634,9 +634,10 @@ class TestSpecifics(unittest.TestCase): @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") def test_compiler_recursion_limit(self): # Expected limit is Py_C_RECURSION_LIMIT - fail_depth = Py_C_RECURSION_LIMIT + 1 - crash_depth = Py_C_RECURSION_LIMIT * 100 - success_depth = int(Py_C_RECURSION_LIMIT * 0.8) + limit = get_c_recursion_limit() + fail_depth = limit + 1 + crash_depth = limit * 100 + success_depth = int(limit * 0.8) def check_limit(prefix, repeated, mode="single"): expect_ok = prefix + repeated * success_depth diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 620d0ca4f4c..e5dba7cdc57 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -8,7 +8,7 @@ import sys import unittest import weakref from test import support -from test.support import import_helper, Py_C_RECURSION_LIMIT +from test.support import import_helper, get_c_recursion_limit class DictTest(unittest.TestCase): @@ -596,7 +596,7 @@ class DictTest(unittest.TestCase): def test_repr_deep(self): d = {} - for i in range(Py_C_RECURSION_LIMIT + 1): + for i in range(get_c_recursion_limit() + 1): d = {1: d} self.assertRaises(RecursionError, repr, d) diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index cad568b6ac4..d9881611c19 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -2,7 +2,7 @@ import collections.abc import copy import pickle import unittest -from test.support import Py_C_RECURSION_LIMIT +from test.support import get_c_recursion_limit class DictSetTest(unittest.TestCase): @@ -279,7 +279,7 @@ class DictSetTest(unittest.TestCase): def test_deeply_nested_repr(self): d = {} - for i in range(Py_C_RECURSION_LIMIT//2 + 100): + for i in range(get_c_recursion_limit()//2 + 100): d = {42: d.values()} self.assertRaises(RecursionError, repr, d) diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index 20122679223..b4fc290b1f3 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -1,7 +1,7 @@ import collections.abc import types import unittest -from test.support import Py_C_RECURSION_LIMIT +from test.support import get_c_recursion_limit class TestExceptionGroupTypeHierarchy(unittest.TestCase): def test_exception_group_types(self): @@ -460,7 +460,7 @@ class ExceptionGroupSplitTests(ExceptionGroupTestBase): class DeepRecursionInSplitAndSubgroup(unittest.TestCase): def make_deep_eg(self): e = TypeError(1) - for i in range(Py_C_RECURSION_LIMIT + 1): + for i in range(get_c_recursion_limit() + 1): e = ExceptionGroup('eg', [e]) return e diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 6ad6acc6156..36fd89dbb88 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1424,7 +1424,7 @@ class ExceptionTests(unittest.TestCase): next(generator) recursionlimit = sys.getrecursionlimit() try: - recurse(support.EXCEEDS_RECURSION_LIMIT) + recurse(support.exceeds_recursion_limit()) finally: sys.setrecursionlimit(recursionlimit) print('Done.') diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 3ba4929dd1b..c48c399a10c 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1867,7 +1867,7 @@ class TestLRU: return fib(n-1) + fib(n-2) if not support.Py_DEBUG: - depth = support.Py_C_RECURSION_LIMIT*2//7 + depth = support.get_c_recursion_limit()*2//7 with support.infinite_recursion(): fib(depth) if self.module == c_functools: diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 7f759fb3317..95a119ba683 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -352,7 +352,7 @@ def blowstack(fxn, arg, compare_to): # Make sure that calling isinstance with a deeply nested tuple for its # argument will raise RecursionError eventually. tuple_arg = (compare_to,) - for cnt in range(support.EXCEEDS_RECURSION_LIMIT): + for cnt in range(support.exceeds_recursion_limit()): tuple_arg = (tuple_arg,) fxn(arg, tuple_arg) diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 615568e6af2..64ee1ba867d 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -118,7 +118,7 @@ class CodeTestCase(unittest.TestCase): def test_many_codeobjects(self): # Issue2957: bad recursion count on code objects # more than MAX_MARSHAL_STACK_DEPTH - count = support.EXCEEDS_RECURSION_LIMIT + count = support.exceeds_recursion_limit() codes = (ExceptionTestCase.test_exceptions.__code__,) * count marshal.loads(marshal.dumps(codes)) diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 125f4022711..ded1d9224d8 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -3039,7 +3039,7 @@ class TestExtendedArgs(unittest.TestCase): def test_trace_lots_of_globals(self): - count = min(1000, int(support.Py_C_RECURSION_LIMIT * 0.8)) + count = min(1000, int(support.get_c_recursion_limit() * 0.8)) code = """if 1: def f():