~ubuntu-branches/debian/sid/meliae/sid

« back to all changes in this revision

Viewing changes to meliae/tests/test__scanner.py

  • Committer: Bazaar Package Importer
  • Author(s): Jelmer Vernooij
  • Date: 2009-12-19 18:23:37 UTC
  • Revision ID: james.westby@ubuntu.com-20091219182337-t09txw6ca1yfysn9
Tags: upstream-0.2.0
ImportĀ upstreamĀ versionĀ 0.2.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2009 Canonical Ltd
 
2
 
3
# This program is free software: you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License version 3 as
 
5
# published by the Free Software Foundation.
 
6
 
7
# This program is distributed in the hope that it will be useful, but
 
8
# WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
10
# General Public License for more details.
 
11
 
12
# You should have received a copy of the GNU General Public License
 
13
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
14
 
 
15
"""Tests for the object scanner."""
 
16
 
 
17
import gc
 
18
import sys
 
19
import tempfile
 
20
import types
 
21
 
 
22
from meliae import (
 
23
    _scanner,
 
24
    tests,
 
25
    )
 
26
 
 
27
 
 
28
STRING_BASE = 8
 
29
STRING_SCALING = 4
 
30
 
 
31
 
 
32
class TestSizeOf(tests.TestCase):
 
33
 
 
34
    def assertSizeOf(self, num_words, obj, extra_size=0, has_gc=True):
 
35
        expected_size = extra_size + num_words * _scanner._word_size
 
36
        if has_gc:
 
37
            expected_size += _scanner._gc_head_size
 
38
        self.assertEqual(expected_size, _scanner.size_of(obj))
 
39
 
 
40
    def test_empty_string(self):
 
41
        self.assertSizeOf(STRING_SCALING, '', extra_size=0+STRING_BASE, has_gc=False)
 
42
 
 
43
    def test_short_string(self):
 
44
        self.assertSizeOf(STRING_SCALING, 'a', extra_size=1+STRING_BASE, has_gc=False)
 
45
 
 
46
    def test_long_string(self):
 
47
        self.assertSizeOf(STRING_SCALING, ('abcd'*25)*1024,
 
48
                          extra_size=100*1024+STRING_BASE, has_gc=False)
 
49
 
 
50
    def test_tuple(self):
 
51
        self.assertSizeOf(3, ())
 
52
 
 
53
    def test_tuple_one(self):
 
54
        self.assertSizeOf(3+1, ('a',))
 
55
 
 
56
    def test_tuple_n(self):
 
57
        self.assertSizeOf(3+3, (1, 2, 3))
 
58
 
 
59
    def test_empty_list(self):
 
60
        self.assertSizeOf(5, [])
 
61
 
 
62
    def test_list_with_one(self):
 
63
        self.assertSizeOf(5+1, [1])
 
64
 
 
65
    def test_list_with_three(self):
 
66
        self.assertSizeOf(5+3, [1, 2, 3])
 
67
 
 
68
    def test_int(self):
 
69
        self.assertSizeOf(3, 1, has_gc=False)
 
70
 
 
71
    def test_list_appended(self):
 
72
        # Lists over-allocate when you append to them, we want the *allocated*
 
73
        # size
 
74
        lst = []
 
75
        lst.append(1)
 
76
        self.assertSizeOf(5+4, lst)
 
77
 
 
78
    def test_empty_set(self):
 
79
        self.assertSizeOf(25, set())
 
80
        self.assertSizeOf(25, frozenset())
 
81
 
 
82
    def test_small_sets(self):
 
83
        self.assertSizeOf(25, set(range(1)))
 
84
        self.assertSizeOf(25, set(range(2)))
 
85
        self.assertSizeOf(25, set(range(3)))
 
86
        self.assertSizeOf(25, set(range(4)))
 
87
        self.assertSizeOf(25, set(range(5)))
 
88
        self.assertSizeOf(25, frozenset(range(3)))
 
89
 
 
90
    def test_medium_sets(self):
 
91
        self.assertSizeOf(25 + 512*2, set(range(100)))
 
92
        self.assertSizeOf(25 + 512*2, frozenset(range(100)))
 
93
 
 
94
    def test_empty_dict(self):
 
