Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | from test.test_support import verify, verbose, TestFailed, vereq |
2 | import sys | |
3 | import gc | |
4 | import weakref | |
5 | ||
6 | def expect(actual, expected, name): | |
7 | if actual != expected: | |
8 | raise TestFailed, "test_%s: actual %r, expected %r" % ( | |
9 | name, actual, expected) | |
10 | ||
11 | def expect_nonzero(actual, name): | |
12 | if actual == 0: | |
13 | raise TestFailed, "test_%s: unexpected zero" % name | |
14 | ||
15 | def run_test(name, thunk): | |
16 | if verbose: | |
17 | print "testing %s..." % name, | |
18 | thunk() | |
19 | if verbose: | |
20 | print "ok" | |
21 | ||
22 | def test_list(): | |
23 | l = [] | |
24 | l.append(l) | |
25 | gc.collect() | |
26 | del l | |
27 | expect(gc.collect(), 1, "list") | |
28 | ||
29 | def test_dict(): | |
30 | d = {} | |
31 | d[1] = d | |
32 | gc.collect() | |
33 | del d | |
34 | expect(gc.collect(), 1, "dict") | |
35 | ||
36 | def test_tuple(): | |
37 | # since tuples are immutable we close the loop with a list | |
38 | l = [] | |
39 | t = (l,) | |
40 | l.append(t) | |
41 | gc.collect() | |
42 | del t | |
43 | del l | |
44 | expect(gc.collect(), 2, "tuple") | |
45 | ||
46 | def test_class(): | |
47 | class A: | |
48 | pass | |
49 | A.a = A | |
50 | gc.collect() | |
51 | del A | |
52 | expect_nonzero(gc.collect(), "class") | |
53 | ||
54 | def test_newstyleclass(): | |
55 | class A(object): | |
56 | pass | |
57 | gc.collect() | |
58 | del A | |
59 | expect_nonzero(gc.collect(), "staticclass") | |
60 | ||
61 | def test_instance(): | |
62 | class A: | |
63 | pass | |
64 | a = A() | |
65 | a.a = a | |
66 | gc.collect() | |
67 | del a | |
68 | expect_nonzero(gc.collect(), "instance") | |
69 | ||
70 | def test_newinstance(): | |
71 | class A(object): | |
72 | pass | |
73 | a = A() | |
74 | a.a = a | |
75 | gc.collect() | |
76 | del a | |
77 | expect_nonzero(gc.collect(), "newinstance") | |
78 | class B(list): | |
79 | pass | |
80 | class C(B, A): | |
81 | pass | |
82 | a = C() | |
83 | a.a = a | |
84 | gc.collect() | |
85 | del a | |
86 | expect_nonzero(gc.collect(), "newinstance(2)") | |
87 | del B, C | |
88 | expect_nonzero(gc.collect(), "newinstance(3)") | |
89 | A.a = A() | |
90 | del A | |
91 | expect_nonzero(gc.collect(), "newinstance(4)") | |
92 | expect(gc.collect(), 0, "newinstance(5)") | |
93 | ||
94 | def test_method(): | |
95 | # Tricky: self.__init__ is a bound method, it references the instance. | |
96 | class A: | |
97 | def __init__(self): | |
98 | self.init = self.__init__ | |
99 | a = A() | |
100 | gc.collect() | |
101 | del a | |
102 | expect_nonzero(gc.collect(), "method") | |
103 | ||
104 | def test_finalizer(): | |
105 | # A() is uncollectable if it is part of a cycle, make sure it shows up | |
106 | # in gc.garbage. | |
107 | class A: | |
108 | def __del__(self): pass | |
109 | class B: | |
110 | pass | |
111 | a = A() | |
112 | a.a = a | |
113 | id_a = id(a) | |
114 | b = B() | |
115 | b.b = b | |
116 | gc.collect() | |
117 | del a | |
118 | del b | |
119 | expect_nonzero(gc.collect(), "finalizer") | |
120 | for obj in gc.garbage: | |
121 | if id(obj) == id_a: | |
122 | del obj.a | |
123 | break | |
124 | else: | |
125 | raise TestFailed, "didn't find obj in garbage (finalizer)" | |
126 | gc.garbage.remove(obj) | |
127 | ||
128 | def test_finalizer_newclass(): | |
129 | # A() is uncollectable if it is part of a cycle, make sure it shows up | |
130 | # in gc.garbage. | |
131 | class A(object): | |
132 | def __del__(self): pass | |
133 | class B(object): | |
134 | pass | |
135 | a = A() | |
136 | a.a = a | |
137 | id_a = id(a) | |
138 | b = B() | |
139 | b.b = b | |
140 | gc.collect() | |
141 | del a | |
142 | del b | |
143 | expect_nonzero(gc.collect(), "finalizer") | |
144 | for obj in gc.garbage: | |
145 | if id(obj) == id_a: | |
146 | del obj.a | |
147 | break | |
148 | else: | |
149 | raise TestFailed, "didn't find obj in garbage (finalizer)" | |
150 | gc.garbage.remove(obj) | |
151 | ||
152 | def test_function(): | |
153 | # Tricky: f -> d -> f, code should call d.clear() after the exec to | |
154 | # break the cycle. | |
155 | d = {} | |
156 | exec("def f(): pass\n") in d | |
157 | gc.collect() | |
158 | del d | |
159 | expect(gc.collect(), 2, "function") | |
160 | ||
161 | def test_frame(): | |
162 | def f(): | |
163 | frame = sys._getframe() | |
164 | gc.collect() | |
165 | f() | |
166 | expect(gc.collect(), 1, "frame") | |
167 | ||
168 | ||
169 | def test_saveall(): | |
170 | # Verify that cyclic garbage like lists show up in gc.garbage if the | |
171 | # SAVEALL option is enabled. | |
172 | ||
173 | # First make sure we don't save away other stuff that just happens to | |
174 | # be waiting for collection. | |
175 | gc.collect() | |
176 | vereq(gc.garbage, []) # if this fails, someone else created immortal trash | |
177 | ||
178 | L = [] | |
179 | L.append(L) | |
180 | id_L = id(L) | |
181 | ||
182 | debug = gc.get_debug() | |
183 | gc.set_debug(debug | gc.DEBUG_SAVEALL) | |
184 | del L | |
185 | gc.collect() | |
186 | gc.set_debug(debug) | |
187 | ||
188 | vereq(len(gc.garbage), 1) | |
189 | obj = gc.garbage.pop() | |
190 | vereq(id(obj), id_L) | |
191 | ||
192 | def test_del(): | |
193 | # __del__ methods can trigger collection, make this to happen | |
194 | thresholds = gc.get_threshold() | |
195 | gc.enable() | |
196 | gc.set_threshold(1) | |
197 | ||
198 | class A: | |
199 | def __del__(self): | |
200 | dir(self) | |
201 | a = A() | |
202 | del a | |
203 | ||
204 | gc.disable() | |
205 | gc.set_threshold(*thresholds) | |
206 | ||
207 | def test_del_newclass(): | |
208 | # __del__ methods can trigger collection, make this to happen | |
209 | thresholds = gc.get_threshold() | |
210 | gc.enable() | |
211 | gc.set_threshold(1) | |
212 | ||
213 | class A(object): | |
214 | def __del__(self): | |
215 | dir(self) | |
216 | a = A() | |
217 | del a | |
218 | ||
219 | gc.disable() | |
220 | gc.set_threshold(*thresholds) | |
221 | ||
222 | class Ouch: | |
223 | n = 0 | |
224 | def __del__(self): | |
225 | Ouch.n = Ouch.n + 1 | |
226 | if Ouch.n % 17 == 0: | |
227 | gc.collect() | |
228 | ||
229 | def test_trashcan(): | |
230 | # "trashcan" is a hack to prevent stack overflow when deallocating | |
231 | # very deeply nested tuples etc. It works in part by abusing the | |
232 | # type pointer and refcount fields, and that can yield horrible | |
233 | # problems when gc tries to traverse the structures. | |
234 | # If this test fails (as it does in 2.0, 2.1 and 2.2), it will | |
235 | # most likely die via segfault. | |
236 | ||
237 | # Note: In 2.3 the possibility for compiling without cyclic gc was | |
238 | # removed, and that in turn allows the trashcan mechanism to work | |
239 | # via much simpler means (e.g., it never abuses the type pointer or | |
240 | # refcount fields anymore). Since it's much less likely to cause a | |
241 | # problem now, the various constants in this expensive (we force a lot | |
242 | # of full collections) test are cut back from the 2.2 version. | |
243 | gc.enable() | |
244 | N = 150 | |
245 | for count in range(2): | |
246 | t = [] | |
247 | for i in range(N): | |
248 | t = [t, Ouch()] | |
249 | u = [] | |
250 | for i in range(N): | |
251 | u = [u, Ouch()] | |
252 | v = {} | |
253 | for i in range(N): | |
254 | v = {1: v, 2: Ouch()} | |
255 | gc.disable() | |
256 | ||
257 | class Boom: | |
258 | def __getattr__(self, someattribute): | |
259 | del self.attr | |
260 | raise AttributeError | |
261 | ||
262 | def test_boom(): | |
263 | a = Boom() | |
264 | b = Boom() | |
265 | a.attr = b | |
266 | b.attr = a | |
267 | ||
268 | gc.collect() | |
269 | garbagelen = len(gc.garbage) | |
270 | del a, b | |
271 | # a<->b are in a trash cycle now. Collection will invoke Boom.__getattr__ | |
272 | # (to see whether a and b have __del__ methods), and __getattr__ deletes | |
273 | # the internal "attr" attributes as a side effect. That causes the | |
274 | # trash cycle to get reclaimed via refcounts falling to 0, thus mutating | |
275 | # the trash graph as a side effect of merely asking whether __del__ | |
276 | # exists. This used to (before 2.3b1) crash Python. Now __getattr__ | |
277 | # isn't called. | |
278 | expect(gc.collect(), 4, "boom") | |
279 | expect(len(gc.garbage), garbagelen, "boom") | |
280 | ||
281 | class Boom2: | |
282 | def __init__(self): | |
283 | self.x = 0 | |
284 | ||
285 | def __getattr__(self, someattribute): | |
286 | self.x += 1 | |
287 | if self.x > 1: | |
288 | del self.attr | |
289 | raise AttributeError | |
290 | ||
291 | def test_boom2(): | |
292 | a = Boom2() | |
293 | b = Boom2() | |
294 | a.attr = b | |
295 | b.attr = a | |
296 | ||
297 | gc.collect() | |
298 | garbagelen = len(gc.garbage) | |
299 | del a, b | |
300 | # Much like test_boom(), except that __getattr__ doesn't break the | |
301 | # cycle until the second time gc checks for __del__. As of 2.3b1, | |
302 | # there isn't a second time, so this simply cleans up the trash cycle. | |
303 | # We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get reclaimed | |
304 | # this way. | |
305 | expect(gc.collect(), 4, "boom2") | |
306 | expect(len(gc.garbage), garbagelen, "boom2") | |
307 | ||
308 | # boom__new and boom2_new are exactly like boom and boom2, except use | |
309 | # new-style classes. | |
310 | ||
311 | class Boom_New(object): | |
312 | def __getattr__(self, someattribute): | |
313 | del self.attr | |
314 | raise AttributeError | |
315 | ||
316 | def test_boom_new(): | |
317 | a = Boom_New() | |
318 | b = Boom_New() | |
319 | a.attr = b | |
320 | b.attr = a | |
321 | ||
322 | gc.collect() | |
323 | garbagelen = len(gc.garbage) | |
324 | del a, b | |
325 | expect(gc.collect(), 4, "boom_new") | |
326 | expect(len(gc.garbage), garbagelen, "boom_new") | |
327 | ||
328 | class Boom2_New(object): | |
329 | def __init__(self): | |
330 | self.x = 0 | |
331 | ||
332 | def __getattr__(self, someattribute): | |
333 | self.x += 1 | |
334 | if self.x > 1: | |
335 | del self.attr | |
336 | raise AttributeError | |
337 | ||
338 | def test_boom2_new(): | |
339 | a = Boom2_New() | |
340 | b = Boom2_New() | |
341 | a.attr = b | |
342 | b.attr = a | |
343 | ||
344 | gc.collect() | |
345 | garbagelen = len(gc.garbage) | |
346 | del a, b | |
347 | expect(gc.collect(), 4, "boom2_new") | |
348 | expect(len(gc.garbage), garbagelen, "boom2_new") | |
349 | ||
350 | def test_get_referents(): | |
351 | alist = [1, 3, 5] | |
352 | got = gc.get_referents(alist) | |
353 | got.sort() | |
354 | expect(got, alist, "get_referents") | |
355 | ||
356 | atuple = tuple(alist) | |
357 | got = gc.get_referents(atuple) | |
358 | got.sort() | |
359 | expect(got, alist, "get_referents") | |
360 | ||
361 | adict = {1: 3, 5: 7} | |
362 | expected = [1, 3, 5, 7] | |
363 | got = gc.get_referents(adict) | |
364 | got.sort() | |
365 | expect(got, expected, "get_referents") | |
366 | ||
367 | got = gc.get_referents([1, 2], {3: 4}, (0, 0, 0)) | |
368 | got.sort() | |
369 | expect(got, [0, 0] + range(5), "get_referents") | |
370 | ||
371 | expect(gc.get_referents(1, 'a', 4j), [], "get_referents") | |
372 | ||
373 | # Bug 1055820 has several tests of longstanding bugs involving weakrefs and | |
374 | # cyclic gc. | |
375 | ||
376 | # An instance of C1055820 has a self-loop, so becomes cyclic trash when | |
377 | # unreachable. | |
378 | class C1055820(object): | |
379 | def __init__(self, i): | |
380 | self.i = i | |
381 | self.loop = self | |
382 | ||
383 | class GC_Detector(object): | |
384 | # Create an instance I. Then gc hasn't happened again so long as | |
385 | # I.gc_happened is false. | |
386 | ||
387 | def __init__(self): | |
388 | self.gc_happened = False | |
389 | ||
390 | def it_happened(ignored): | |
391 | self.gc_happened = True | |
392 | ||
393 | # Create a piece of cyclic trash that triggers it_happened when | |
394 | # gc collects it. | |
395 | self.wr = weakref.ref(C1055820(666), it_happened) | |
396 | ||
397 | def test_bug1055820b(): | |
398 | # Corresponds to temp2b.py in the bug report. | |
399 | ||
400 | ouch = [] | |
401 | def callback(ignored): | |
402 | ouch[:] = [wr() for wr in WRs] | |
403 | ||
404 | Cs = [C1055820(i) for i in range(2)] | |
405 | WRs = [weakref.ref(c, callback) for c in Cs] | |
406 | c = None | |
407 | ||
408 | gc.collect() | |
409 | expect(len(ouch), 0, "bug1055820b") | |
410 | # Make the two instances trash, and collect again. The bug was that | |
411 | # the callback materialized a strong reference to an instance, but gc | |
412 | # cleared the instance's dict anyway. | |
413 | Cs = None | |
414 | gc.collect() | |
415 | expect(len(ouch), 2, "bug1055820b") # else the callbacks didn't run | |
416 | for x in ouch: | |
417 | # If the callback resurrected one of these guys, the instance | |
418 | # would be damaged, with an empty __dict__. | |
419 | expect(x, None, "bug1055820b") | |
420 | ||
421 | def test_bug1055820c(): | |
422 | # Corresponds to temp2c.py in the bug report. This is pretty elaborate. | |
423 | ||
424 | c0 = C1055820(0) | |
425 | # Move c0 into generation 2. | |
426 | gc.collect() | |
427 | ||
428 | c1 = C1055820(1) | |
429 | c1.keep_c0_alive = c0 | |
430 | del c0.loop # now only c1 keeps c0 alive | |
431 | ||
432 | c2 = C1055820(2) | |
433 | c2wr = weakref.ref(c2) # no callback! | |
434 | ||
435 | ouch = [] | |
436 | def callback(ignored): | |
437 | ouch[:] = [c2wr()] | |
438 | ||
439 | # The callback gets associated with a wr on an object in generation 2. | |
440 | c0wr = weakref.ref(c0, callback) | |
441 | ||
442 | c0 = c1 = c2 = None | |
443 | ||
444 | # What we've set up: c0, c1, and c2 are all trash now. c0 is in | |
445 | # generation 2. The only thing keeping it alive is that c1 points to it. | |
446 | # c1 and c2 are in generation 0, and are in self-loops. There's a global | |
447 | # weakref to c2 (c2wr), but that weakref has no callback. There's also | |
448 | # a global weakref to c0 (c0wr), and that does have a callback, and that | |
449 | # callback references c2 via c2wr(). | |
450 | # | |
451 | # c0 has a wr with callback, which references c2wr | |
452 | # ^ | |
453 | # | | |
454 | # | Generation 2 above dots | |
455 | #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . | |
456 | # | Generation 0 below dots | |
457 | # | | |
458 | # | | |
459 | # ^->c1 ^->c2 has a wr but no callback | |
460 | # | | | | | |
461 | # <--v <--v | |
462 | # | |
463 | # So this is the nightmare: when generation 0 gets collected, we see that | |
464 | # c2 has a callback-free weakref, and c1 doesn't even have a weakref. | |
465 | # Collecting generation 0 doesn't see c0 at all, and c0 is the only object | |
466 | # that has a weakref with a callback. gc clears c1 and c2. Clearing c1 | |
467 | # has the side effect of dropping the refcount on c0 to 0, so c0 goes | |
468 | # away (despite that it's in an older generation) and c0's wr callback | |
469 | # triggers. That in turn materializes a reference to c2 via c2wr(), but | |
470 | # c2 gets cleared anyway by gc. | |
471 | ||
472 | # We want to let gc happen "naturally", to preserve the distinction | |
473 | # between generations. | |
474 | junk = [] | |
475 | i = 0 | |
476 | detector = GC_Detector() | |
477 | while not detector.gc_happened: | |
478 | i += 1 | |
479 | if i > 10000: | |
480 | raise TestFailed("gc didn't happen after 10000 iterations") | |
481 | expect(len(ouch), 0, "bug1055820c") | |
482 | junk.append([]) # this will eventually trigger gc | |
483 | ||
484 | expect(len(ouch), 1, "bug1055820c") # else the callback wasn't invoked | |
485 | for x in ouch: | |
486 | # If the callback resurrected c2, the instance would be damaged, | |
487 | # with an empty __dict__. | |
488 | expect(x, None, "bug1055820c") | |
489 | ||
490 | def test_bug1055820d(): | |
491 | # Corresponds to temp2d.py in the bug report. This is very much like | |
492 | # test_bug1055820c, but uses a __del__ method instead of a weakref | |
493 | # callback to sneak in a resurrection of cyclic trash. | |
494 | ||
495 | ouch = [] | |
496 | class D(C1055820): | |
497 | def __del__(self): | |
498 | ouch[:] = [c2wr()] | |
499 | ||
500 | d0 = D(0) | |
501 | # Move all the above into generation 2. | |
502 | gc.collect() | |
503 | ||
504 | c1 = C1055820(1) | |
505 | c1.keep_d0_alive = d0 | |
506 | del d0.loop # now only c1 keeps d0 alive | |
507 | ||
508 | c2 = C1055820(2) | |
509 | c2wr = weakref.ref(c2) # no callback! | |
510 | ||
511 | d0 = c1 = c2 = None | |
512 | ||
513 | # What we've set up: d0, c1, and c2 are all trash now. d0 is in | |
514 | # generation 2. The only thing keeping it alive is that c1 points to it. | |
515 | # c1 and c2 are in generation 0, and are in self-loops. There's a global | |
516 | # weakref to c2 (c2wr), but that weakref has no callback. There are no | |
517 | # other weakrefs. | |
518 | # | |
519 | # d0 has a __del__ method that references c2wr | |
520 | # ^ | |
521 | # | | |
522 | # | Generation 2 above dots | |
523 | #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . | |
524 | # | Generation 0 below dots | |
525 | # | | |
526 | # | | |
527 | # ^->c1 ^->c2 has a wr but no callback | |
528 | # | | | | | |
529 | # <--v <--v | |
530 | # | |
531 | # So this is the nightmare: when generation 0 gets collected, we see that | |
532 | # c2 has a callback-free weakref, and c1 doesn't even have a weakref. | |
533 | # Collecting generation 0 doesn't see d0 at all. gc clears c1 and c2. | |
534 | # Clearing c1 has the side effect of dropping the refcount on d0 to 0, so | |
535 | # d0 goes away (despite that it's in an older generation) and d0's __del__ | |
536 | # triggers. That in turn materializes a reference to c2 via c2wr(), but | |
537 | # c2 gets cleared anyway by gc. | |
538 | ||
539 | # We want to let gc happen "naturally", to preserve the distinction | |
540 | # between generations. | |
541 | detector = GC_Detector() | |
542 | junk = [] | |
543 | i = 0 | |
544 | while not detector.gc_happened: | |
545 | i += 1 | |
546 | if i > 10000: | |
547 | raise TestFailed("gc didn't happen after 10000 iterations") | |
548 | expect(len(ouch), 0, "bug1055820d") | |
549 | junk.append([]) # this will eventually trigger gc | |
550 | ||
551 | expect(len(ouch), 1, "bug1055820d") # else __del__ wasn't invoked | |
552 | for x in ouch: | |
553 | # If __del__ resurrected c2, the instance would be damaged, with an | |
554 | # empty __dict__. | |
555 | expect(x, None, "bug1055820d") | |
556 | ||
557 | ||
558 | def test_all(): | |
559 | gc.collect() # Delete 2nd generation garbage | |
560 | run_test("lists", test_list) | |
561 | run_test("dicts", test_dict) | |
562 | run_test("tuples", test_tuple) | |
563 | run_test("classes", test_class) | |
564 | run_test("new style classes", test_newstyleclass) | |
565 | run_test("instances", test_instance) | |
566 | run_test("new instances", test_newinstance) | |
567 | run_test("methods", test_method) | |
568 | run_test("functions", test_function) | |
569 | run_test("frames", test_frame) | |
570 | run_test("finalizers", test_finalizer) | |
571 | run_test("finalizers (new class)", test_finalizer_newclass) | |
572 | run_test("__del__", test_del) | |
573 | run_test("__del__ (new class)", test_del_newclass) | |
574 | run_test("saveall", test_saveall) | |
575 | run_test("trashcan", test_trashcan) | |
576 | run_test("boom", test_boom) | |
577 | run_test("boom2", test_boom2) | |
578 | run_test("boom_new", test_boom_new) | |
579 | run_test("boom2_new", test_boom2_new) | |
580 | run_test("get_referents", test_get_referents) | |
581 | run_test("bug1055820b", test_bug1055820b) | |
582 | ||
583 | gc.enable() | |
584 | try: | |
585 | run_test("bug1055820c", test_bug1055820c) | |
586 | finally: | |
587 | gc.disable() | |
588 | ||
589 | gc.enable() | |
590 | try: | |
591 | run_test("bug1055820d", test_bug1055820d) | |
592 | finally: | |
593 | gc.disable() | |
594 | ||
595 | def test(): | |
596 | if verbose: | |
597 | print "disabling automatic collection" | |
598 | enabled = gc.isenabled() | |
599 | gc.disable() | |
600 | verify(not gc.isenabled()) | |
601 | debug = gc.get_debug() | |
602 | gc.set_debug(debug & ~gc.DEBUG_LEAK) # this test is supposed to leak | |
603 | ||
604 | try: | |
605 | test_all() | |
606 | finally: | |
607 | gc.set_debug(debug) | |
608 | # test gc.enable() even if GC is disabled by default | |
609 | if verbose: | |
610 | print "restoring automatic collection" | |
611 | # make sure to always test gc.enable() | |
612 | gc.enable() | |
613 | verify(gc.isenabled()) | |
614 | if not enabled: | |
615 | gc.disable() | |
616 | ||
617 | ||
618 | test() |