|
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 def test_get_count(): |
|
223 gc.collect() |
|
224 expect(gc.get_count(), (0, 0, 0), "get_count()") |
|
225 a = dict() |
|
226 expect(gc.get_count(), (1, 0, 0), "get_count()") |
|
227 |
|
228 def test_collect_generations(): |
|
229 gc.collect() |
|
230 a = dict() |
|
231 gc.collect(0) |
|
232 expect(gc.get_count(), (0, 1, 0), "collect(0)") |
|
233 gc.collect(1) |
|
234 expect(gc.get_count(), (0, 0, 1), "collect(1)") |
|
235 gc.collect(2) |
|
236 expect(gc.get_count(), (0, 0, 0), "collect(1)") |
|
237 |
|
238 class Ouch: |
|
239 n = 0 |
|
240 def __del__(self): |
|
241 Ouch.n = Ouch.n + 1 |
|
242 if Ouch.n % 17 == 0: |
|
243 gc.collect() |
|
244 |
|
245 def test_trashcan(): |
|
246 # "trashcan" is a hack to prevent stack overflow when deallocating |
|
247 # very deeply nested tuples etc. It works in part by abusing the |
|
248 # type pointer and refcount fields, and that can yield horrible |
|
249 # problems when gc tries to traverse the structures. |
|
250 # If this test fails (as it does in 2.0, 2.1 and 2.2), it will |
|
251 # most likely die via segfault. |
|
252 |
|
253 # Note: In 2.3 the possibility for compiling without cyclic gc was |
|
254 # removed, and that in turn allows the trashcan mechanism to work |
|
255 # via much simpler means (e.g., it never abuses the type pointer or |
|
256 # refcount fields anymore). Since it's much less likely to cause a |
|
257 # problem now, the various constants in this expensive (we force a lot |
|
258 # of full collections) test are cut back from the 2.2 version. |
|
259 gc.enable() |
|
260 N = 150 |
|
261 for count in range(2): |
|
262 t = [] |
|
263 for i in range(N): |
|
264 t = [t, Ouch()] |
|
265 u = [] |
|
266 for i in range(N): |
|
267 u = [u, Ouch()] |
|
268 v = {} |
|
269 for i in range(N): |
|
270 v = {1: v, 2: Ouch()} |
|
271 gc.disable() |
|
272 |
|
273 class Boom: |
|
274 def __getattr__(self, someattribute): |
|
275 del self.attr |
|
276 raise AttributeError |
|
277 |
|
278 def test_boom(): |
|
279 a = Boom() |
|
280 b = Boom() |
|
281 a.attr = b |
|
282 b.attr = a |
|
283 |
|
284 gc.collect() |
|
285 garbagelen = len(gc.garbage) |
|
286 del a, b |
|
287 # a<->b are in a trash cycle now. Collection will invoke Boom.__getattr__ |
|
288 # (to see whether a and b have __del__ methods), and __getattr__ deletes |
|
289 # the internal "attr" attributes as a side effect. That causes the |
|
290 # trash cycle to get reclaimed via refcounts falling to 0, thus mutating |
|
291 # the trash graph as a side effect of merely asking whether __del__ |
|
292 # exists. This used to (before 2.3b1) crash Python. Now __getattr__ |
|
293 # isn't called. |
|
294 expect(gc.collect(), 4, "boom") |
|
295 expect(len(gc.garbage), garbagelen, "boom") |
|
296 |
|
297 class Boom2: |
|
298 def __init__(self): |
|
299 self.x = 0 |
|
300 |
|
301 def __getattr__(self, someattribute): |
|
302 self.x += 1 |
|
303 if self.x > 1: |
|
304 del self.attr |
|
305 raise AttributeError |
|
306 |
|
307 def test_boom2(): |
|
308 a = Boom2() |
|
309 b = Boom2() |
|
310 a.attr = b |
|
311 b.attr = a |
|
312 |
|
313 gc.collect() |
|
314 garbagelen = len(gc.garbage) |
|
315 del a, b |
|
316 # Much like test_boom(), except that __getattr__ doesn't break the |
|
317 # cycle until the second time gc checks for __del__. As of 2.3b1, |
|
318 # there isn't a second time, so this simply cleans up the trash cycle. |
|
319 # We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get reclaimed |
|
320 # this way. |
|
321 expect(gc.collect(), 4, "boom2") |
|
322 expect(len(gc.garbage), garbagelen, "boom2") |
|
323 |
|
324 # boom__new and boom2_new are exactly like boom and boom2, except use |
|
325 # new-style classes. |
|
326 |
|
327 class Boom_New(object): |
|
328 def __getattr__(self, someattribute): |
|
329 del self.attr |
|
330 raise AttributeError |
|
331 |
|
332 def test_boom_new(): |
|
333 a = Boom_New() |
|
334 b = Boom_New() |
|
335 a.attr = b |
|
336 b.attr = a |
|
337 |
|
338 gc.collect() |
|
339 garbagelen = len(gc.garbage) |
|
340 del a, b |
|
341 expect(gc.collect(), 4, "boom_new") |
|
342 expect(len(gc.garbage), garbagelen, "boom_new") |
|
343 |
|
344 class Boom2_New(object): |
|
345 def __init__(self): |
|
346 self.x = 0 |
|
347 |
|
348 def __getattr__(self, someattribute): |
|
349 self.x += 1 |
|
350 if self.x > 1: |
|
351 del self.attr |
|
352 raise AttributeError |
|
353 |
|
354 def test_boom2_new(): |
|
355 a = Boom2_New() |
|
356 b = Boom2_New() |
|
357 a.attr = b |
|
358 b.attr = a |
|
359 |
|
360 gc.collect() |
|
361 garbagelen = len(gc.garbage) |
|
362 del a, b |
|
363 expect(gc.collect(), 4, "boom2_new") |
|
364 expect(len(gc.garbage), garbagelen, "boom2_new") |
|
365 |
|
366 def test_get_referents(): |
|
367 alist = [1, 3, 5] |
|
368 got = gc.get_referents(alist) |
|
369 got.sort() |
|
370 expect(got, alist, "get_referents") |
|
371 |
|
372 atuple = tuple(alist) |
|
373 got = gc.get_referents(atuple) |
|
374 got.sort() |
|
375 expect(got, alist, "get_referents") |
|
376 |
|
377 adict = {1: 3, 5: 7} |
|
378 expected = [1, 3, 5, 7] |
|
379 got = gc.get_referents(adict) |
|
380 got.sort() |
|
381 expect(got, expected, "get_referents") |
|
382 |
|
383 got = gc.get_referents([1, 2], {3: 4}, (0, 0, 0)) |
|
384 got.sort() |
|
385 expect(got, [0, 0] + range(5), "get_referents") |
|
386 |
|
387 expect(gc.get_referents(1, 'a', 4j), [], "get_referents") |
|
388 |
|
389 # Bug 1055820 has several tests of longstanding bugs involving weakrefs and |
|
390 # cyclic gc. |
|
391 |
|
392 # An instance of C1055820 has a self-loop, so becomes cyclic trash when |
|
393 # unreachable. |
|
394 class C1055820(object): |
|
395 def __init__(self, i): |
|
396 self.i = i |
|
397 self.loop = self |
|
398 |
|
399 class GC_Detector(object): |
|
400 # Create an instance I. Then gc hasn't happened again so long as |
|
401 # I.gc_happened is false. |
|
402 |
|
403 def __init__(self): |
|
404 self.gc_happened = False |
|
405 |
|
406 def it_happened(ignored): |
|
407 self.gc_happened = True |
|
408 |
|
409 # Create a piece of cyclic trash that triggers it_happened when |
|
410 # gc collects it. |
|
411 self.wr = weakref.ref(C1055820(666), it_happened) |
|
412 |
|
413 def test_bug1055820b(): |
|
414 # Corresponds to temp2b.py in the bug report. |
|
415 |
|
416 ouch = [] |
|
417 def callback(ignored): |
|
418 ouch[:] = [wr() for wr in WRs] |
|
419 |
|
420 Cs = [C1055820(i) for i in range(2)] |
|
421 WRs = [weakref.ref(c, callback) for c in Cs] |
|
422 c = None |
|
423 |
|
424 gc.collect() |
|
425 expect(len(ouch), 0, "bug1055820b") |
|
426 # Make the two instances trash, and collect again. The bug was that |
|
427 # the callback materialized a strong reference to an instance, but gc |
|
428 # cleared the instance's dict anyway. |
|
429 Cs = None |
|
430 gc.collect() |
|
431 expect(len(ouch), 2, "bug1055820b") # else the callbacks didn't run |
|
432 for x in ouch: |
|
433 # If the callback resurrected one of these guys, the instance |
|
434 # would be damaged, with an empty __dict__. |
|
435 expect(x, None, "bug1055820b") |
|
436 |
|
437 def test_bug1055820c(): |
|
438 # Corresponds to temp2c.py in the bug report. This is pretty elaborate. |
|
439 |
|
440 c0 = C1055820(0) |
|
441 # Move c0 into generation 2. |
|
442 gc.collect() |
|
443 |
|
444 c1 = C1055820(1) |
|
445 c1.keep_c0_alive = c0 |
|
446 del c0.loop # now only c1 keeps c0 alive |
|
447 |
|
448 c2 = C1055820(2) |
|
449 c2wr = weakref.ref(c2) # no callback! |
|
450 |
|
451 ouch = [] |
|
452 def callback(ignored): |
|
453 ouch[:] = [c2wr()] |
|
454 |
|
455 # The callback gets associated with a wr on an object in generation 2. |
|
456 c0wr = weakref.ref(c0, callback) |
|
457 |
|
458 c0 = c1 = c2 = None |
|
459 |
|
460 # What we've set up: c0, c1, and c2 are all trash now. c0 is in |
|
461 # generation 2. The only thing keeping it alive is that c1 points to it. |
|
462 # c1 and c2 are in generation 0, and are in self-loops. There's a global |
|
463 # weakref to c2 (c2wr), but that weakref has no callback. There's also |
|
464 # a global weakref to c0 (c0wr), and that does have a callback, and that |
|
465 # callback references c2 via c2wr(). |
|
466 # |
|
467 # c0 has a wr with callback, which references c2wr |
|
468 # ^ |
|
469 # | |
|
470 # | Generation 2 above dots |
|
471 #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . |
|
472 # | Generation 0 below dots |
|
473 # | |
|
474 # | |
|
475 # ^->c1 ^->c2 has a wr but no callback |
|
476 # | | | | |
|
477 # <--v <--v |
|
478 # |
|
479 # So this is the nightmare: when generation 0 gets collected, we see that |
|
480 # c2 has a callback-free weakref, and c1 doesn't even have a weakref. |
|
481 # Collecting generation 0 doesn't see c0 at all, and c0 is the only object |
|
482 # that has a weakref with a callback. gc clears c1 and c2. Clearing c1 |
|
483 # has the side effect of dropping the refcount on c0 to 0, so c0 goes |
|
484 # away (despite that it's in an older generation) and c0's wr callback |
|
485 # triggers. That in turn materializes a reference to c2 via c2wr(), but |
|
486 # c2 gets cleared anyway by gc. |
|
487 |
|
488 # We want to let gc happen "naturally", to preserve the distinction |
|
489 # between generations. |
|
490 junk = [] |
|
491 i = 0 |
|
492 detector = GC_Detector() |
|
493 while not detector.gc_happened: |
|
494 i += 1 |
|
495 if i > 10000: |
|
496 raise TestFailed("gc didn't happen after 10000 iterations") |
|
497 expect(len(ouch), 0, "bug1055820c") |
|
498 junk.append([]) # this will eventually trigger gc |
|
499 |
|
500 expect(len(ouch), 1, "bug1055820c") # else the callback wasn't invoked |
|
501 for x in ouch: |
|
502 # If the callback resurrected c2, the instance would be damaged, |
|
503 # with an empty __dict__. |
|
504 expect(x, None, "bug1055820c") |
|
505 |
|
506 def test_bug1055820d(): |
|
507 # Corresponds to temp2d.py in the bug report. This is very much like |
|
508 # test_bug1055820c, but uses a __del__ method instead of a weakref |
|
509 # callback to sneak in a resurrection of cyclic trash. |
|
510 |
|
511 ouch = [] |
|
512 class D(C1055820): |
|
513 def __del__(self): |
|
514 ouch[:] = [c2wr()] |
|
515 |
|
516 d0 = D(0) |
|
517 # Move all the above into generation 2. |
|
518 gc.collect() |
|
519 |
|
520 c1 = C1055820(1) |
|
521 c1.keep_d0_alive = d0 |
|
522 del d0.loop # now only c1 keeps d0 alive |
|
523 |
|
524 c2 = C1055820(2) |
|
525 c2wr = weakref.ref(c2) # no callback! |
|
526 |
|
527 d0 = c1 = c2 = None |
|
528 |
|
529 # What we've set up: d0, c1, and c2 are all trash now. d0 is in |
|
530 # generation 2. The only thing keeping it alive is that c1 points to it. |
|
531 # c1 and c2 are in generation 0, and are in self-loops. There's a global |
|
532 # weakref to c2 (c2wr), but that weakref has no callback. There are no |
|
533 # other weakrefs. |
|
534 # |
|
535 # d0 has a __del__ method that references c2wr |
|
536 # ^ |
|
537 # | |
|
538 # | Generation 2 above dots |
|
539 #. . . . . . . .|. . . . . . . . . . . . . . . . . . . . . . . . |
|
540 # | Generation 0 below dots |
|
541 # | |
|
542 # | |
|
543 # ^->c1 ^->c2 has a wr but no callback |
|
544 # | | | | |
|
545 # <--v <--v |
|
546 # |
|
547 # So this is the nightmare: when generation 0 gets collected, we see that |
|
548 # c2 has a callback-free weakref, and c1 doesn't even have a weakref. |
|
549 # Collecting generation 0 doesn't see d0 at all. gc clears c1 and c2. |
|
550 # Clearing c1 has the side effect of dropping the refcount on d0 to 0, so |
|
551 # d0 goes away (despite that it's in an older generation) and d0's __del__ |
|
552 # triggers. That in turn materializes a reference to c2 via c2wr(), but |
|
553 # c2 gets cleared anyway by gc. |
|
554 |
|
555 # We want to let gc happen "naturally", to preserve the distinction |
|
556 # between generations. |
|
557 detector = GC_Detector() |
|
558 junk = [] |
|
559 i = 0 |
|
560 while not detector.gc_happened: |
|
561 i += 1 |
|
562 if i > 10000: |
|
563 raise TestFailed("gc didn't happen after 10000 iterations") |
|
564 expect(len(ouch), 0, "bug1055820d") |
|
565 junk.append([]) # this will eventually trigger gc |
|
566 |
|
567 expect(len(ouch), 1, "bug1055820d") # else __del__ wasn't invoked |
|
568 for x in ouch: |
|
569 # If __del__ resurrected c2, the instance would be damaged, with an |
|
570 # empty __dict__. |
|
571 expect(x, None, "bug1055820d") |
|
572 |
|
573 |
|
574 def test_all(): |
|
575 gc.collect() # Delete 2nd generation garbage |
|
576 run_test("lists", test_list) |
|
577 run_test("dicts", test_dict) |
|
578 run_test("tuples", test_tuple) |
|
579 run_test("classes", test_class) |
|
580 run_test("new style classes", test_newstyleclass) |
|
581 run_test("instances", test_instance) |
|
582 run_test("new instances", test_newinstance) |
|
583 run_test("methods", test_method) |
|
584 run_test("functions", test_function) |
|
585 run_test("frames", test_frame) |
|
586 run_test("finalizers", test_finalizer) |
|
587 run_test("finalizers (new class)", test_finalizer_newclass) |
|
588 run_test("__del__", test_del) |
|
589 run_test("__del__ (new class)", test_del_newclass) |
|
590 run_test("get_count()", test_get_count) |
|
591 run_test("collect(n)", test_collect_generations) |
|
592 run_test("saveall", test_saveall) |
|
593 run_test("trashcan", test_trashcan) |
|
594 run_test("boom", test_boom) |
|
595 run_test("boom2", test_boom2) |
|
596 run_test("boom_new", test_boom_new) |
|
597 run_test("boom2_new", test_boom2_new) |
|
598 run_test("get_referents", test_get_referents) |
|
599 run_test("bug1055820b", test_bug1055820b) |
|
600 |
|
601 gc.enable() |
|
602 try: |
|
603 run_test("bug1055820c", test_bug1055820c) |
|
604 finally: |
|
605 gc.disable() |
|
606 |
|
607 gc.enable() |
|
608 try: |
|
609 run_test("bug1055820d", test_bug1055820d) |
|
610 finally: |
|
611 gc.disable() |
|
612 |
|
613 def test(): |
|
614 if verbose: |
|
615 print "disabling automatic collection" |
|
616 enabled = gc.isenabled() |
|
617 gc.disable() |
|
618 verify(not gc.isenabled()) |
|
619 debug = gc.get_debug() |
|
620 gc.set_debug(debug & ~gc.DEBUG_LEAK) # this test is supposed to leak |
|
621 |
|
622 try: |
|
623 test_all() |
|
624 finally: |
|
625 gc.set_debug(debug) |
|
626 # test gc.enable() even if GC is disabled by default |
|
627 if verbose: |
|
628 print "restoring automatic collection" |
|
629 # make sure to always test gc.enable() |
|
630 gc.enable() |
|
631 verify(gc.isenabled()) |
|
632 if not enabled: |
|
633 gc.disable() |
|
634 |
|
635 |
|
636 test() |