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