From 40cd953c6851dbb24c5546ad97dc898bad86187d Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 26 Jun 2026 11:01:05 +0300 Subject: [PATCH 1/4] gh-152236: Fix skips on `_testcapi.set_nomemory` tests --- Lib/test/test_atexit.py | 4 +++- Lib/test/test_capi/test_mem.py | 1 + Lib/test/test_class.py | 1 + Lib/test/test_interpreters/test_stress.py | 7 +++++-- Lib/test/test_list.py | 1 + Lib/test/test_weakref.py | 2 ++ 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py index 8256ff183f28c9c..d23c9d402c7949f 100644 --- a/Lib/test/test_atexit.py +++ b/Lib/test/test_atexit.py @@ -4,7 +4,7 @@ import textwrap import unittest from test import support -from test.support import SuppressCrashReport, script_helper +from test.support import SuppressCrashReport, script_helper, import_helper from test.support import os_helper from test.support import threading_helper @@ -194,9 +194,11 @@ def callback(): # Python built with Py_TRACE_REFS fail with a fatal error in # _PyRefchain_Trace() on memory allocation error. @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.cpython_only def test_atexit_with_low_memory(self): # gh-140080: Test that setting low memory after registering an atexit # callback doesn't cause an infinite loop during finalization. + import_helper.import_module('_testcapi') code = textwrap.dedent(""" import atexit import _testcapi diff --git a/Lib/test/test_capi/test_mem.py b/Lib/test/test_capi/test_mem.py index 5035b2b4829bf64..27394b54d370095 100644 --- a/Lib/test/test_capi/test_mem.py +++ b/Lib/test/test_capi/test_mem.py @@ -120,6 +120,7 @@ def test_pyobject_freed_is_freed(self): # Python built with Py_TRACE_REFS fail with a fatal error in # _PyRefchain_Trace() on memory allocation error. @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.cpython_only def test_set_nomemory(self): code = """if 1: import _testcapi diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 64222555166a2e3..cff4c4fb182639a 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1014,6 +1014,7 @@ class C: C.a = X() @cpython_only + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') def test_detach_materialized_dict_no_memory(self): # Skip test if _testcapi is not available: import_helper.import_module('_testcapi') diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py index 6b40a536bd3c319..48f6ef946464cc1 100644 --- a/Lib/test/test_interpreters/test_stress.py +++ b/Lib/test/test_interpreters/test_stress.py @@ -75,12 +75,15 @@ def run(): start.set() support.gc_collect() + @support.cpython_only + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') def test_create_interpreter_no_memory(self): import _interpreters _testcapi = import_helper.import_module("_testcapi") - with self.assertRaises(InterpreterError): - _testcapi.set_nomemory(0, 1) + assertion = self.assertRaises(InterpreterError) + _testcapi.set_nomemory(0, 1) + with assertion: _interpreters.create() diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 642b54d34849dab..be885e6624ffb6b 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -327,6 +327,7 @@ def test_tier2_invalidates_iterator(self): self.assertEqual(list(it), []) @support.cpython_only + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') def test_no_memory(self): # gh-118331: Make sure we don't crash if list allocation fails import_module("_testcapi") diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 0c4717521f16f67..80a8bb3311d9c28 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -1025,9 +1025,11 @@ def __del__(self): pass support.gc_collect() @support.cpython_only + @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') def test_no_memory_when_clearing(self): # gh-118331: Make sure we do not raise an exception from the destructor # when clearing weakrefs if allocating the intermediate tuple fails. + import_helper.import_module('_testcapi') code = textwrap.dedent(""" import _testcapi import weakref From 802c08004e4c965c6a9e0cbe8aceb6f35c3bf5b8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 26 Jun 2026 14:57:10 +0300 Subject: [PATCH 2/4] Address review, add `nomemtest` helper --- Lib/test/support/__init__.py | 18 +++++++++++++++++- Lib/test/test_atexit.py | 8 ++------ Lib/test/test_capi/test_mem.py | 5 +---- Lib/test/test_class.py | 6 +----- Lib/test/test_exceptions.py | 18 +++--------------- Lib/test/test_interpreters/test_stress.py | 8 +++----- Lib/test/test_list.py | 5 +---- Lib/test/test_repl.py | 7 +------ Lib/test/test_weakref.py | 4 +--- 9 files changed, 30 insertions(+), 49 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index e9966e1f7a6d495..de367bfc2aac716 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -35,7 +35,7 @@ "requires_gil_enabled", "requires_linux_version", "requires_mac_ver", "check_syntax_error", "requires_gzip", "requires_bz2", "requires_lzma", "requires_zstd", - "bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute", + "bigmemtest", "nomemtest", "bigaddrspacetest", "cpython_only", "get_attribute", "requires_IEEE_754", "requires_zlib", "has_fork_support", "requires_fork", "has_subprocess_support", "requires_subprocess", @@ -1305,6 +1305,22 @@ def wrapper(self): return wrapper return decorator +def nomemtest(f): + """Check that we can use this test with `_testcapi.set_nomemory`.""" + # Python built with Py_TRACE_REFS fail with a fatal error in + # _PyRefchain_Trace() on memory allocation error. + from .import_helper import import_module + + @functools.wraps(f) + def internal(*args, **kwargs): + import_module('_testcapi') + return f(*args, **kwargs) + + return unittest.skipIf( + Py_TRACE_REFS, + 'cannot test Py_TRACE_REFS build', + )(cpython_only(internal)) + def bigaddrspacetest(f): """Decorator for tests that fill the address space.""" def wrapper(self): diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py index d23c9d402c7949f..33c37648da31fc7 100644 --- a/Lib/test/test_atexit.py +++ b/Lib/test/test_atexit.py @@ -4,7 +4,7 @@ import textwrap import unittest from test import support -from test.support import SuppressCrashReport, script_helper, import_helper +from test.support import SuppressCrashReport, script_helper from test.support import os_helper from test.support import threading_helper @@ -191,14 +191,10 @@ def callback(): self.assertEqual(os.read(r, len(expected)), expected) os.close(r) - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') - @support.cpython_only + @support.nomemtest def test_atexit_with_low_memory(self): # gh-140080: Test that setting low memory after registering an atexit # callback doesn't cause an infinite loop during finalization. - import_helper.import_module('_testcapi') code = textwrap.dedent(""" import atexit import _testcapi diff --git a/Lib/test/test_capi/test_mem.py b/Lib/test/test_capi/test_mem.py index 27394b54d370095..66ab072733e17bd 100644 --- a/Lib/test/test_capi/test_mem.py +++ b/Lib/test/test_capi/test_mem.py @@ -117,10 +117,7 @@ def test_pyobject_forbidden_bytes_is_freed(self): def test_pyobject_freed_is_freed(self): self.check_pyobject_is_freed('check_pyobject_freed_is_freed') - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') - @support.cpython_only + @support.nomemtest def test_set_nomemory(self): code = """if 1: import _testcapi diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index cff4c4fb182639a..807d59e64f7ae8b 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -1013,12 +1013,8 @@ class C: C.a = X() C.a = X() - @cpython_only - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.nomemtest def test_detach_materialized_dict_no_memory(self): - # Skip test if _testcapi is not available: - import_helper.import_module('_testcapi') - code = """if 1: import test.support import _testcapi diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index df7a04273b9b41c..cc7faef93e1af7c 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1583,11 +1583,7 @@ def recurse_in_body_and_except(): sys.setrecursionlimit(recursionlimit) - @cpython_only - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') - @unittest.skipIf(_testcapi is None, "requires _testcapi") + @support.nomemtest 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 @@ -1774,11 +1770,7 @@ def test_unhandled(self): self.assertIn("test message", report) self.assertEndsWith(report, "\n") - @cpython_only - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') - @unittest.skipIf(_testcapi is None, "requires _testcapi") + @support.nomemtest def test_memory_error_in_PyErr_PrintEx(self): code = """if 1: import _testcapi @@ -1936,12 +1928,8 @@ def test_keyerror_context(self): exc2 = None - @cpython_only - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.nomemtest def test_exec_set_nomemory_hang(self): - import_module("_testcapi") # gh-134163: A MemoryError inside code that was wrapped by a try/except # block would lead to an infinite loop. diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py index 48f6ef946464cc1..50d87a6ccd3cadb 100644 --- a/Lib/test/test_interpreters/test_stress.py +++ b/Lib/test/test_interpreters/test_stress.py @@ -5,7 +5,7 @@ from test.support import import_helper from test.support import threading_helper # Raise SkipTest if subinterpreters not supported. -import_helper.import_module('_interpreters') +_interpreters = import_helper.import_module('_interpreters') from concurrent import interpreters from concurrent.interpreters import InterpreterError from .utils import TestBase @@ -75,11 +75,9 @@ def run(): start.set() support.gc_collect() - @support.cpython_only - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.nomemtest def test_create_interpreter_no_memory(self): - import _interpreters - _testcapi = import_helper.import_module("_testcapi") + import _testcapi assertion = self.assertRaises(InterpreterError) _testcapi.set_nomemory(0, 1) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index be885e6624ffb6b..44ecd62d6735def 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -3,7 +3,6 @@ import textwrap from test import list_tests, support from test.support import cpython_only -from test.support.import_helper import import_module from test.support.script_helper import assert_python_failure, assert_python_ok import pickle import unittest @@ -326,11 +325,9 @@ def test_tier2_invalidates_iterator(self): a.append(4) self.assertEqual(list(it), []) - @support.cpython_only - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.nomemtest def test_no_memory(self): # gh-118331: Make sure we don't crash if list allocation fails - import_module("_testcapi") code = textwrap.dedent(""" import _testcapi, sys # Prime the freelist diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 850cb66a89ba84a..0f5009c32124902 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -17,7 +17,6 @@ SHORT_TIMEOUT, ) from test.support.script_helper import kill_python -from test.support.import_helper import import_module try: import pty @@ -99,12 +98,8 @@ def run_on_interactive_mode(source): @support.force_not_colorized_test_class class TestInteractiveInterpreter(unittest.TestCase): - @cpython_only - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.nomemtest def test_no_memory(self): - import_module("_testcapi") # Issue #30696: Fix the interactive interpreter looping endlessly when # no memory. Check also that the fix does not break the interactive # loop when an exception is raised. diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 80a8bb3311d9c28..b9d1745592c09ca 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -1024,12 +1024,10 @@ def __del__(self): pass del x support.gc_collect() - @support.cpython_only - @unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build') + @support.nomemtest def test_no_memory_when_clearing(self): # gh-118331: Make sure we do not raise an exception from the destructor # when clearing weakrefs if allocating the intermediate tuple fails. - import_helper.import_module('_testcapi') code = textwrap.dedent(""" import _testcapi import weakref From 3971d40ff445d5352193862e6792c60966ad4f69 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 26 Jun 2026 14:58:38 +0300 Subject: [PATCH 3/4] Fix lint --- Lib/test/test_class.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 807d59e64f7ae8b..e401941c6d69700 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -449,7 +449,6 @@ def __delattr__(self, *args): def testHasAttrString(self): import sys - from test.support import import_helper _testlimitedcapi = import_helper.import_module('_testlimitedcapi') class A: From 558b889ba85f0d8cf307fcca78c5c5a41020b646 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 26 Jun 2026 15:41:29 +0300 Subject: [PATCH 4/4] Move the comment closer to the definition --- Lib/test/support/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index de367bfc2aac716..a3f8a733a081f62 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -1307,8 +1307,6 @@ def wrapper(self): def nomemtest(f): """Check that we can use this test with `_testcapi.set_nomemory`.""" - # Python built with Py_TRACE_REFS fail with a fatal error in - # _PyRefchain_Trace() on memory allocation error. from .import_helper import import_module @functools.wraps(f) @@ -1317,6 +1315,8 @@ def internal(*args, **kwargs): return f(*args, **kwargs) return unittest.skipIf( + # Python built with Py_TRACE_REFS fail with a fatal error in + # _PyRefchain_Trace() on memory allocation error. Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build', )(cpython_only(internal))