95
        self.assertSizeOf(31, dict())
 
96
 
 
97
    def test_small_dict(self):
 
98
        self.assertSizeOf(31, dict.fromkeys(range(1)))
 
99
        self.assertSizeOf(31, dict.fromkeys(range(2)))
 
100
        self.assertSizeOf(31, dict.fromkeys(range(3)))
 
101
        self.assertSizeOf(31, dict.fromkeys(range(4)))
 
102
        self.assertSizeOf(31, dict.fromkeys(range(5)))
 
103
 
 
104
    def test_medium_dict(self):
 
105
        self.assertSizeOf(31+512*3, dict.fromkeys(range(100)))
 
106
 
 
107
    def test_basic_types(self):
 
108
        type_size = 106
 
109
        if sys.version_info[:2] >= (2, 6):
 
110
            type_size = 109
 
111
        self.assertSizeOf(type_size, dict)
 
112
        self.assertSizeOf(type_size, set)
 
113
        self.assertSizeOf(type_size, tuple)
 
114
 
 
115
    def test_user_type(self):
 
116
        class Foo(object):
 
117
            pass
 
118
        if sys.version_info[:2] >= (2, 6):
 
119
            self.assertSizeOf(109, Foo)
 
120
        else:
 
121
            self.assertSizeOf(106, Foo)
 
122
 
 
123
    def test_simple_object(self):
 
124
        obj = object()
 
125
        self.assertSizeOf(2, obj, has_gc=False)
 
126
 
 
127
    def test_user_instance(self):
 
128
        class Foo(object):
 
129
            pass
 
130
        # This has a pointer to a dict and a weakref list
 
131
        f = Foo()
 
132
        self.assertSizeOf(4, f)
 
133
 
 
134
    def test_slotted_instance(self):
 
135
        class One(object):
 
136
            __slots__ = ['one']
 
137
        # The basic object plus memory for one member
 
138
        self.assertSizeOf(3, One())
 
139
        class Two(One):
 
140
            __slots__ = ['two']
 
141
        self.assertSizeOf(4, Two())
 
142
 
 
143
    def test_empty_unicode(self):
 
144
        self.assertSizeOf(6, u'', extra_size=0, has_gc=False)
 
145
 
 
146
    def test_small_unicode(self):
 
147
        self.assertSizeOf(6, u'a', extra_size=_scanner._unicode_size*1,
 
148
                          has_gc=False)
 
149
        self.assertSizeOf(6, u'abcd', extra_size=_scanner._unicode_size*4,
 
150
                          has_gc=False)
 
151
        self.assertSizeOf(6, u'\xbe\xe5', extra_size=_scanner._unicode_size*2,
 
152
                          has_gc=False)
 
153
 
 
154
    def test_None(self):
 
155
        self.assertSizeOf(2, None, has_gc=False)
 
156
 
 
157
    def test__sizeof__instance(self):
 
158
        # __sizeof__ appears to have been introduced in python 2.6, and
 
159
        # is meant to return the number of bytes allocated to this
 
160
        # object. It does not include GC overhead, that seems to be added back
 
161
        # in as part of sys.getsizeof(). So meliae does the same in size_of()
 
162
        class CustomSize(object):
 
163
            def __init__(self, size):
 
164
                self.size = size
 
165
            def __sizeof__(self):
 
166
                return self.size
 
167
        self.assertSizeOf(0, CustomSize(10), 10, has_gc=True)
 
168
        self.assertSizeOf(0, CustomSize(20), 20, has_gc=True)
 
169
        # If we get '-1' as the size we assume something is wrong, and fall
 
170
        # back to the original size
 
171
        self.assertSizeOf(4, CustomSize(-1), has_gc=True)
 
172
 
 
173
 
 
174
def _string_to_json(s):
 
175
    out = ['"']
 
176
    for c in s:
 
177
        if c <= '\x1f' or c > '\x7e':
 
178
            out.append(r'\u%04x' % ord(c))
 
179
        elif c in r'\/"':
 
180
            # Simple escape
 
181
            out.append('\\' + c)
 
182
        else:
 
183
            out.append(c)
 
184
    out.append('"')
 
185
    return ''.join(out)
 
186
 
 
187
 
 
188
def _unicode_to_json(u):
 
