~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/test/test_gc.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

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