]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-143460: Skip infinite recusion tests for infinite stack size (GH-143606...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 9 Jan 2026 16:54:49 +0000 (17:54 +0100)
committerGitHub <noreply@github.com>
Fri, 9 Jan 2026 16:54:49 +0000 (16:54 +0000)
gh-143460: Skip infinite recusion tests for infinite stack size (GH-143606)

Avoid tests being killed due to OOM on Linux if a system is configured with
'ulimit -s unlimited' by skipping tests relying on infinite recursion.

While unclear if Python should support 'ulimit -s unlimited', we should at
least try to avoid failing a PGO build running tests due to an unlimited
stack size being set.
(cherry picked from commit 61e036691c8ac70facb8d3fc39c670bde56218e8)

Signed-off-by: Jan André Reuter <j.reuter@fz-juelich.de>
Co-authored-by: Jan André Reuter <jan.andre.reuter@hotmail.de>
Lib/test/pickletester.py
Lib/test/support/__init__.py
Lib/test/test_ast/test_ast.py
Lib/test/test_functools.py
Lib/test/test_isinstance.py
Lib/test/test_json/test_recursion.py
Lib/test/test_support.py
Lib/test/test_tomllib/test_misc.py
Misc/NEWS.d/next/Tests/2026-01-09-13-52-10.gh-issue-143460._nW2jt.rst [new file with mode: 0644]

index aaac9fc196823de4ae668b23488a206684827132..570233f7ec516aaa31236fef1f76911ec4e9b262 100644 (file)
@@ -2267,6 +2267,7 @@ class AbstractPicklingErrorTests:
         with self.assertRaises(TypeError):
             self.dumps(c)
 
+    @support.skip_if_unlimited_stack_size
     @no_tracing
     def test_bad_getattr(self):
         # Issue #3514: crash when there is an infinite loop in __getattr__
index 03de1f4df3a4713b04706ced1daed2d86632446c..a0dcd517d9b4535bb1dbf603de648180f8985246 100644 (file)
@@ -44,6 +44,7 @@ __all__ = [
     "check__all__", "skip_if_buggy_ucrt_strfptime",
     "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer",
     "requires_limited_api", "requires_specialization", "thread_unsafe",
+    "skip_if_unlimited_stack_size",
     # sys
     "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
     "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
@@ -1684,6 +1685,25 @@ def skip_if_pgo_task(test):
     return test if ok else unittest.skip(msg)(test)
 
 
+def skip_if_unlimited_stack_size(test):
+    """Skip decorator for tests not run when an unlimited stack size is configured.
+
+    Tests using support.infinite_recursion([...]) may otherwise run into
+    an infinite loop, running until the memory on the system is filled and
+    crashing due to OOM.
+
+    See https://github.com/python/cpython/issues/143460.
+    """
+    if is_wasi or os.name == "nt":
+        return test
+
+    import resource
+    curlim, maxlim = resource.getrlimit(resource.RLIMIT_STACK)
+    unlimited_stack_size_cond = curlim == maxlim and curlim in (-1, 0xFFFF_FFFF_FFFF_FFFF)
+    reason = "Not run due to unlimited stack size"
+    return unittest.skipIf(unlimited_stack_size_cond, reason)(test)
+
+
 def detect_api_mismatch(ref_api, other_api, *, ignore=()):
     """Returns the set of items in ref_api not in other_api, except for a
     defined list of items to be ignored in this check.
index c13423edba625ed71586ce70d7360ec833badc7d..5b4045ff4201e302a6c986f999494a7181c63576 100644 (file)
@@ -24,7 +24,7 @@ except ImportError:
 
 from test import support
 from test.support import os_helper
-from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow
+from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, skip_if_unlimited_stack_size
 from test.support.ast_helper import ASTTestMixin
 from test.support.import_helper import ensure_lazy_imports
 from test.test_ast.utils import to_tuple
@@ -988,6 +988,7 @@ class AST_Tests(unittest.TestCase):
         enum._test_simple_enum(_Precedence, _ast_unparse._Precedence)
 
     @support.cpython_only
+    @skip_if_unlimited_stack_size
     @skip_wasi_stack_overflow()
     @skip_emscripten_stack_overflow()
     def test_ast_recursion_limit(self):
@@ -1103,6 +1104,7 @@ class CopyTests(unittest.TestCase):
                     ast2 = pickle.loads(pickle.dumps(tree, protocol))
                     self.assertEqual(to_tuple(ast2), to_tuple(tree))
 
+    @skip_if_unlimited_stack_size
     def test_copy_with_parents(self):
         # gh-120108
         code = """
@@ -1951,6 +1953,7 @@ Module(
         exec(code, ns)
         self.assertIn('sleep', ns)
 
+    @skip_if_unlimited_stack_size
     @skip_emscripten_stack_overflow()
     def test_recursion_direct(self):
         e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1))
@@ -1959,6 +1962,7 @@ Module(
             with support.infinite_recursion():
                 compile(ast.Expression(e), "<test>", "eval")
 
+    @skip_if_unlimited_stack_size
     @skip_emscripten_stack_overflow()
     def test_recursion_indirect(self):
         e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1))