189
    out = ['"']
 
190
    for c in u:
 
191
        if c <= u'\u001f' or c > u'\u007e':
 
192
            out.append(r'\u%04x' % ord(c))
 
193
        elif c in ur'\/"':
 
194
            # Simple escape
 
195
            out.append('\\' + str(c))
 
196
        else:
 
197
            out.append(str(c))
 
198
    out.append('"')
 
199
    return ''.join(out)
 
200
 
 
201
 
 
202
class TestJSONString(tests.TestCase):
 
203
 
 
204
    def assertJSONString(self, exp, input):
 
205
        self.assertEqual(exp, _string_to_json(input))
 
206
 
 
207
    def test_empty_string(self):
 
208
        self.assertJSONString('""', '')
 
209
 
 
210
    def test_simple_strings(self):
 
211
        self.assertJSONString('"foo"', 'foo')
 
212
        self.assertJSONString('"aoeu aoeu"', 'aoeu aoeu')
 
213
 
 
214
    def test_simple_escapes(self):
 
215
        self.assertJSONString(r'"\\x\/y\""', r'\x/y"')
 
216
 
 
217
    def test_control_escapes(self):
 
218
        self.assertJSONString(r'"\u0000\u0001\u0002\u001f"', '\x00\x01\x02\x1f')
 
219
 
 
220
 
 
221
class TestJSONUnicode(tests.TestCase):
 
222
 
 
223
    def assertJSONUnicode(self, exp, input):
 
224
        val = _unicode_to_json(input)
 
225
        self.assertEqual(exp, val)
 
226
        self.assertTrue(isinstance(val, str))
 
227
 
 
228
    def test_empty(self):
 
229
        self.assertJSONUnicode('""', u'')
 
230
 
 
231
    def test_ascii_chars(self):
 
232
        self.assertJSONUnicode('"abcdefg"', u'abcdefg')
 
233
 
 
234
    def test_unicode_chars(self):
 
235
        self.assertJSONUnicode(r'"\u0012\u00b5\u2030\u001f"',
 
236
                               u'\x12\xb5\u2030\x1f')
 
237
 
 
238
    def test_simple_escapes(self):
 
239
        self.assertJSONUnicode(r'"\\x\/y\""', ur'\x/y"')
 
240
 
 
241
 
 
242
# A pure python implementation of dump_object_info
 
243
def _py_dump_json_obj(obj):
 
244
    klass = getattr(obj, '__class__', None)
 
245
    if klass is None:
 
246
        # This is an old style class
 
247
        klass = type(obj)
 
248
    content = [(
 
249
        '{"address": %d'
 
250
        ', "type": %s'
 
251
        ', "size": %d'
 
252
        ) % (id(obj), _string_to_json(klass.__name__),
 
253
             _scanner.size_of(obj))
 
254
        ]
 
255
    name = getattr(obj, '__name__', None)
 
256
    if name is not None:
 
257
        content.append(', "name": %s' % (_string_to_json(name),))
 
258
    if getattr(obj, '__len__', None) is not None:
 
259
        content.append(', "len": %s' % (len(obj),))
 
260
    if isinstance(obj, str):
 
261
        content.append(', "value": %s' % (_string_to_json(obj[:100]),))
 
262
    elif isinstance(obj, unicode):
 
263
        content.append(', "value": %s' % (_unicode_to_json(obj[:100]),))
 
264
    elif obj is True:
 
265
        content.append(', "value": "True"')
 
266
    elif obj is False:
 
267
        content.append(', "value": "False"')
 
268
    elif isinstance(obj, int):
 
269
        content.append(', "value": %d' % (obj,))
 
270
    first = True
 
271
    content.append(', "refs": [')
 
272
    ref_strs = []
 
273
    for ref in gc.get_referents(obj):
 
274
        ref_strs.append('%d' % (id(ref),))
 
275
    content.append(', '.join(ref_strs))
 
276
    content.append(']')
 
277
    content.append('}\n')
 
278
    return ''.join(content)
 
279
 
 
280
 
 
281
def py_dump_object_info(obj, nodump=None):
 
282
    if nodump is not None:
 
283
        if obj is nodump:
 
284
            return ''
 
285
        try:
 
286
            if obj in nodump:
 
