Skip to content

Commit 4583d27

Browse files
pythongh-139951: Tests on tuple GC tracking (python#140575)
1 parent 84e01df commit 4583d27

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

Lib/test/test_capi/test_tuple.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ class TupleSubclass(tuple):
1414

1515

1616
class CAPITest(unittest.TestCase):
17+
def _not_tracked(self, t):
18+
self.assertFalse(gc.is_tracked(t), t)
19+
20+
def _tracked(self, t):
21+
self.assertTrue(gc.is_tracked(t), t)
22+
1723
def test_check(self):
1824
# Test PyTuple_Check()
1925
check = _testlimitedcapi.tuple_check
@@ -52,16 +58,47 @@ def test_tuple_new(self):
5258
self.assertEqual(tup1, ())
5359
self.assertEqual(size(tup1), 0)
5460
self.assertIs(type(tup1), tuple)
61+
self._not_tracked(tup1)
62+
5563
tup2 = tuple_new(1)
5664
self.assertIs(type(tup2), tuple)
5765
self.assertEqual(size(tup2), 1)
5866
self.assertIsNot(tup2, tup1)
5967
self.assertTrue(checknull(tup2, 0))
68+
self._tracked(tup2)
6069

6170
self.assertRaises(SystemError, tuple_new, -1)
6271
self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
6372
self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX)
6473

74+
def test_tuple_fromarray(self):
75+
# Test PyTuple_FromArray()
76+
tuple_fromarray = _testcapi.tuple_fromarray
77+
78+
tup = tuple([i] for i in range(5))
79+
copy = tuple_fromarray(tup)
80+
self.assertEqual(copy, tup)
81+
self._tracked(copy)
82+
83+
tup = tuple(42**i for i in range(5))
84+
copy = tuple_fromarray(tup)
85+
self.assertEqual(copy, tup)
86+
self._not_tracked(copy)
87+
88+
tup = ()
89+
copy = tuple_fromarray(tup)
90+
self.assertIs(copy, tup)
91+
92+
copy = tuple_fromarray(NULL, 0)
93+
self.assertIs(copy, ())
94+
95+
with self.assertRaises(SystemError):
96+
tuple_fromarray(NULL, -1)
97+
with self.assertRaises(SystemError):
98+
tuple_fromarray(NULL, PY_SSIZE_T_MIN)
99+
with self.assertRaises(MemoryError):
100+
tuple_fromarray(NULL, PY_SSIZE_T_MAX)
101+
65102
def test_tuple_pack(self):
66103
# Test PyTuple_Pack()
67104
pack = _testlimitedcapi.tuple_pack
@@ -70,6 +107,10 @@ def test_tuple_pack(self):
70107
self.assertEqual(pack(1, [1]), ([1],))
71108
self.assertEqual(pack(2, [1], [2]), ([1], [2]))
72109

110+
self._tracked(pack(1, [1]))
111+
self._tracked(pack(2, [1], b'abc'))
112+
self._not_tracked(pack(2, 42, b'abc'))
113+
73114
self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN)
74115
self.assertRaises(SystemError, pack, -1)
75116
self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX)

Lib/test/test_tuple.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,18 @@ def test_repr(self):
290290
self.assertEqual(repr(a0), "()")
291291
self.assertEqual(repr(a2), "(0, 1, 2)")
292292

293+
# Checks that t is not tracked without any GC collections.
294+
def _not_tracked_instantly(self, t):
295+
self.assertFalse(gc.is_tracked(t), t)
296+
297+
# Checks that t is not tracked after GC collection.
293298
def _not_tracked(self, t):
294299
# Nested tuples can take several collections to untrack
295300
gc.collect()
296301
gc.collect()
297302
self.assertFalse(gc.is_tracked(t), t)
298303

304+
# Checks that t continues to be tracked even after GC collection.
299305
def _tracked(self, t):
300306
self.assertTrue(gc.is_tracked(t), t)
301307
gc.collect()
@@ -307,13 +313,19 @@ def test_track_literals(self):
307313
# Test GC-optimization of tuple literals
308314
x, y, z = 1.5, "a", []
309315

310-
self._not_tracked(())
311-
self._not_tracked((1,))
312-
self._not_tracked((1, 2))
313-
self._not_tracked((1, 2, "a"))
314-
self._not_tracked((1, 2, (None, True, False, ()), int))
315-
self._not_tracked((object(),))
316+
# We check that those objects aren't tracked at all.
317+
# It's essential for the GC performance, see gh-139951.
318+
self._not_tracked_instantly(())
319+
self._not_tracked_instantly((1,))
320+
self._not_tracked_instantly((1, 2))
321+
self._not_tracked_instantly((1, 2, "a"))
322+
self._not_tracked_instantly((1, 2) * 5)
323+
self._not_tracked_instantly((12, 10**10, 'a_' * 100))
324+
self._not_tracked_instantly((object(),))
325+
316326
self._not_tracked(((1, x), y, (2, 3)))
327+
self._not_tracked((1, 2, (None, True, False, ()), int))
328+
self._not_tracked((object(), ()))
317329

318330
# Tuples with mutable elements are always tracked, even if those
319331
# elements are not tracked right now.
@@ -343,6 +355,12 @@ def check_track_dynamic(self, tp, always_track):
343355
self._tracked(tp(tuple([obj]) for obj in [x, y, z]))
344356
self._tracked(tuple(tp([obj]) for obj in [x, y, z]))
345357

358+
t = tp([1, x, y, z])
359+
self.assertEqual(type(t), tp)
360+
self._tracked(t)
361+
self.assertEqual(type(t[:]), tuple)
362+
self._tracked(t[:])
363+
346364
@support.cpython_only
347365
def test_track_dynamic(self):
348366
# Test GC-optimization of dynamically constructed tuples.

0 commit comments

Comments
 (0)