From 9900e7b4a4ab1111cef411d3140b67917a2d3976 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Sat, 25 Oct 2025 13:29:09 +0300 Subject: [PATCH 1/2] Tests are added --- Lib/test/test_capi/test_tuple.py | 19 +++++++++++++++++++ Lib/test/test_tuple.py | 25 +++++++++++++++++++------ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index b6d6da008d0b7b..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,11 +58,14 @@ 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) @@ -69,6 +78,12 @@ def test_tuple_fromarray(self): 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) @@ -92,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..68111b3ec19b17 100644 --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -290,6 +290,9 @@ def test_repr(self): self.assertEqual(repr(a0), "()") self.assertEqual(repr(a2), "(0, 1, 2)") + def _not_tracked_instantly(self, t): + self.assertFalse(gc.is_tracked(t), t) + def _not_tracked(self, t): # Nested tuples can take several collections to untrack gc.collect() @@ -307,13 +310,17 @@ 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(),)) + 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 +350,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 f335ce10ccbcd469f72b444121bc5490e0fc06f5 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Sat, 25 Oct 2025 14:58:13 +0300 Subject: [PATCH 2/2] Add some comments. --- Lib/test/test_tuple.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_tuple.py b/Lib/test/test_tuple.py index 68111b3ec19b17..e533392b8cae94 100644 --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -290,15 +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() @@ -310,6 +313,8 @@ def test_track_literals(self): # Test GC-optimization of tuple literals x, y, z = 1.5, "a", [] + # 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))