287
                return ''
 
288
        except TypeError:
 
289
            # This is probably an 'unhashable' object, which means it can't be
 
290
            # put into a set, and thus we are sure it isn't in the 'nodump'
 
291
            # set.
 
292
            pass
 
293
    obj_info = _py_dump_json_obj(obj)
 
294
    # Now we walk again, for certain types we dump them directly
 
295
    child_vals = []
 
296
    for ref in gc.get_referents(obj):
 
297
        if (isinstance(ref, (str, unicode, int, types.CodeType))
 
298
            or ref is None
 
299
            or type(ref) is object):
 
300
            # These types have no traverse func, so we dump them right away
 
301
            if nodump is None or ref not in nodump:
 
302
                child_vals.append(_py_dump_json_obj(ref))
 
303
    return obj_info + ''.join(child_vals)
 
304
 
 
305
 
 
306
class TestPyDumpJSONObj(tests.TestCase):
 
307
 
 
308
    def assertDumpText(self, expected, obj):
 
309
        self.assertEqual(expected, _py_dump_json_obj(obj))
 
310
 
 
311
    def test_str(self):
 
312
        mystr = 'a string'
 
313
        self.assertDumpText(
 
314
            '{"address": %d, "type": "str", "size": %d, "len": 8'
 
315
            ', "value": "a string", "refs": []}\n'
 
316
            % (id(mystr), _scanner.size_of(mystr)),
 
317
            mystr)
 
318
        mystr = 'a \\str/with"control'
 
319
        self.assertDumpText(
 
320
            '{"address": %d, "type": "str", "size": %d, "len": 19'
 
321
            ', "value": "a \\\\str\\/with\\"control", "refs": []}\n'
 
322
            % (id(mystr), _scanner.size_of(mystr)),
 
323
            mystr)
 
324
 
 
325
    def test_unicode(self):
 
326
        myu = u'a \xb5nicode'
 
327
        self.assertDumpText(
 
328
            '{"address": %d, "type": "unicode", "size": %d'
 
329
            ', "len": 9, "value": "a \\u00b5nicode", "refs": []}\n' % (
 
330
                id(myu), _scanner.size_of(myu)),
 
331
            myu)
 
332
 
 
333
    def test_obj(self):
 
334
        obj = object()
 
335
        self.assertDumpText(
 
336
            '{"address": %d, "type": "object", "size": %d, "refs": []}\n'
 
337
            % (id(obj), _scanner.size_of(obj)), obj)
 
338
 
 
339
    def test_tuple(self):
 
340
        a = object()
 
341
        b = object()
 
342
        t = (a, b)
 
343
        self.assertDumpText(
 
344
            '{"address": %d, "type": "tuple", "size": %d'
 
345
            ', "len": 2, "refs": [%d, %d]}\n'
 
346
            % (id(t), _scanner.size_of(t), id(b), id(a)), t)
 
347
 
 
348
    def test_module(self):
 
349
        m = _scanner
 
350
        self.assertDumpText(
 
351
            '{"address": %d, "type": "module", "size": %d'
 
352
            ', "name": "meliae._scanner", "refs": [%d]}\n'
 
353
            % (id(m), _scanner.size_of(m), id(m.__dict__)), m)
 
354
 
 
355
    def test_bool(self):
 
356
        a = True
 
357
        b = False
 
358
        self.assertDumpText(
 
359
            '{"address": %d, "type": "bool", "size": %d'
 
360
            ', "value": "True", "refs": []}\n'
 
361
            % (id(a), _scanner.size_of(a)), a)
 
362
        self.assertDumpText(
 
363
            '{"address": %d, "type": "bool", "size": %d'
 
364
            ', "value": "False", "refs": []}\n'
 
365
            % (id(b), _scanner.size_of(b)), b)
 
366
 
 
367
 
 
368
class TestDumpInfo(tests.TestCase):
 
369
    """dump_object_info should give the same result at py_dump_object_info"""
 
370
 
 
371
    def assertDumpInfo(self, obj, nodump=None):
 
372
        t = tempfile.TemporaryFile(prefix='meliae-')
 
373
        # On some platforms TemporaryFile returns a wrapper object with 'file'
 
