from test
.test_support
import verify
, verbose
, TestFailed
, vereq
def expect(actual
, expected
, name
):
raise TestFailed
, "test_%s: actual %r, expected %r" % (
def expect_nonzero(actual
, name
):
raise TestFailed
, "test_%s: unexpected zero" % name
def run_test(name
, thunk
):
print "testing %s..." % name
,
expect(gc
.collect(), 1, "list")
expect(gc
.collect(), 1, "dict")
# since tuples are immutable we close the loop with a list
expect(gc
.collect(), 2, "tuple")
expect_nonzero(gc
.collect(), "class")
def test_newstyleclass():
expect_nonzero(gc
.collect(), "staticclass")
expect_nonzero(gc
.collect(), "instance")
expect_nonzero(gc
.collect(), "newinstance")
expect_nonzero(gc
.collect(), "newinstance(2)")
expect_nonzero(gc
.collect(), "newinstance(3)")
expect_nonzero(gc
.collect(), "newinstance(4)")
expect(gc
.collect(), 0, "newinstance(5)")
# Tricky: self.__init__ is a bound method, it references the instance.
self
.init
= self
.__init
__
expect_nonzero(gc
.collect(), "method")
# A() is uncollectable if it is part of a cycle, make sure it shows up
expect_nonzero(gc
.collect(), "finalizer")
raise TestFailed
, "didn't find obj in garbage (finalizer)"
def test_finalizer_newclass():
# A() is uncollectable if it is part of a cycle, make sure it shows up
expect_nonzero(gc
.collect(), "finalizer")
raise TestFailed
, "didn't find obj in garbage (finalizer)"
# Tricky: f -> d -> f, code should call d.clear() after the exec to
exec("def f(): pass\n") in d
expect(gc
.collect(), 2, "function")
expect(gc
.collect(), 1, "frame")
# 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.
vereq(gc
.garbage
, []) # if this fails, someone else created immortal trash
gc
.set_debug(debug | gc
.DEBUG_SAVEALL
)
vereq(len(gc
.garbage
), 1)
# __del__ methods can trigger collection, make this to happen
thresholds
= gc
.get_threshold()
gc
.set_threshold(*thresholds
)
# __del__ methods can trigger collection, make this to happen
thresholds
= gc
.get_threshold()
gc
.set_threshold(*thresholds
)
# "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.
def __getattr__(self
, someattribute
):
garbagelen
= len(gc
.garbage
)
# 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__
expect(gc
.collect(), 4, "boom")
expect(len(gc
.garbage
), garbagelen
, "boom")
def __getattr__(self
, someattribute
):
garbagelen
= len(gc
.garbage
)
# 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
expect(gc
.collect(), 4, "boom2")
expect(len(gc
.garbage
), garbagelen
, "boom2")
# boom__new and boom2_new are exactly like boom and boom2, except use
def __getattr__(self
, someattribute
):
garbagelen
= len(gc
.garbage
)
expect(gc
.collect(), 4, "boom_new")
expect(len(gc
.garbage
), garbagelen
, "boom_new")
def __getattr__(self
, someattribute
):
garbagelen
= len(gc
.garbage
)
expect(gc
.collect(), 4, "boom2_new")
expect(len(gc
.garbage
), garbagelen
, "boom2_new")
def test_get_referents():
got
= gc
.get_referents(alist
)
expect(got
, alist
, "get_referents")
got
= gc
.get_referents(atuple
)
expect(got
, alist
, "get_referents")
got
= gc
.get_referents(adict
)
expect(got
, expected
, "get_referents")
got
= gc
.get_referents([1, 2], {3: 4}, (0, 0, 0))
expect(got
, [0, 0] + range(5), "get_referents")
expect(gc
.get_referents(1, 'a', 4j
), [], "get_referents")
# Bug 1055820 has several tests of longstanding bugs involving weakrefs and
# An instance of C1055820 has a self-loop, so becomes cyclic trash when
class GC_Detector(object):
# Create an instance I. Then gc hasn't happened again so long as
# I.gc_happened is false.
def it_happened(ignored
):
# Create a piece of cyclic trash that triggers it_happened when
self
.wr
= weakref
.ref(C1055820(666), it_happened
)
# Corresponds to temp2b.py in the bug report.
ouch
[:] = [wr() for wr
in WRs
]
Cs
= [C1055820(i
) for i
in range(2)]
WRs
= [weakref
.ref(c
, callback
) for c
in Cs
]
expect(len(ouch
), 0, "bug1055820b")
# 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.
expect(len(ouch
), 2, "bug1055820b") # else the callbacks didn't run
# If the callback resurrected one of these guys, the instance
# would be damaged, with an empty __dict__.
expect(x
, None, "bug1055820b")
# Corresponds to temp2c.py in the bug report. This is pretty elaborate.
# Move c0 into generation 2.
del c0
.loop
# now only c1 keeps c0 alive
c2wr
= weakref
.ref(c2
) # no callback!
# The callback gets associated with a wr on an object in generation 2.
c0wr
= weakref
.ref(c0
, callback
)
# 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
# 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
while not detector
.gc_happened
:
raise TestFailed("gc didn't happen after 10000 iterations")
expect(len(ouch
), 0, "bug1055820c")
junk
.append([]) # this will eventually trigger gc
expect(len(ouch
), 1, "bug1055820c") # else the callback wasn't invoked
# If the callback resurrected c2, the instance would be damaged,
# with an empty __dict__.
expect(x
, None, "bug1055820c")
# 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.
# Move all the above into generation 2.
del d0
.loop
# now only c1 keeps d0 alive
c2wr
= weakref
.ref(c2
) # no callback!
# 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
# d0 has a __del__ method that references c2wr
# | Generation 2 above dots
#. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . .
# | Generation 0 below dots
# ^->c1 ^->c2 has a wr but no callback
# 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
while not detector
.gc_happened
:
raise TestFailed("gc didn't happen after 10000 iterations")
expect(len(ouch
), 0, "bug1055820d")
junk
.append([]) # this will eventually trigger gc
expect(len(ouch
), 1, "bug1055820d") # else __del__ wasn't invoked
# If __del__ resurrected c2, the instance would be damaged, with an
expect(x
, None, "bug1055820d")
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("finalizers (new class)", test_finalizer_newclass
)
run_test("__del__", test_del
)
run_test("__del__ (new class)", test_del_newclass
)
run_test("saveall", test_saveall
)
run_test("trashcan", test_trashcan
)
run_test("boom", test_boom
)
run_test("boom2", test_boom2
)
run_test("boom_new", test_boom_new
)
run_test("boom2_new", test_boom2_new
)
run_test("get_referents", test_get_referents
)
run_test("bug1055820b", test_bug1055820b
)
run_test("bug1055820c", test_bug1055820c
)
run_test("bug1055820d", test_bug1055820d
)
print "disabling automatic collection"
verify(not gc
.isenabled())
gc
.set_debug(debug
& ~gc
.DEBUG_LEAK
) # this test is supposed to leak
# test gc.enable() even if GC is disabled by default
print "restoring automatic collection"
# make sure to always test gc.enable()