8000 Update test_gc.py from CPython · RustPython/RustPython@ee0ffda · GitHub
[go: up one dir, main page]

Skip to content

Commit ee0ffda

Browse files
committed
Update test_gc.py from CPython
1 parent 764e061 commit ee0ffda

File tree

1 file changed

+94
-17
lines changed

1 file changed

+94
-17
lines changed

Lib/test/test_gc.py

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,11 @@ class Cyclic(tuple):
262262
# finalizer.
263263
def __del__(self):
264264
265-
# 5. Create a weakref to `func` now. If we had created
266-
# it earlier, it would have been cleared by the
267-
# garbage collector before calling the finalizers.
265+
# 5. Create a weakref to `func` now. In previous
266+
# versions of Python, this would avoid having it
267+
# cleared by the garbage collector before calling
268+
# the finalizers. Now, weakrefs get cleared after
269+
# calling finalizers.
268270
self[1].ref = weakref.ref(self[0])
269271
270272
# 6. Drop the global reference to `latefin`. The only
@@ -293,16 +295,40 @@ def func():
293295
# which will find `cyc` and `func` as garbage.
294296
gc.collect()
295297
296-
# 9. Previously, this would crash because `func_qualname`
297-
# had been NULL-ed out by func_clear().
298+
# 9. Previously, this would crash because the weakref
299+
# created in the finalizer revealed the function after
300+
# `tp_clear` was called and `func_qualname`
301+
# had been NULL-ed out by func_clear(). Now, we clear
302+
# weakrefs to unreachable objects before calling `tp_clear`
303+
# but after calling finalizers.
298304
print(f"{func=}")
299305
"""
300-
# We're mostly just checking that this doesn't crash.
301306
rc, stdout, stderr = assert_python_ok("-c", code)
302307
self.assertEqual(rc, 0)
303-
self.assertRegex(stdout, rb"""\A\s*func=<function at \S+>\s*\z""")
308+
# The `func` global is None because the weakref was cleared.
309+
self.assertRegex(stdout, rb"""\A\s*func=None""")
304310
self.assertFalse(stderr)
305311

312+
def test_datetime_weakref_cycle(self):
313+
# https://github.com/python/cpython/issues/132413
314+
# If the weakref used by the datetime extension gets cleared by the GC (due to being
315+
# in an unreachable cycle) then datetime functions would crash (get_module_state()
316+
# was returning a NULL pointer). This bug is fixed by clearing weakrefs without
317+
# callbacks *after* running finalizers.
318+
code = """if 1:
319+
import _datetime
320+
class C:
321+
def __del__(self):
322+
print('__del__ called')
323+
_datetime.timedelta(days=1) # crash?
324+
325+
l = [C()]
326+
l.append(l)
327+
"""
328+
rc, stdout, stderr = assert_python_ok("-c", code)
329+
self.assertEqual(rc, 0)
330+
self.assertEqual(stdout.strip(), b'__del__ called')
331+
306332
@refcount_test
307333
def test_frame(self):
308334
def f():
@@ -652,9 +678,8 @@ def callback(ignored):
652678
gc.collect()
653679
self.assertEqual(len(ouch), 2) # else the callbacks didn't run
654680
for x in ouch:
655-
# If the callback resurrected one of these guys, the instance
656-
# would be damaged, with an empty __dict__.
657-
self.assertEqual(x, None)
681+
# The weakref should be cleared before executing the callback.
682+
self.assertIsNone(x)
658683

659684
def test_bug21435(self):
660685
# This is a poor test - its only virtue is that it happened to
@@ -821,11 +846,15 @@ def test_get_stats(self):
821846
self.assertEqual(len(stats), 3)
822847
for st in stats:
823848
self.assertIsInstance(st, dict)
824-
self.assertEqual(set(st),
825-
{"collected", "collections", "uncollectable", "candidates", "duration"})
849+
self.assertEqual(
850+
set(st),
851+
{"collected", "collections", "uncollectable", "candidates", "duration"}
852+
)
826853
self.assertGreaterEqual(st["collected"], 0)
827854
self.assertGreaterEqual(st["collections"], 0)
828855
self.assertGreaterEqual(st["uncollectable"], 0)
856+
self.assertGreaterEqual(st["candidates"], 0)
857+
self.assertGreaterEqual(st["duration"], 0)
829858
# Check that collection counts are incremented correctly
830859
if gc.isenabled():
831860
self.addCleanup(gc.enable)
@@ -836,11 +865,25 @@ def test_get_stats(self):
836865
self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
837866
self.assertEqual(new[1]["collections"], old[1]["collections"])
838867
self.assertEqual(new[2]["collections"], old[2]["collections"])
868+
self.assertGreater(new[0]["duration"], old[0]["duration"])
869+
self.assertEqual(new[1]["duration"], old[1]["duration"])
870+
self.assertEqual(new[2]["duration"], old[2]["duration"])
871+
for stat in ["collected", "uncollectable", "candidates"]:
872+
self.assertGreaterEqual(new[0][stat], old[0][stat])
873+
self.assertEqual(new[1][stat], old[1][stat])
874+
self.assertEqual(new[2][stat], old[2][stat])
839875
gc.collect(2)
840-
new = gc.get_stats()
841-
self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
876+
old, new = new, gc.get_stats()
877+
self.assertEqual(new[0]["collections"], old[0]["collections"])
842878
self.assertEqual(new[1]["collections"], old[1]["collections"])
843879
self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
880+
self.assertEqual(new[0]["duration"], old[0]["duration"])
881+
self.assertEqual(new[1]["duration"], old[1]["duration"])
882+
self.assertGreater(new[2]["duration"], old[2]["duration"])
883+
for stat in ["collected", "uncollectable", "candidates"]:
884+
self.assertEqual(new[0][stat], old[0][stat])
885+
self.assertEqual(new[1][stat], old[1][stat])
886+
self.assertGreaterEqual(new[2][stat], old[2][stat])
844887

845888
def test_freeze(self):
846889
gc.freeze()
@@ -1156,6 +1199,37 @@ def test_something(self):
11561199
""")
11571200
assert_python_ok("-c", source)
11581201

