[3.13] gh-118846: Fix free-threading test failures when run sequentially (GH-118864) (#118927)

The free-threaded build currently immortalizes some objects once the
first thread is started. This can lead to test failures depending on the
order in which tests are run. This PR addresses those failures by
suppressing immortalization or skipping the affected tests.
(cherry picked from commit b309c8ebff)

Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
Miss Islington (bot) 2024-05-10 22:50:59 +02:00 committed by GitHub
parent b3074f0d5e
commit cceb758c78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 35 additions and 6 deletions

View File

@ -426,6 +426,7 @@ class CommonTest(unittest.TestCase):
self.assertEqual(lst2, lst) self.assertEqual(lst2, lst)
self.assertNotEqual(id(lst2), id(lst)) self.assertNotEqual(id(lst2), id(lst))
@support.suppress_immortalization()
def test_free_after_iterating(self): def test_free_after_iterating(self):
support.check_free_after_iterating(self, iter, self.type2test) support.check_free_after_iterating(self, iter, self.type2test)
support.check_free_after_iterating(self, reversed, self.type2test) support.check_free_after_iterating(self, reversed, self.type2test)

View File

@ -26,7 +26,8 @@ from test.support import import_helper
from test.support import threading_helper from test.support import threading_helper
from test.support import warnings_helper from test.support import warnings_helper
from test.support import requires_limited_api from test.support import requires_limited_api
from test.support import requires_gil_enabled, expected_failure_if_gil_disabled from test.support import suppress_immortalization
from test.support import expected_failure_if_gil_disabled
from test.support import Py_GIL_DISABLED from test.support import Py_GIL_DISABLED
from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end from test.support.script_helper import assert_python_failure, assert_python_ok, run_python_until_end
try: try:
@ -481,6 +482,7 @@ class CAPITest(unittest.TestCase):
def test_null_type_doc(self): def test_null_type_doc(self):
self.assertEqual(_testcapi.NullTpDocType.__doc__, None) self.assertEqual(_testcapi.NullTpDocType.__doc__, None)
@suppress_immortalization()
def test_subclass_of_heap_gc_ctype_with_tpdealloc_decrefs_once(self): def test_subclass_of_heap_gc_ctype_with_tpdealloc_decrefs_once(self):
class HeapGcCTypeSubclass(_testcapi.HeapGcCType): class HeapGcCTypeSubclass(_testcapi.HeapGcCType):
def __init__(self): def __init__(self):
@ -498,6 +500,7 @@ class CAPITest(unittest.TestCase):
del subclass_instance del subclass_instance
self.assertEqual(type_refcnt - 1, sys.getrefcount(HeapGcCTypeSubclass)) self.assertEqual(type_refcnt - 1, sys.getrefcount(HeapGcCTypeSubclass))
@suppress_immortalization()
def test_subclass_of_heap_gc_ctype_with_del_modifying_dunder_class_only_decrefs_once(self): def test_subclass_of_heap_gc_ctype_with_del_modifying_dunder_class_only_decrefs_once(self):
class A(_testcapi.HeapGcCType): class A(_testcapi.HeapGcCType):
def __init__(self): def __init__(self):

View File

@ -5014,6 +5014,7 @@ class ClassPropertiesAndMethods(unittest.TestCase):
cls.lst = [2**i for i in range(10000)] cls.lst = [2**i for i in range(10000)]
X.descr X.descr
@support.suppress_immortalization()
def test_remove_subclass(self): def test_remove_subclass(self):
# bpo-46417: when the last subclass of a type is deleted, # bpo-46417: when the last subclass of a type is deleted,
# remove_subclass() clears the internal dictionary of subclasses: # remove_subclass() clears the internal dictionary of subclasses:

View File

@ -3,7 +3,8 @@ import unittest.mock
from test import support from test import support
from test.support import (verbose, refcount_test, from test.support import (verbose, refcount_test,
cpython_only, requires_subprocess, cpython_only, requires_subprocess,
requires_gil_enabled) requires_gil_enabled, suppress_immortalization,
Py_GIL_DISABLED)
from test.support.import_helper import import_module from test.support.import_helper import import_module
from test.support.os_helper import temp_dir, TESTFN, unlink from test.support.os_helper import temp_dir, TESTFN, unlink
from test.support.script_helper import assert_python_ok, make_script from test.support.script_helper import assert_python_ok, make_script
@ -109,6 +110,7 @@ class GCTests(unittest.TestCase):
del l del l
self.assertEqual(gc.collect(), 2) self.assertEqual(gc.collect(), 2)
@suppress_immortalization()
def test_class(self): def test_class(self):
class A: class A:
pass pass
@ -117,6 +119,7 @@ class GCTests(unittest.TestCase):
del A del A
self.assertNotEqual(gc.collect(), 0) self.assertNotEqual(gc.collect(), 0)
@suppress_immortalization()
def test_newstyleclass(self): def test_newstyleclass(self):
class A(object): class A(object):
pass pass
@ -133,6 +136,7 @@ class GCTests(unittest.TestCase):
del a del a
self.assertNotEqual(gc.collect(), 0) self.assertNotEqual(gc.collect(), 0)
@suppress_immortalization()
def test_newinstance(self): def test_newinstance(self):
class A(object): class A(object):
pass pass
@ -219,6 +223,7 @@ class GCTests(unittest.TestCase):
self.fail("didn't find obj in garbage (finalizer)") self.fail("didn't find obj in garbage (finalizer)")
gc.garbage.remove(obj) gc.garbage.remove(obj)
@suppress_immortalization()
def test_function(self): def test_function(self):
# Tricky: f -> d -> f, code should call d.clear() after the exec to # Tricky: f -> d -> f, code should call d.clear() after the exec to
# break the cycle. # break the cycle.
@ -561,6 +566,7 @@ class GCTests(unittest.TestCase):
self.assertEqual(gc.get_referents(1, 'a', 4j), []) self.assertEqual(gc.get_referents(1, 'a', 4j), [])
@suppress_immortalization()
def test_is_tracked(self): def test_is_tracked(self):
# Atomic built-in types are not tracked, user-defined objects and # Atomic built-in types are not tracked, user-defined objects and
# mutable containers are. # mutable containers are.
@ -598,7 +604,9 @@ class GCTests(unittest.TestCase):
class UserIntSlots(int): class UserIntSlots(int):
__slots__ = () __slots__ = ()
self.assertTrue(gc.is_tracked(gc)) if not Py_GIL_DISABLED:
# gh-117783: modules may be immortalized in free-threaded build
self.assertTrue(gc.is_tracked(gc))
self.assertTrue(gc.is_tracked(UserClass)) self.assertTrue(gc.is_tracked(UserClass))
self.assertTrue(gc.is_tracked(UserClass())) self.assertTrue(gc.is_tracked(UserClass()))
self.assertTrue(gc.is_tracked(UserInt())) self.assertTrue(gc.is_tracked(UserInt()))
@ -1347,6 +1355,10 @@ class GCTogglingTests(unittest.TestCase):
junk = [] junk = []
i = 0 i = 0
detector = GC_Detector() detector = GC_Detector()
if Py_GIL_DISABLED:
# The free-threaded build doesn't have multiple generations, so
# just trigger a GC manually.
gc.collect()
while not detector.gc_happened: while not detector.gc_happened:
i += 1 i += 1
if i > 10000: if i > 10000:
@ -1415,6 +1427,10 @@ class GCTogglingTests(unittest.TestCase):
detector = GC_Detector() detector = GC_Detector()
junk = [] junk = []
i = 0 i = 0
if Py_GIL_DISABLED:
# The free-threaded build doesn't have multiple generations, so
# just trigger a GC manually.
gc.collect()
while not detector.gc_happened: while not detector.gc_happened:
i += 1 i += 1
if i > 10000: if i > 10000:

View File

@ -34,7 +34,7 @@ try:
except ImportError: except ImportError:
ThreadPoolExecutor = None ThreadPoolExecutor = None
from test.support import cpython_only, import_helper from test.support import cpython_only, import_helper, suppress_immortalization
from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ from test.support import MISSING_C_DOCSTRINGS, ALWAYS_EQ
from test.support.import_helper import DirsOnSysPath, ready_to_import from test.support.import_helper import DirsOnSysPath, ready_to_import
from test.support.os_helper import TESTFN, temp_cwd from test.support.os_helper import TESTFN, temp_cwd
@ -768,6 +768,7 @@ class TestRetrievingSourceCode(GetSourceBase):
inspect.getfile(list.append) inspect.getfile(list.append)
self.assertIn('expected, got', str(e_append.exception)) self.assertIn('expected, got', str(e_append.exception))
@suppress_immortalization()
def test_getfile_class_without_module(self): def test_getfile_class_without_module(self):
class CM(type): class CM(type):
@property @property
@ -2430,6 +2431,7 @@ class TestGetattrStatic(unittest.TestCase):
self.assertFalse(test.called) self.assertFalse(test.called)
@suppress_immortalization()
def test_cache_does_not_cause_classes_to_persist(self): def test_cache_does_not_cause_classes_to_persist(self):
# regression test for gh-118013: # regression test for gh-118013:
# check that the internal _shadowed_dict cache does not cause # check that the internal _shadowed_dict cache does not cause

View File

@ -4,6 +4,7 @@ import unittest
import weakref import weakref
from test.support import gc_collect from test.support import gc_collect
from test.support import import_helper from test.support import import_helper
from test.support import suppress_immortalization
from test.support.script_helper import assert_python_ok from test.support.script_helper import assert_python_ok
import sys import sys
@ -103,6 +104,7 @@ class ModuleTests(unittest.TestCase):
gc_collect() gc_collect()
self.assertEqual(f().__dict__["bar"], 4) self.assertEqual(f().__dict__["bar"], 4)
@suppress_immortalization()
def test_clear_dict_in_ref_cycle(self): def test_clear_dict_in_ref_cycle(self):
destroyed = [] destroyed = []
m = ModuleType("foo") m = ModuleType("foo")
@ -118,6 +120,7 @@ a = A(destroyed)"""
gc_collect() gc_collect()
self.assertEqual(destroyed, [1]) self.assertEqual(destroyed, [1])
@suppress_immortalization()
def test_weakref(self): def test_weakref(self):
m = ModuleType("foo") m = ModuleType("foo")
wr = weakref.ref(m) wr = weakref.ref(m)

View File

@ -1,7 +1,7 @@
import os import os
from pickle import dump from pickle import dump
import sys import sys
from test.support import captured_stdout, requires_resource from test.support import captured_stdout, requires_resource, requires_gil_enabled
from test.support.os_helper import (TESTFN, rmtree, unlink) from test.support.os_helper import (TESTFN, rmtree, unlink)
from test.support.script_helper import assert_python_ok, assert_python_failure from test.support.script_helper import assert_python_ok, assert_python_failure
import textwrap import textwrap
@ -301,6 +301,7 @@ class TestFuncs(unittest.TestCase):
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'pre-existing trace function throws off measurements') 'pre-existing trace function throws off measurements')
@requires_gil_enabled("gh-117783: immortalization of types affects traced method names")
def test_inst_method_calling(self): def test_inst_method_calling(self):
obj = TracedClass(20) obj = TracedClass(20)
self.tracer.runfunc(obj.inst_method_calling, 1) self.tracer.runfunc(obj.inst_method_calling, 1)
@ -334,6 +335,7 @@ class TestCallers(unittest.TestCase):
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'pre-existing trace function throws off measurements') 'pre-existing trace function throws off measurements')
@requires_gil_enabled("gh-117783: immortalization of types affects traced method names")
def test_loop_caller_importing(self): def test_loop_caller_importing(self):
self.tracer.runfunc(traced_func_importing_caller, 1) self.tracer.runfunc(traced_func_importing_caller, 1)

View File

@ -17,7 +17,7 @@ import unittest
from datetime import date, datetime, time, timedelta, timezone from datetime import date, datetime, time, timedelta, timezone
from functools import cached_property from functools import cached_property
from test.support import MISSING_C_DOCSTRINGS from test.support import MISSING_C_DOCSTRINGS, requires_gil_enabled
from test.test_zoneinfo import _support as test_support from test.test_zoneinfo import _support as test_support
from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase
from test.support.import_helper import import_module, CleanImport from test.support.import_helper import import_module, CleanImport
@ -1931,6 +1931,7 @@ class ExtensionBuiltTest(unittest.TestCase):
self.assertFalse(hasattr(c_zoneinfo.ZoneInfo, "_weak_cache")) self.assertFalse(hasattr(c_zoneinfo.ZoneInfo, "_weak_cache"))
self.assertTrue(hasattr(py_zoneinfo.ZoneInfo, "_weak_cache")) self.assertTrue(hasattr(py_zoneinfo.ZoneInfo, "_weak_cache"))
@requires_gil_enabled("gh-117783: types may be immortalized")
def test_gc_tracked(self): def test_gc_tracked(self):
import gc import gc