summaryrefslogtreecommitdiff
path: root/Lib/test/test_gc.py
diff options
context:
space:
mode:
authorBenjamin Peterson <benjamin@python.org>2015-02-01 17:59:49 -0500
committerBenjamin Peterson <benjamin@python.org>2015-02-01 17:59:49 -0500
commit596ffac4b9ff54aeb2134988bb85b3c492ae5849 (patch)
treebf0d44eaf89cefa4bbda16672c882ad3667b433d /Lib/test/test_gc.py
parentc67c6e554e82f48627dd4e3b70232ca73d00ec64 (diff)
parent08e89d1aa305a920347fd4d7350684ff37c238f9 (diff)
downloadcpython-596ffac4b9ff54aeb2134988bb85b3c492ae5849.tar.gz
merge 3.3 (#23369)
Diffstat (limited to 'Lib/test/test_gc.py')
-rw-r--r--Lib/test/test_gc.py126
1 files changed, 119 insertions, 7 deletions
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index c59b72eacf..c0be53795f 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1,6 +1,8 @@
import unittest
from test.support import (verbose, refcount_test, run_unittest,
- strip_python_stderr)
+ strip_python_stderr, cpython_only)
+from test.script_helper import assert_python_ok, make_script, temp_dir
+
import sys
import time
import gc
@@ -11,6 +13,15 @@ try:
except ImportError:
threading = None
+try:
+ from _testcapi import with_tp_del
+except ImportError:
+ def with_tp_del(cls):
+ class C(object):
+ def __new__(cls, *args, **kwargs):
+ raise TypeError('requires _testcapi.with_tp_del')
+ return C
+
### Support code
###############################################################################
@@ -38,6 +49,7 @@ class GC_Detector(object):
# gc collects it.
self.wr = weakref.ref(C1055820(666), it_happened)
+@with_tp_del
class Uncollectable(object):
"""Create a reference cycle with multiple __del__ methods.
@@ -50,7 +62,7 @@ class Uncollectable(object):
self.partner = Uncollectable(partner=self)
else:
self.partner = partner
- def __del__(self):
+ def __tp_del__(self):
pass
### Tests
@@ -139,11 +151,13 @@ class GCTests(unittest.TestCase):
del a
self.assertNotEqual(gc.collect(), 0)
- def test_finalizer(self):
+ @cpython_only
+ def test_legacy_finalizer(self):
# A() is uncollectable if it is part of a cycle, make sure it shows up
# in gc.garbage.
+ @with_tp_del
class A:
- def __del__(self): pass
+ def __tp_del__(self): pass
class B:
pass
a = A()
@@ -163,11 +177,13 @@ class GCTests(unittest.TestCase):
self.fail("didn't find obj in garbage (finalizer)")
gc.garbage.remove(obj)
- def test_finalizer_newclass(self):
+ @cpython_only
+ def test_legacy_finalizer_newclass(self):
# A() is uncollectable if it is part of a cycle, make sure it shows up
# in gc.garbage.
+ @with_tp_del
class A(object):
- def __del__(self): pass
+ def __tp_del__(self): pass
class B(object):
pass
a = A()
@@ -564,16 +580,51 @@ class GCTests(unittest.TestCase):
# would be damaged, with an empty __dict__.
self.assertEqual(x, None)
+ def test_bug21435(self):
+ # This is a poor test - its only virtue is that it happened to
+ # segfault on Tim's Windows box before the patch for 21435 was
+ # applied. That's a nasty bug relying on specific pieces of cyclic
+ # trash appearing in exactly the right order in finalize_garbage()'s
+ # input list.
+ # But there's no reliable way to force that order from Python code,
+ # so over time chances are good this test won't really be testing much
+ # of anything anymore. Still, if it blows up, there's _some_
+ # problem ;-)
+ gc.collect()
+
+ class A:
+ pass
+
+ class B:
+ def __init__(self, x):
+ self.x = x
+
+ def __del__(self):
+ self.attr = None
+
+ def do_work():
+ a = A()
+ b = B(A())
+
+ a.attr = b
+ b.attr = a
+
+ do_work()
+ gc.collect() # this blows up (bad C pointer) when it fails
+
+ @cpython_only
def test_garbage_at_shutdown(self):
import subprocess
code = """if 1:
import gc
+ import _testcapi
+ @_testcapi.with_tp_del
class X:
def __init__(self, name):
self.name = name
def __repr__(self):
return "<X %%r>" %% self.name
- def __del__(self):
+ def __tp_del__(self):
pass
x = X('first')
@@ -610,6 +661,66 @@ class GCTests(unittest.TestCase):
stderr = run_command(code % "gc.DEBUG_SAVEALL")
self.assertNotIn(b"uncollectable objects at shutdown", stderr)
+ def test_gc_main_module_at_shutdown(self):
+ # Create a reference cycle through the __main__ module and check
+ # it gets collected at interpreter shutdown.
+ code = """if 1:
+ import weakref
+ class C:
+ def __del__(self):
+ print('__del__ called')
+ l = [C()]
+ l.append(l)
+ """
+ rc, out, err = assert_python_ok('-c', code)
+ self.assertEqual(out.strip(), b'__del__ called')
+
+ def test_gc_ordinary_module_at_shutdown(self):
+ # Same as above, but with a non-__main__ module.
+ with temp_dir() as script_dir:
+ module = """if 1:
+ import weakref
+ class C:
+ def __del__(self):
+ print('__del__ called')
+ l = [C()]
+ l.append(l)
+ """
+ code = """if 1:
+ import sys
+ sys.path.insert(0, %r)
+ import gctest
+ """ % (script_dir,)
+ make_script(script_dir, 'gctest', module)
+ rc, out, err = assert_python_ok('-c', code)
+ self.assertEqual(out.strip(), b'__del__ called')
+
+ def test_get_stats(self):
+ stats = gc.get_stats()
+ self.assertEqual(len(stats), 3)
+ for st in stats:
+ self.assertIsInstance(st, dict)
+ self.assertEqual(set(st),
+ {"collected", "collections", "uncollectable"})
+ self.assertGreaterEqual(st["collected"], 0)
+ self.assertGreaterEqual(st["collections"], 0)
+ self.assertGreaterEqual(st["uncollectable"], 0)
+ # Check that collection counts are incremented correctly
+ if gc.isenabled():
+ self.addCleanup(gc.enable)
+ gc.disable()
+ old = gc.get_stats()
+ gc.collect(0)
+ new = gc.get_stats()
+ self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
+ self.assertEqual(new[1]["collections"], old[1]["collections"])
+ self.assertEqual(new[2]["collections"], old[2]["collections"])
+ gc.collect(2)
+ new = gc.get_stats()
+ self.assertEqual(new[0]["collections"], old[0]["collections"] + 1)
+ self.assertEqual(new[1]["collections"], old[1]["collections"])
+ self.assertEqual(new[2]["collections"], old[2]["collections"] + 1)
+
class GCCallbackTests(unittest.TestCase):
def setUp(self):
@@ -696,6 +807,7 @@ class GCCallbackTests(unittest.TestCase):
info = v[2]
self.assertEqual(info["generation"], 2)
+ @cpython_only
def test_collect_garbage(self):
self.preclean()
# Each of these cause four objects to be garbage: Two