gh-139951: Tests on tuple GC tracking (#140575)

This commit is contained in:
Mikhail Efimov 2025-10-28 12:55:41 +03:00 committed by GitHub
parent d26686a7f8
commit 88ad41fa34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 43 additions and 6 deletions

View file

@ -14,6 +14,12 @@ class TupleSubclass(tuple):
class CAPITest(unittest.TestCase): class CAPITest(unittest.TestCase):
def _not_tracked(self, t):
self.assertFalse(gc.is_tracked(t), t)
def _tracked(self, t):
self.assertTrue(gc.is_tracked(t), t)
def test_check(self): def test_check(self):
# Test PyTuple_Check() # Test PyTuple_Check()
check = _testlimitedcapi.tuple_check check = _testlimitedcapi.tuple_check
@ -52,11 +58,14 @@ def test_tuple_new(self):
self.assertEqual(tup1, ()) self.assertEqual(tup1, ())
self.assertEqual(size(tup1), 0) self.assertEqual(size(tup1), 0)
self.assertIs(type(tup1), tuple) self.assertIs(type(tup1), tuple)
self._not_tracked(tup1)
tup2 = tuple_new(1) tup2 = tuple_new(1)
self.assertIs(type(tup2), tuple) self.assertIs(type(tup2), tuple)
self.assertEqual(size(tup2), 1) self.assertEqual(size(tup2), 1)
self.assertIsNot(tup2, tup1) self.assertIsNot(tup2, tup1)
self.assertTrue(checknull(tup2, 0)) self.assertTrue(checknull(tup2, 0))
self._tracked(tup2)
self.assertRaises(SystemError, tuple_new, -1) self.assertRaises(SystemError, tuple_new, -1)
self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
@ -69,6 +78,12 @@ def test_tuple_fromarray(self):
tup = tuple([i] for i in range(5)) tup = tuple([i] for i in range(5))
copy = tuple_fromarray(tup) copy = tuple_fromarray(tup)
self.assertEqual(copy, tup) self.assertEqual(copy, tup)
self._tracked(copy)
tup = tuple(42**i for i in range(5))
copy = tuple_fromarray(tup)
self.assertEqual(copy, tup)
self._not_tracked(copy)
tup = () tup = ()
copy = tuple_fromarray(tup) copy = tuple_fromarray(tup)
@ -92,6 +107,10 @@ def test_tuple_pack(self):
self.assertEqual(pack(1, [1]), ([1],)) self.assertEqual(pack(1, [1]), ([1],))
self.assertEqual(pack(2, [1], [2]), ([1], [2])) self.assertEqual(pack(2, [1], [2]), ([1], [2]))
self._tracked(pack(1, [1]))
self._tracked(pack(2, [1], b'abc'))
self._not_tracked(pack(2, 42, b'abc'))
self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN) self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN)
self.assertRaises(SystemError, pack, -1) self.assertRaises(SystemError, pack, -1)
self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX)

View file

@ -290,12 +290,18 @@ def test_repr(self):
self.assertEqual(repr(a0), "()") self.assertEqual(repr(a0), "()")
self.assertEqual(repr(a2), "(0, 1, 2)") self.assertEqual(repr(a2), "(0, 1, 2)")
# Checks that t is not tracked without any GC collections.
def _not_tracked_instantly(self, t):
self.assertFalse(gc.is_tracked(t), t)
# Checks that t is not tracked after GC collection.
def _not_tracked(self, t): def _not_tracked(self, t):
# Nested tuples can take several collections to untrack # Nested tuples can take several collections to untrack
gc.collect() gc.collect()
gc.collect() gc.collect()
self.assertFalse(gc.is_tracked(t), t) self.assertFalse(gc.is_tracked(t), t)
# Checks that t continues to be tracked even after GC collection.
def _tracked(self, t): def _tracked(self, t):
self.assertTrue(gc.is_tracked(t), t) self.assertTrue(gc.is_tracked(t), t)
gc.collect() gc.collect()
@ -307,13 +313,19 @@ def test_track_literals(self):
# Test GC-optimization of tuple literals # Test GC-optimization of tuple literals
x, y, z = 1.5, "a", [] x, y, z = 1.5, "a", []
self._not_tracked(()) # We check that those objects aren't tracked at all.
self._not_tracked((1,)) # It's essential for the GC performance, see gh-139951.
self._not_tracked((1, 2)) self._not_tracked_instantly(())
self._not_tracked((1, 2, "a")) self._not_tracked_instantly((1,))
self._not_tracked((1, 2, (None, True, False, ()), int)) self._not_tracked_instantly((1, 2))
self._not_tracked((object(),)) self._not_tracked_instantly((1, 2, "a"))
self._not_tracked_instantly((1, 2) * 5)
self._not_tracked_instantly((12, 10**10, 'a_' * 100))
self._not_tracked_instantly((object(),))
self._not_tracked(((1, x), y, (2, 3))) self._not_tracked(((1, x), y, (2, 3)))
self._not_tracked((1, 2, (None, True, False, ()), int))
self._not_tracked((object(), ()))
# Tuples with mutable elements are always tracked, even if those # Tuples with mutable elements are always tracked, even if those
# elements are not tracked right now. # elements are not tracked right now.
@ -343,6 +355,12 @@ def check_track_dynamic(self, tp, always_track):
self._tracked(tp(tuple([obj]) for obj in [x, y, z])) self._tracked(tp(tuple([obj]) for obj in [x, y, z]))
self._tracked(tuple(tp([obj]) for obj in [x, y, z])) self._tracked(tuple(tp([obj]) for obj in [x, y, z]))
t = tp([1, x, y, z])
self.assertEqual(type(t), tp)
self._tracked(t)
self.assertEqual(type(t[:]), tuple)
self._tracked(t[:])
@support.cpython_only @support.cpython_only
def test_track_dynamic(self): def test_track_dynamic(self):
# Test GC-optimization of dynamically constructed tuples. # Test GC-optimization of dynamically constructed tuples.