1202+
def test_do_not_cleanup_type_subclasses_before_finalization(self):
1203+
# See https://github.com/python/cpython/issues/135552
1204+
# If we cleanup weakrefs for tp_subclasses before calling
1205+
# the finalizer (__del__) then the line `fail = BaseNode.next.next`
1206+
# should fail because we are trying to access a subclass
1207+
# attribute. But subclass type cache was not properly invalidated.
1208+
code = """
1209+
class BaseNode:
1210+
def __del__(self):
1211+
BaseNode.next = BaseNode.next.next
1212+
fail = BaseNode.next.next
1213+
1214+
class Node(BaseNode):
1215+
pass
1216+
1217+
BaseNode.next = Node()
1218+
BaseNode.next.next = Node()
1219+
"""
1220+
# this test checks garbage collection while interp
1221+
# finalization
1222+
assert_python_ok("-c", textwrap.dedent(code))
1223+
1224+
code_inside_function = textwrap.dedent(F"""
1225+
def test():
1226+
{textwrap.indent(code, ' ')}
1227+
1228+
test()
1229+
""")
1230+
# this test checks regular garbage collection
1231+
assert_python_ok("-c", code_inside_function)
1232+
11591233

11601234
@unittest.skipUnless(Py_GIL_DISABLED, "requires free-threaded GC")
11611235
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
@@ -1260,9 +1334,11 @@ def test_collect(self):
12601334
# Check that we got the right info dict for all callbacks
12611335
for v in self.visit:
12621336
info = v[2]
1263-
self.assertTrue("generation" in info)
1264-
self.assertTrue("collected" in info)
1265-
self.assertTrue("uncollectable" in info)
1337+
self.assertIn("generation", info)
1338+
self.assertIn("collected", info)
1339+
self.assertIn("uncollectable", info)
1340+
self.assertIn("candidates", info)
1341+
self.assertIn("duration", info)
12661342

12671343
def test_collect_generation(self):
12681344
self.preclean()
@@ -1450,6 +1526,7 @@ def callback(ignored):
14501526
self.assertEqual(x, None)
14511527

14521528
@gc_threshold(1000, 0, 0)
1529+
@unittest.skipIf(Py_GIL_DISABLED, "requires GC generations or increments")
14531530
def test_bug1055820d(self):
14541531
# Corresponds to temp2d.py in the bug report. This is very much like
14551532
# test_bug1055820c, but uses a __del__ method instead of a weakref

0 commit comments

Comments
 (0)
0