from test.test_support import verify, verbose, TestFailed import sys import gc def expect(actual, expected, name): if actual != expected: raise TestFailed, "test_%s: actual %d, expected %d" % ( name, actual, expected) def expect_nonzero(actual, name): if actual == 0: raise TestFailed, "test_%s: unexpected zero" % name def run_test(name, thunk): if verbose: print "testing %s..." % name, thunk() if verbose: print "ok" def test_list(): l = [] l.append(l) gc.collect() del l expect(gc.collect(), 1, "list") def test_dict(): d = {} d[1] = d gc.collect() del d expect(gc.collect(), 1, "dict") def test_tuple(): # since tuples are immutable we close the loop with a list l = [] t = (l,) l.append(t) gc.collect() del t del l expect(gc.collect(), 2, "tuple") def test_class(): class A: pass A.a = A gc.collect() del A expect_nonzero(gc.collect(), "class") def test_newstyleclass(): class A(object): pass gc.collect() del A expect_nonzero(gc.collect(), "staticclass") def test_instance(): class A: pass a = A() a.a = a gc.collect() del a expect_nonzero(gc.collect(), "instance") def test_newinstance(): class A(object): pass a = A() a.a = a gc.collect() del a expect_nonzero(gc.collect(), "newinstance") class B(list): pass class C(B, A): pass a = C() a.a = a gc.collect() del a expect_nonzero(gc.collect(), "newinstance(2)") del B, C expect_nonzero(gc.collect(), "newinstance(3)") A.a = A() del A expect_nonzero(gc.collect(), "newinstance(4)") expect(gc.collect(), 0, "newinstance(5)") def test_method(): # 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 expect_nonzero(gc.collect(), "method") def test_finalizer(): # 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 expect_nonzero(gc.collect(), "finalizer") for obj in gc.garbage: if id(obj) == id_a: del obj.a break else: raise TestFailed, "didn't find obj in garbage (finalizer)" gc.garbage.remove(obj) def test_function(): # 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 expect(gc.collect(), 2, "function") def test_frame(): def f(): frame = sys._getframe() gc.collect() f() expect(gc.collect(), 1, "frame") def test_saveall(): # Verify that cyclic garbage like lists show up in gc.garbage if the # SAVEALL option is enabled. debug = gc.get_debug() gc.set_debug(debug | gc.DEBUG_SAVEALL) l = [] l.append(l) id_l = id(l) del l gc.collect() try: for obj in gc.garbage: if id(obj) == id_l: del obj[:] break else: raise TestFailed, "didn't find obj in garbage (saveall)" gc.garbage.remove(obj) finally: gc.set_debug(debug) def test_del(): # __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() apply(gc.set_threshold, thresholds) class Ouch: n = 0 def __del__(self): Ouch.n = Ouch.n + 1 if Ouch.n % 17 == 0: gc.collect() def test_trashcan(): # "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_all(): gc.collect() # Delete 2nd generation garbage run_test("lists", test_list) run_test("dicts", test_dict) run_test("tuples", test_tuple) run_test("classes", test_class) run_test("new style classes", test_newstyleclass) run_test("instances", test_instance) run_test("new instances", test_newinstance) run_test("methods", test_method) run_test("functions", test_function) run_test("frames", test_frame) run_test("finalizers", test_finalizer) run_test("__del__", test_del) run_test("saveall", test_saveall) run_test("trashcan", test_trashcan) def test(): if verbose: print "disabling automatic collection" enabled = gc.isenabled() gc.disable() verify(not gc.isenabled()) debug = gc.get_debug() gc.set_debug(debug & ~gc.DEBUG_LEAK) # this test is supposed to leak try: test_all() 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() verify(gc.isenabled()) if not enabled: gc.disable() test()