index ce9e7f6d57dd3c8fbc05fa05f66c3a804cacc92d..41cface3be37768891d24ae56ff3eaa292ce79e9 100644 (file)
@@ -438,6 +438,7 @@ class TestPartial:
         self.assertIs(type(r[0]), tuple)
 
     @support.skip_if_sanitizer("thread sanitizer crashes in __tsan::FuncEntry", thread=True)
+    @support.skip_if_unlimited_stack_size
     @support.skip_emscripten_stack_overflow()
     def test_recursive_pickle(self):
         with replaced_module('functools', self.module):
@@ -2139,6 +2140,7 @@ class TestLRU:
     @support.skip_on_s390x
     @unittest.skipIf(support.is_wasi, "WASI has limited C stack")
     @support.skip_if_sanitizer("requires deep stack", ub=True, thread=True)
+    @support.skip_if_unlimited_stack_size
     @support.skip_emscripten_stack_overflow()
     def test_lru_recursion(self):
 
index f440fc28ee7b7d4b8a1987b092c3db3738c8dfed..d97535ba46e677f9899705e93775f9b6119b5ebe 100644 (file)
@@ -317,6 +317,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
             self.assertRaises(RecursionError, issubclass, int, X())
             self.assertRaises(RecursionError, isinstance, 1, X())
 
+    @support.skip_if_unlimited_stack_size
     @support.skip_emscripten_stack_overflow()
     @support.skip_wasi_stack_overflow()
     def test_infinite_recursion_via_bases_tuple(self):
@@ -328,6 +329,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
             with self.assertRaises(RecursionError):
                 issubclass(Failure(), int)
 
+    @support.skip_if_unlimited_stack_size
     @support.skip_emscripten_stack_overflow()
     @support.skip_wasi_stack_overflow()
     def test_infinite_cycle_in_bases(self):
index 40a0baa53f0c3bcf06b9c7d6a53e1395880b98b2..ffd3404e6f77a07aca81beed7cbf3aa7f7cc3adc 100644 (file)
@@ -68,6 +68,7 @@ class TestRecursion:
             self.fail("didn't raise ValueError on default recursion")
 
 
+    @support.skip_if_unlimited_stack_size
     @support.skip_emscripten_stack_overflow()
     @support.skip_wasi_stack_overflow()
     def test_highly_nested_objects_decoding(self):
@@ -84,6 +85,7 @@ class TestRecursion:
             with support.infinite_recursion():
                 self.loads('[' * very_deep + '1' + ']' * very_deep)
 
+    @support.skip_if_unlimited_stack_size
     @support.skip_wasi_stack_overflow()
     @support.skip_emscripten_stack_overflow()
     @support.requires_resource('cpu')
@@ -99,6 +101,7 @@ class TestRecursion:
             with support.infinite_recursion(5000):
                 self.dumps(d)
 
+    @support.skip_if_unlimited_stack_size
     @support.skip_emscripten_stack_overflow()
     @support.skip_wasi_stack_overflow()
     def test_endless_recursion(self):
index dba1dbf2c5a8e659ea2b9ca64ee286205a065ca8..27cd5afb390ec3d1e783935ad29ad77924e884c2 100644 (file)
@@ -662,6 +662,7 @@ class TestSupport(unittest.TestCase):
         """)
         script_helper.assert_python_ok("-c", code)
 
+    @support.skip_if_unlimited_stack_size
     def test_recursion(self):
         # Test infinite_recursion() and get_recursion_available() functions.
         def recursive_function(depth):
index 59116afa1f36ad10345a9ac2ef67ef8457c82bf4..118fde24d88521f101602fd884de306459f913a4 100644 (file)
@@ -93,6 +93,7 @@ class TestMiscellaneous(unittest.TestCase):
         }
         self.assertEqual(obj_copy, expected_obj)
 
+    @support.skip_if_unlimited_stack_size
     def test_inline_array_recursion_limit(self):
         with support.infinite_recursion(max_depth=100):
             available = support.get_recursion_available()
@@ -104,6 +105,7 @@ class TestMiscellaneous(unittest.TestCase):
                 recursive_array_toml = "arr = " + nest_count * "[" + nest_count * "]"
                 tomllib.loads(recursive_array_toml)
 
+    @support.skip_if_unlimited_stack_size
     def test_inline_table_recursion_limit(self):
         with support.infinite_recursion(max_depth=100):
             available = support.get_recursion_available()
diff --git a/Misc/NEWS.d/next/Tests/2026-01-09-13-52-10.gh-issue-143460._nW2jt.rst b/Misc/NEWS.d/next/Tests/2026-01-09-13-52-10.gh-issue-143460._nW2jt.rst
new file mode 100644 (file)
index 0000000..b0df991
--- /dev/null
@@ -0,0 +1 @@
+Skip tests relying on infinite recusion if stack size is unlimited.