From 4583d27a4ee7093a45cf720b373c9bc8a57fa651 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 28 Oct 2025 12:55:41 +0300 Subject: [PATCH 1/2] gh-139951: Tests on tuple GC tracking (#140575) --- Lib/test/test_capi/test_tuple.py | 41 ++++++++++++++++++++++++++++++++ Lib/test/test_tuple.py | 30 ++++++++++++++++++----- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 7c07bc64e247c5..d6669d7802c5b8 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -14,6 +14,12 @@ class TupleSubclass(tuple): 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): # Test PyTuple_Check() check = _testlimitedcapi.tuple_check @@ -52,16 +58,47 @@ def test_tuple_new(self): self.assertEqual(tup1, ()) self.assertEqual(size(tup1), 0) self.assertIs(type(tup1), tuple) + self._not_tracked(tup1) + tup2 = tuple_new(1) self.assertIs(type(tup2), tuple) self.assertEqual(size(tup2), 1) self.assertIsNot(tup2, tup1) self.assertTrue(checknull(tup2, 0)) + self._tracked(tup2) self.assertRaises(SystemError, tuple_new, -1) self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX) + def test_tuple_fromarray(self): + # Test PyTuple_FromArray() + tuple_fromarray = _testcapi.tuple_fromarray + + tup = tuple([i] for i in range(5)) + copy = tuple_fromarray(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 = () + copy = tuple_fromarray(tup) + self.assertIs(copy, tup) + + copy = tuple_fromarray(NULL, 0) + self.assertIs(copy, ()) + + with self.assertRaises(SystemError): + tuple_fromarray(NULL, -1) + with self.assertRaises(SystemError): + tuple_fromarray(NULL, PY_SSIZE_T_MIN) + with self.assertRaises(MemoryError): + tuple_fromarray(NULL, PY_SSIZE_T_MAX) + def test_tuple_pack(self): # Test PyTuple_Pack() pack = _testlimitedcapi.tuple_pack @@ -70,6 +107,10 @@ def test_tuple_pack(self): self.assertEqual(pack(1, [1]), ([1],)) 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, -1) self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py index 9ce80c5e8ea009..e533392b8cae94 100644 --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -290,12 +290,18 @@ def test_repr(self): self.assertEqual(repr(a0), "()") 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): # Nested tuples can take several collections to untrack gc.collect() gc.collect() self.assertFalse(gc.is_tracked(t), t) + # Checks that t continues to be tracked even after GC collection. def _tracked(self, t): self.assertTrue(gc.is_tracked(t), t) gc.collect() @@ -307,13 +313,19 @@ def test_track_literals(self): # Test GC-optimization of tuple literals x, y, z = 1.5, "a", [] - self._not_tracked(()) - self._not_tracked((1,)) - self._not_tracked((1, 2)) - self._not_tracked((1, 2, "a")) - self._not_tracked((1, 2, (None, True, False, ()), int)) - self._not_tracked((object(),)) + # We check that those objects aren't tracked at all. + # It's essential for the GC performance, see gh-139951. + self._not_tracked_instantly(()) + self._not_tracked_instantly((1,)) + self._not_tracked_instantly((1, 2)) + 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, 2, (None, True, False, ()), int)) + self._not_tracked((object(), ())) # Tuples with mutable elements are always tracked, even if those # 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(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 def test_track_dynamic(self): # Test GC-optimization of dynamically constructed tuples. From bbe2b5c3beb97d7871a7aa8717803edd49a90997 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 28 Oct 2025 13:16:37 +0300 Subject: [PATCH 2/2] Remove test_tuple_fromarray --- Lib/test/test_capi/test_tuple.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index d6669d7802c5b8..be6c9155831206 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -71,34 +71,6 @@ def test_tuple_new(self): self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX) - def test_tuple_fromarray(self): - # Test PyTuple_FromArray() - tuple_fromarray = _testcapi.tuple_fromarray - - tup = tuple([i] for i in range(5)) - copy = tuple_fromarray(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 = () - copy = tuple_fromarray(tup) - self.assertIs(copy, tup) - - copy = tuple_fromarray(NULL, 0) - self.assertIs(copy, ()) - - with self.assertRaises(SystemError): - tuple_fromarray(NULL, -1) - with self.assertRaises(SystemError): - tuple_fromarray(NULL, PY_SSIZE_T_MIN) - with self.assertRaises(MemoryError): - tuple_fromarray(NULL, PY_SSIZE_T_MAX) - def test_tuple_pack(self): # Test PyTuple_Pack() pack = _testlimitedcapi.tuple_pack