aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/test/test_gc.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_gc.py')
-rw-r--r--Lib/test/test_gc.py89
1 files changed, 25 insertions, 64 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index b5140057a69..96ebb23f73d 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -7,7 +7,7 @@ from test.support import (verbose, refcount_test,
Py_GIL_DISABLED)
from test.support.import_helper import import_module
from test.support.os_helper import temp_dir, TESTFN, unlink
-from test.support.script_helper import assert_python_ok, make_script
+from test.support.script_helper import assert_python_ok, make_script, run_test_script
from test.support import threading_helper, gc_threshold
import gc
@@ -262,9 +262,11 @@ class GCTests(unittest.TestCase):
# finalizer.
def __del__(self):
- # 5. Create a weakref to `func` now. If we had created
- # it earlier, it would have been cleared by the
- # garbage collector before calling the finalizers.
+ # 5. Create a weakref to `func` now. In previous
+ # versions of Python, this would avoid having it
+ # cleared by the garbage collector before calling
+ # the finalizers. Now, weakrefs get cleared after
+ # calling finalizers.
self[1].ref = weakref.ref(self[0])
# 6. Drop the global reference to `latefin`. The only
@@ -293,14 +295,18 @@ class GCTests(unittest.TestCase):
# which will find `cyc` and `func` as garbage.
gc.collect()
- # 9. Previously, this would crash because `func_qualname`
- # had been NULL-ed out by func_clear().
+ # 9. Previously, this would crash because the weakref
+ # created in the finalizer revealed the function after
+ # `tp_clear` was called and `func_qualname`
+ # had been NULL-ed out by func_clear(). Now, we clear
+ # weakrefs to unreachable objects before calling `tp_clear`
+ # but after calling finalizers.
print(f"{func=}")
"""
- # We're mostly just checking that this doesn't crash.
rc, stdout, stderr = assert_python_ok("-c", code)
self.assertEqual(rc, 0)
- self.assertRegex(stdout, rb"""\A\s*func=<function at \S+>\s*\Z""")
+ # The `func` global is None because the weakref was cleared.
+ self.assertRegex(stdout, rb"""\A\s*func=None""")
self.assertFalse(stderr)
@refcount_test
@@ -726,6 +732,9 @@ class GCTests(unittest.TestCase):
self.assertIn(b"ResourceWarning: gc: 2 uncollectable objects at "
b"shutdown; use", stderr)
self.assertNotIn(b"<X 'first'>", stderr)
+ one_line_re = b"gc: uncollectable <X 0x[0-9A-Fa-f]+>"
+ expected_re = one_line_re + b"\r?\n" + one_line_re
+ self.assertNotRegex(stderr, expected_re)
# With DEBUG_UNCOLLECTABLE, the garbage list gets printed
stderr = run_command(code % "gc.DEBUG_UNCOLLECTABLE")
self.assertIn(b"ResourceWarning: gc: 2 uncollectable objects at "
@@ -733,6 +742,8 @@ class GCTests(unittest.TestCase):
self.assertTrue(
(b"[<X 'first'>, <X 'second'>]" in stderr) or
(b"[<X 'second'>, <X 'first'>]" in stderr), stderr)
+ # we expect two lines with uncollectable objects
+ self.assertRegex(stderr, expected_re)
# With DEBUG_SAVEALL, no additional message should get printed
# (because gc.garbage also contains normally reclaimable cyclic
# references, and its elements get printed at runtime anyway).
@@ -914,7 +925,7 @@ class GCTests(unittest.TestCase):
gc.collect()
self.assertEqual(len(Lazarus.resurrected_instances), 1)
instance = Lazarus.resurrected_instances.pop()
- self.assertTrue(hasattr(instance, "cargo"))
+ self.assertHasAttr(instance, "cargo")
self.assertEqual(id(instance.cargo), cargo_id)
gc.collect()
@@ -1127,64 +1138,14 @@ class GCTests(unittest.TestCase):
class IncrementalGCTests(unittest.TestCase):
-
- def setUp(self):
- # Reenable GC as it is disabled module-wide
- gc.enable()
-
- def tearDown(self):
- gc.disable()
-
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
@requires_gil_enabled("Free threading does not support incremental GC")
- # Use small increments to emulate longer running process in a shorter time
- @gc_threshold(200, 10)
def test_incremental_gc_handles_fast_cycle_creation(self):
-
- class LinkedList:
-
- #Use slots to reduce number of implicit objects
- __slots__ = "next", "prev", "surprise"
-
- def __init__(self, next=None, prev=None):
- self.next = next
- if next is not None:
- next.prev = self
- self.prev = prev
- if prev is not None:
- prev.next = self
-
- def make_ll(depth):
- head = LinkedList()
- for i in range(depth):
- head = LinkedList(head, head.prev)
- return head
-
- head = make_ll(1000)
- count = 1000
-
- # There will be some objects we aren't counting,
- # e.g. the gc stats dicts. This test checks
- # that the counts don't grow, so we try to
- # correct for the uncounted objects
- # This is just an estimate.
- CORRECTION = 20
-
- enabled = gc.isenabled()
- gc.enable()
- olds = []
- initial_heap_size = _testinternalcapi.get_tracked_heap_size()
- for i in range(20_000):
- newhead = make_ll(20)
- count += 20
- newhead.surprise = head
- olds.append(newhead)
- if len(olds) == 20:
- new_objects = _testinternalcapi.get_tracked_heap_size() - initial_heap_size
- self.assertLess(new_objects, 27_000, f"Heap growing. Reached limit after {i} iterations")
- del olds[:]
- if not enabled:
- gc.disable()
+ # Run this test in a fresh process. The number of alive objects (which can
+ # be from unit tests run before this one) can influence how quickly cyclic
+ # garbage is found.
+ script = support.findfile("_test_gc_fast_cycles.py")
+ run_test_script(script)
class GCCallbackTests(unittest.TestCase):