374
        # being the real object, on others, the returned object *is* the real
 
375
        # file object
 
376
        t_file = getattr(t, 'file', t)
 
377
        _scanner.dump_object_info(t_file, obj, nodump=nodump)
 
378
        t.seek(0)
 
379
        as_bytes = t.read()
 
380
        self.assertEqual(py_dump_object_info(obj, nodump=nodump), as_bytes)
 
381
        as_list = []
 
382
        _scanner.dump_object_info(as_list.append, obj, nodump=nodump)
 
383
        self.assertEqual(as_bytes, ''.join(as_list))
 
384
 
 
385
    def test_dump_int(self):
 
386
        self.assertDumpInfo(1)
 
387
 
 
388
    def test_dump_tuple(self):
 
389
        obj1 = object()
 
390
        obj2 = object()
 
391
        t = (obj1, obj2)
 
392
        self.assertDumpInfo(t)
 
393
 
 
394
    def test_dump_dict(self):
 
395
        key = object()
 
396
        val = object()
 
397
        d = {key: val}
 
398
        self.assertDumpInfo(d)
 
399
 
 
400
    def test_class(self):
 
401
        class Foo(object):
 
402
            pass
 
403
        class Child(Foo):
 
404
            pass
 
405
        self.assertDumpInfo(Child)
 
406
 
 
407
    def test_module(self):
 
408
        self.assertDumpInfo(_scanner)
 
409
 
 
410
    def test_instance(self):
 
411
        class Foo(object):
 
412
            pass
 
413
        f = Foo()
 
414
        self.assertDumpInfo(f)
 
415
 
 
416
    def test_slot_instance(self):
 
417
        class One(object):
 
418
            __slots__ = ['one']
 
419
        one = One()
 
420
        one.one = object()
 
421
        self.assertDumpInfo(one)
 
422
 
 
423
        class Two(One):
 
424
            __slots__ = ['two']
 
425
        two = Two()
 
426
        two.one = object()
 
427
        two.two = object()
 
428
        self.assertDumpInfo(two)
 
429
 
 
430
    def test_None(self):
 
431
        self.assertDumpInfo(None)
 
432
 
 
433
    def test_str(self):
 
434
        self.assertDumpInfo('this is a short \x00 \x1f \xffstring\n')
 
435
        self.assertDumpInfo('a \\string / with " control chars')
 
436
 
 
437
    def test_long_str(self):
 
438
        self.assertDumpInfo('abcd'*1000)
 
439
 
 
440
    def test_unicode(self):
 
441
        self.assertDumpInfo(u'this is a short \u1234 \x00 \x1f \xffstring\n')
 
442
 
 
443
    def test_long_unicode(self):
 
444
        self.assertDumpInfo(u'abcd'*1000)
 
445
 
 
446
    def test_nodump(self):
 
447
        self.assertDumpInfo(None, nodump=set([None]))
 
448
 
 
449
    def test_ref_nodump(self):
 
450
        self.assertDumpInfo((None, None), nodump=set([None]))
 
451
 
 
452
    def test_nodump_the_nodump(self):
 
453
        nodump = set([None, 1])
 
454
        t = (20, nodump)
 
455
        self.assertDumpInfo(t, nodump=nodump)
 
456
 
 
457
    def test_function(self):
 
458
        def myfunction():
 
459
            pass
 
460
        self.assertDumpInfo(myfunction)
 
461
 
 
462
    def test_class(self):
 
463
        class MyClass(object):
 
464
            pass
 
465
        self.assertDumpInfo(MyClass, nodump=set([object]))
 
466
        inst = MyClass()
 
467
        self.assertDumpInfo(inst)
 
468
 
 
469
    def test_old_style_class(self):
 
470
        class MyOldClass:
 
471
            pass
 
472
        self.assertDumpInfo(MyOldClass)
 
473
 
 
474
    def test_bool(self):
 
475
        self.assertDumpInfo(True)
 
476
        self.assertDumpInfo(False)
 
477
 
 
478
 
 
479
class TestGetReferents(tests.TestCase):
 
480
 
 
481
    def test_list_referents(self):
 
482
        l = ['one', 2, object(), 4.0]
 
483
        self.assertEqual(gc.get_referents(l), _scanner.get_referents(l))