+++ /dev/null
-import unittest\r
-from test.test_support import verbose, run_unittest\r
-import sys\r
-import gc\r
-import weakref\r
-\r
-### Support code\r
-###############################################################################\r
-\r
-# Bug 1055820 has several tests of longstanding bugs involving weakrefs and\r
-# cyclic gc.\r
-\r
-# An instance of C1055820 has a self-loop, so becomes cyclic trash when\r
-# unreachable.\r
-class C1055820(object):\r
- def __init__(self, i):\r
- self.i = i\r
- self.loop = self\r
-\r
-class GC_Detector(object):\r
- # Create an instance I. Then gc hasn't happened again so long as\r
- # I.gc_happened is false.\r
-\r
- def __init__(self):\r
- self.gc_happened = False\r
-\r
- def it_happened(ignored):\r
- self.gc_happened = True\r
-\r
- # Create a piece of cyclic trash that triggers it_happened when\r
- # gc collects it.\r
- self.wr = weakref.ref(C1055820(666), it_happened)\r
-\r
-\r
-### Tests\r
-###############################################################################\r
-\r
-class GCTests(unittest.TestCase):\r
- def test_list(self):\r
- l = []\r
- l.append(l)\r
- gc.collect()\r
- del l\r
- self.assertEqual(gc.collect(), 1)\r
-\r
- def test_dict(self):\r
- d = {}\r
- d[1] = d\r
- gc.collect()\r
- del d\r
- self.assertEqual(gc.collect(), 1)\r
-\r
- def test_tuple(self):\r
- # since tuples are immutable we close the loop with a list\r
- l = []\r
- t = (l,)\r
- l.append(t)\r
- gc.collect()\r
- del t\r
- del l\r
- self.assertEqual(gc.collect(), 2)\r
-\r
- def test_class(self):\r
- class A:\r
- pass\r
- A.a = A\r
- gc.collect()\r
- del A\r
- self.assertNotEqual(gc.collect(), 0)\r
-\r
- def test_newstyleclass(self):\r
- class A(object):\r
- pass\r
- gc.collect()\r
- del A\r
- self.assertNotEqual(gc.collect(), 0)\r
-\r
- def test_instance(self):\r
- class A:\r
- pass\r
- a = A()\r
- a.a = a\r
- gc.collect()\r
- del a\r
- self.assertNotEqual(gc.collect(), 0)\r
-\r
- def test_newinstance(self):\r
- class A(object):\r
- pass\r
- a = A()\r
- a.a = a\r
- gc.collect()\r
- del a\r
- self.assertNotEqual(gc.collect(), 0)\r
- class B(list):\r
- pass\r
- class C(B, A):\r
- pass\r
- a = C()\r
- a.a = a\r
- gc.collect()\r
- del a\r
- self.assertNotEqual(gc.collect(), 0)\r
- del B, C\r
- self.assertNotEqual(gc.collect(), 0)\r
- A.a = A()\r
- del A\r
- self.assertNotEqual(gc.collect(), 0)\r
- self.assertEqual(gc.collect(), 0)\r
-\r
- def test_method(self):\r
- # Tricky: self.__init__ is a bound method, it references the instance.\r
- class A:\r
- def __init__(self):\r
- self.init = self.__init__\r
- a = A()\r
- gc.collect()\r
- del a\r
- self.assertNotEqual(gc.collect(), 0)\r
-\r
- def test_finalizer(self):\r
- # A() is uncollectable if it is part of a cycle, make sure it shows up\r
- # in gc.garbage.\r
- class A:\r
- def __del__(self): pass\r
- class B:\r
- pass\r
- a = A()\r
- a.a = a\r
- id_a = id(a)\r
- b = B()\r
- b.b = b\r
- gc.collect()\r
- del a\r
- del b\r
- self.assertNotEqual(gc.collect(), 0)\r
- for obj in gc.garbage:\r
- if id(obj) == id_a:\r
- del obj.a\r
- break\r
- else:\r
- self.fail("didn't find obj in garbage (finalizer)")\r
- gc.garbage.remove(obj)\r
-\r
- def test_finalizer_newclass(self):\r
- # A() is uncollectable if it is part of a cycle, make sure it shows up\r
- # in gc.garbage.\r
- class A(object):\r
- def __del__(self): pass\r
- class B(object):\r
- pass\r
- a = A()\r
- a.a = a\r
- id_a = id(a)\r
- b = B()\r
- b.b = b\r
- gc.collect()\r
- del a\r
- del b\r
- self.assertNotEqual(gc.collect(), 0)\r
- for obj in gc.garbage:\r
- if id(obj) == id_a:\r
- del obj.a\r
- break\r
- else:\r
- self.fail("didn't find obj in garbage (finalizer)")\r
- gc.garbage.remove(obj)\r
-\r
- def test_function(self):\r
- # Tricky: f -> d -> f, code should call d.clear() after the exec to\r
- # break the cycle.\r
- d = {}\r
- exec("def f(): pass\n") in d\r
- gc.collect()\r
- del d\r
- self.assertEqual(gc.collect(), 2)\r
-\r
- def test_frame(self):\r
- def f():\r
- frame = sys._getframe()\r
- gc.collect()\r
- f()\r
- self.assertEqual(gc.collect(), 1)\r
-\r
- def test_saveall(self):\r
- # Verify that cyclic garbage like lists show up in gc.garbage if the\r
- # SAVEALL option is enabled.\r
-\r
- # First make sure we don't save away other stuff that just happens to\r
- # be waiting for collection.\r
- gc.collect()\r
- # if this fails, someone else created immortal trash\r
- self.assertEqual(gc.garbage, [])\r
-\r
- L = []\r
- L.append(L)\r
- id_L = id(L)\r
-\r
- debug = gc.get_debug()\r
- gc.set_debug(debug | gc.DEBUG_SAVEALL)\r
- del L\r
- gc.collect()\r
- gc.set_debug(debug)\r
-\r
- self.assertEqual(len(gc.garbage), 1)\r
- obj = gc.garbage.pop()\r
- self.assertEqual(id(obj), id_L)\r
-\r
- def test_del(self):\r
- # __del__ methods can trigger collection, make this to happen\r
- thresholds = gc.get_threshold()\r
- gc.enable()\r
- gc.set_threshold(1)\r
-\r
- class A:\r
- def __del__(self):\r
- dir(self)\r
- a = A()\r
- del a\r
-\r
- gc.disable()\r
- gc.set_threshold(*thresholds)\r
-\r
- def test_del_newclass(self):\r
- # __del__ methods can trigger collection, make this to happen\r
- thresholds = gc.get_threshold()\r
- gc.enable()\r
- gc.set_threshold(1)\r
-\r
- class A(object):\r
- def __del__(self):\r
- dir(self)\r
- a = A()\r
- del a\r
-\r
- gc.disable()\r
- gc.set_threshold(*thresholds)\r
-\r
- # The following two tests are fragile:\r
- # They precisely count the number of allocations,\r
- # which is highly implementation-dependent.\r
- # For example:\r
- # - disposed tuples are not freed, but reused\r
- # - the call to assertEqual somehow avoids building its args tuple\r
- def test_get_count(self):\r
- # Avoid future allocation of method object\r
- assertEqual = self._baseAssertEqual\r
- gc.collect()\r
- assertEqual(gc.get_count(), (0, 0, 0))\r
- a = dict()\r
- # since gc.collect(), we created two objects:\r
- # the dict, and the tuple returned by get_count()\r
- assertEqual(gc.get_count(), (2, 0, 0))\r
-\r
- def test_collect_generations(self):\r
- # Avoid future allocation of method object\r
- assertEqual = self.assertEqual\r
- gc.collect()\r
- a = dict()\r
- gc.collect(0)\r
- assertEqual(gc.get_count(), (0, 1, 0))\r
- gc.collect(1)\r
- assertEqual(gc.get_count(), (0, 0, 1))\r
- gc.collect(2)\r
- assertEqual(gc.get_count(), (0, 0, 0))\r
-\r
- def test_trashcan(self):\r
- class Ouch:\r
- n = 0\r
- def __del__(self):\r
- Ouch.n = Ouch.n + 1\r
- if Ouch.n % 17 == 0:\r
- gc.collect()\r
-\r
- # "trashcan" is a hack to prevent stack overflow when deallocating\r
- # very deeply nested tuples etc. It works in part by abusing the\r
- # type pointer and refcount fields, and that can yield horrible\r
- # problems when gc tries to traverse the structures.\r
- # If this test fails (as it does in 2.0, 2.1 and 2.2), it will\r
- # most likely die via segfault.\r
-\r
- # Note: In 2.3 the possibility for compiling without cyclic gc was\r
- # removed, and that in turn allows the trashcan mechanism to work\r
- # via much simpler means (e.g., it never abuses the type pointer or\r
- # refcount fields anymore). Since it's much less likely to cause a\r
- # problem now, the various constants in this expensive (we force a lot\r
- # of full collections) test are cut back from the 2.2 version.\r
- gc.enable()\r
- N = 150\r
- for count in range(2):\r
- t = []\r
- for i in range(N):\r
- t = [t, Ouch()]\r
- u = []\r
- for i in range(N):\r
- u = [u, Ouch()]\r
- v = {}\r
- for i in range(N):\r
- v = {1: v, 2: Ouch()}\r
- gc.disable()\r
-\r
- def test_boom(self):\r
- class Boom:\r
- def __getattr__(self, someattribute):\r
- del self.attr\r
- raise AttributeError\r
-\r
- a = Boom()\r
- b = Boom()\r
- a.attr = b\r
- b.attr = a\r
-\r
- gc.collect()\r
- garbagelen = len(gc.garbage)\r
- del a, b\r
- # a<->b are in a trash cycle now. Collection will invoke\r
- # Boom.__getattr__ (to see whether a and b have __del__ methods), and\r
- # __getattr__ deletes the internal "attr" attributes as a side effect.\r
- # That causes the trash cycle to get reclaimed via refcounts falling to\r
- # 0, thus mutating the trash graph as a side effect of merely asking\r
- # whether __del__ exists. This used to (before 2.3b1) crash Python.\r
- # Now __getattr__ isn't called.\r
- self.assertEqual(gc.collect(), 4)\r
- self.assertEqual(len(gc.garbage), garbagelen)\r
-\r
- def test_boom2(self):\r
- class Boom2:\r
- def __init__(self):\r
- self.x = 0\r
-\r
- def __getattr__(self, someattribute):\r
- self.x += 1\r
- if self.x > 1:\r
- del self.attr\r
- raise AttributeError\r
-\r
- a = Boom2()\r
- b = Boom2()\r
- a.attr = b\r
- b.attr = a\r
-\r
- gc.collect()\r
- garbagelen = len(gc.garbage)\r
- del a, b\r
- # Much like test_boom(), except that __getattr__ doesn't break the\r
- # cycle until the second time gc checks for __del__. As of 2.3b1,\r
- # there isn't a second time, so this simply cleans up the trash cycle.\r
- # We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get\r
- # reclaimed this way.\r
- self.assertEqual(gc.collect(), 4)\r
- self.assertEqual(len(gc.garbage), garbagelen)\r
-\r
- def test_boom_new(self):\r
- # boom__new and boom2_new are exactly like boom and boom2, except use\r
- # new-style classes.\r
-\r
- class Boom_New(object):\r
- def __getattr__(self, someattribute):\r
- del self.attr\r
- raise AttributeError\r
-\r
- a = Boom_New()\r
- b = Boom_New()\r
- a.attr = b\r
- b.attr = a\r
-\r
- gc.collect()\r
- garbagelen = len(gc.garbage)\r
- del a, b\r
- self.assertEqual(gc.collect(), 4)\r
- self.assertEqual(len(gc.garbage), garbagelen)\r
-\r
- def test_boom2_new(self):\r
- class Boom2_New(object):\r
- def __init__(self):\r
- self.x = 0\r
-\r
- def __getattr__(self, someattribute):\r
- self.x += 1\r
- if self.x > 1:\r
- del self.attr\r
- raise AttributeError\r
-\r
- a = Boom2_New()\r
- b = Boom2_New()\r
- a.attr = b\r
- b.attr = a\r
-\r
- gc.collect()\r
- garbagelen = len(gc.garbage)\r
- del a, b\r
- self.assertEqual(gc.collect(), 4)\r
- self.assertEqual(len(gc.garbage), garbagelen)\r
-\r
- def test_get_referents(self):\r
- alist = [1, 3, 5]\r
- got = gc.get_referents(alist)\r
- got.sort()\r
- self.assertEqual(got, alist)\r
-\r
- atuple = tuple(alist)\r
- got = gc.get_referents(atuple)\r
- got.sort()\r
- self.assertEqual(got, alist)\r
-\r
- adict = {1: 3, 5: 7}\r
- expected = [1, 3, 5, 7]\r
- got = gc.get_referents(adict)\r
- got.sort()\r
- self.assertEqual(got, expected)\r
-\r
- got = gc.get_referents([1, 2], {3: 4}, (0, 0, 0))\r
- got.sort()\r
- self.assertEqual(got, [0, 0] + range(5))\r
-\r
- self.assertEqual(gc.get_referents(1, 'a', 4j), [])\r
-\r
- def test_is_tracked(self):\r
- # Atomic built-in types are not tracked, user-defined objects and\r
- # mutable containers are.\r
- # NOTE: types with special optimizations (e.g. tuple) have tests\r
- # in their own test files instead.\r
- self.assertFalse(gc.is_tracked(None))\r
- self.assertFalse(gc.is_tracked(1))\r
- self.assertFalse(gc.is_tracked(1.0))\r
- self.assertFalse(gc.is_tracked(1.0 + 5.0j))\r
- self.assertFalse(gc.is_tracked(True))\r
- self.assertFalse(gc.is_tracked(False))\r
- self.assertFalse(gc.is_tracked("a"))\r
- self.assertFalse(gc.is_tracked(u"a"))\r
- self.assertFalse(gc.is_tracked(bytearray("a")))\r
- self.assertFalse(gc.is_tracked(type))\r
- self.assertFalse(gc.is_tracked(int))\r
- self.assertFalse(gc.is_tracked(object))\r
- self.assertFalse(gc.is_tracked(object()))\r
-\r
- class OldStyle:\r
- pass\r
- class NewStyle(object):\r
- pass\r
- self.assertTrue(gc.is_tracked(gc))\r
- self.assertTrue(gc.is_tracked(OldStyle))\r
- self.assertTrue(gc.is_tracked(OldStyle()))\r
- self.assertTrue(gc.is_tracked(NewStyle))\r
- self.assertTrue(gc.is_tracked(NewStyle()))\r
- self.assertTrue(gc.is_tracked([]))\r
- self.assertTrue(gc.is_tracked(set()))\r
-\r
- def test_bug1055820b(self):\r
- # Corresponds to temp2b.py in the bug report.\r
-\r
- ouch = []\r
- def callback(ignored):\r
- ouch[:] = [wr() for wr in WRs]\r
-\r
- Cs = [C1055820(i) for i in range(2)]\r
- WRs = [weakref.ref(c, callback) for c in Cs]\r
- c = None\r
-\r
- gc.collect()\r
- self.assertEqual(len(ouch), 0)\r
- # Make the two instances trash, and collect again. The bug was that\r
- # the callback materialized a strong reference to an instance, but gc\r
- # cleared the instance's dict anyway.\r
- Cs = None\r
- gc.collect()\r
- self.assertEqual(len(ouch), 2) # else the callbacks didn't run\r
- for x in ouch:\r
- # If the callback resurrected one of these guys, the instance\r
- # would be damaged, with an empty __dict__.\r
- self.assertEqual(x, None)\r
-\r
-class GCTogglingTests(unittest.TestCase):\r
- def setUp(self):\r
- gc.enable()\r
-\r
- def tearDown(self):\r
- gc.disable()\r
-\r
- def test_bug1055820c(self):\r
- # Corresponds to temp2c.py in the bug report. This is pretty\r
- # elaborate.\r
-\r
- c0 = C1055820(0)\r
- # Move c0 into generation 2.\r
- gc.collect()\r
-\r
- c1 = C1055820(1)\r
- c1.keep_c0_alive = c0\r
- del c0.loop # now only c1 keeps c0 alive\r
-\r
- c2 = C1055820(2)\r
- c2wr = weakref.ref(c2) # no callback!\r
-\r
- ouch = []\r
- def callback(ignored):\r
- ouch[:] = [c2wr()]\r
-\r
- # The callback gets associated with a wr on an object in generation 2.\r
- c0wr = weakref.ref(c0, callback)\r
-\r
- c0 = c1 = c2 = None\r
-\r
- # What we've set up: c0, c1, and c2 are all trash now. c0 is in\r
- # generation 2. The only thing keeping it alive is that c1 points to\r
- # it. c1 and c2 are in generation 0, and are in self-loops. There's a\r
- # global weakref to c2 (c2wr), but that weakref has no callback.\r
- # There's also a global weakref to c0 (c0wr), and that does have a\r
- # callback, and that callback references c2 via c2wr().\r
- #\r
- # c0 has a wr with callback, which references c2wr\r
- # ^\r
- # |\r
- # | Generation 2 above dots\r
- #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .\r
- # | Generation 0 below dots\r
- # |\r
- # |\r
- # ^->c1 ^->c2 has a wr but no callback\r
- # | | | |\r
- # <--v <--v\r
- #\r
- # So this is the nightmare: when generation 0 gets collected, we see\r
- # that c2 has a callback-free weakref, and c1 doesn't even have a\r
- # weakref. Collecting generation 0 doesn't see c0 at all, and c0 is\r
- # the only object that has a weakref with a callback. gc clears c1\r
- # and c2. Clearing c1 has the side effect of dropping the refcount on\r
- # c0 to 0, so c0 goes away (despite that it's in an older generation)\r
- # and c0's wr callback triggers. That in turn materializes a reference\r
- # to c2 via c2wr(), but c2 gets cleared anyway by gc.\r
-\r
- # We want to let gc happen "naturally", to preserve the distinction\r
- # between generations.\r
- junk = []\r
- i = 0\r
- detector = GC_Detector()\r
- while not detector.gc_happened:\r
- i += 1\r
- if i > 10000:\r
- self.fail("gc didn't happen after 10000 iterations")\r
- self.assertEqual(len(ouch), 0)\r
- junk.append([]) # this will eventually trigger gc\r
-\r
- self.assertEqual(len(ouch), 1) # else the callback wasn't invoked\r
- for x in ouch:\r
- # If the callback resurrected c2, the instance would be damaged,\r
- # with an empty __dict__.\r
- self.assertEqual(x, None)\r
-\r
- def test_bug1055820d(self):\r
- # Corresponds to temp2d.py in the bug report. This is very much like\r
- # test_bug1055820c, but uses a __del__ method instead of a weakref\r
- # callback to sneak in a resurrection of cyclic trash.\r
-\r
- ouch = []\r
- class D(C1055820):\r
- def __del__(self):\r
- ouch[:] = [c2wr()]\r
-\r
- d0 = D(0)\r
- # Move all the above into generation 2.\r
- gc.collect()\r
-\r
- c1 = C1055820(1)\r
- c1.keep_d0_alive = d0\r
- del d0.loop # now only c1 keeps d0 alive\r
-\r
- c2 = C1055820(2)\r
- c2wr = weakref.ref(c2) # no callback!\r
-\r
- d0 = c1 = c2 = None\r
-\r
- # What we've set up: d0, c1, and c2 are all trash now. d0 is in\r
- # generation 2. The only thing keeping it alive is that c1 points to\r
- # it. c1 and c2 are in generation 0, and are in self-loops. There's\r
- # a global weakref to c2 (c2wr), but that weakref has no callback.\r
- # There are no other weakrefs.\r
- #\r
- # d0 has a __del__ method that references c2wr\r
- # ^\r
- # |\r
- # | Generation 2 above dots\r
- #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .\r
- # | Generation 0 below dots\r
- # |\r
- # |\r
- # ^->c1 ^->c2 has a wr but no callback\r
- # | | | |\r
- # <--v <--v\r
- #\r
- # So this is the nightmare: when generation 0 gets collected, we see\r
- # that c2 has a callback-free weakref, and c1 doesn't even have a\r
- # weakref. Collecting generation 0 doesn't see d0 at all. gc clears\r
- # c1 and c2. Clearing c1 has the side effect of dropping the refcount\r
- # on d0 to 0, so d0 goes away (despite that it's in an older\r
- # generation) and d0's __del__ triggers. That in turn materializes\r
- # a reference to c2 via c2wr(), but c2 gets cleared anyway by gc.\r
-\r
- # We want to let gc happen "naturally", to preserve the distinction\r
- # between generations.\r
- detector = GC_Detector()\r
- junk = []\r
- i = 0\r
- while not detector.gc_happened:\r
- i += 1\r
- if i > 10000:\r
- self.fail("gc didn't happen after 10000 iterations")\r
- self.assertEqual(len(ouch), 0)\r
- junk.append([]) # this will eventually trigger gc\r
-\r
- self.assertEqual(len(ouch), 1) # else __del__ wasn't invoked\r
- for x in ouch:\r
- # If __del__ resurrected c2, the instance would be damaged, with an\r
- # empty __dict__.\r
- self.assertEqual(x, None)\r
-\r
-def test_main():\r
- enabled = gc.isenabled()\r
- gc.disable()\r
- assert not gc.isenabled()\r
- debug = gc.get_debug()\r
- gc.set_debug(debug & ~gc.DEBUG_LEAK) # this test is supposed to leak\r
-\r
- try:\r
- gc.collect() # Delete 2nd generation garbage\r
- run_unittest(GCTests, GCTogglingTests)\r
- finally:\r
- gc.set_debug(debug)\r
- # test gc.enable() even if GC is disabled by default\r
- if verbose:\r
- print "restoring automatic collection"\r
- # make sure to always test gc.enable()\r
- gc.enable()\r
- assert gc.isenabled()\r
- if not enabled:\r
- gc.disable()\r
-\r
-if __name__ == "__main__":\r
- test_main()\r