1
# Copyright (C) 2009 Canonical Ltd
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.
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.
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/>.
15
"""Tests for the object scanner."""
32
class TestSizeOf(tests.TestCase):
34
def assertSizeOf(self, num_words, obj, extra_size=0, has_gc=True):
35
expected_size = extra_size + num_words * _scanner._word_size
37
expected_size += _scanner._gc_head_size
38
self.assertEqual(expected_size, _scanner.size_of(obj))
40
def test_empty_string(self):
41
self.assertSizeOf(STRING_SCALING, '', extra_size=0+STRING_BASE, has_gc=False)
43
def test_short_string(self):
44
self.assertSizeOf(STRING_SCALING, 'a', extra_size=1+STRING_BASE, has_gc=False)
46
def test_long_string(self):
47
self.assertSizeOf(STRING_SCALING, ('abcd'*25)*1024,
48
extra_size=100*1024+STRING_BASE, has_gc=False)
51
self.assertSizeOf(3, ())
53
def test_tuple_one(self):
54
self.assertSizeOf(3+1, ('a',))
56
def test_tuple_n(self):
57
self.assertSizeOf(3+3, (1, 2, 3))
59
def test_empty_list(self):
60
self.assertSizeOf(5, [])
62
def test_list_with_one(self):
63
self.assertSizeOf(5+1, [1])
65
def test_list_with_three(self):
66
self.assertSizeOf(5+3, [1, 2, 3])
69
self.assertSizeOf(3, 1, has_gc=False)
71
def test_list_appended(self):
72
# Lists over-allocate when you append to them, we want the *allocated*
76
self.assertSizeOf(5+4, lst)
78
def test_empty_set(self):
79
self.assertSizeOf(25, set())
80
self.assertSizeOf(25, frozenset())
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)))
90
def test_medium_sets(self):
91
self.assertSizeOf(25 + 512*2, set(range(100)))
92
self.assertSizeOf(25 + 512*2, frozenset(range(100)))
94
def test_empty_dict(self):
95
self.assertSizeOf(31, dict())
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)))
104
def test_medium_dict(self):
105
self.assertSizeOf(31+512*3, dict.fromkeys(range(100)))
107
def test_basic_types(self):
109
if sys.version_info[:2] >= (2, 6):
111
self.assertSizeOf(type_size, dict)
112
self.assertSizeOf(type_size, set)
113
self.assertSizeOf(type_size, tuple)
115
def test_user_type(self):
118
if sys.version_info[:2] >= (2, 6):
119
self.assertSizeOf(109, Foo)
121
self.assertSizeOf(106, Foo)
123
def test_simple_object(self):
125
self.assertSizeOf(2, obj, has_gc=False)
127
def test_user_instance(self):
130
# This has a pointer to a dict and a weakref list
132
self.assertSizeOf(4, f)
134
def test_slotted_instance(self):
137
# The basic object plus memory for one member
138
self.assertSizeOf(3, One())
141
self.assertSizeOf(4, Two())
143
def test_empty_unicode(self):
144
self.assertSizeOf(6, u'', extra_size=0, has_gc=False)
146
def test_small_unicode(self):
147
self.assertSizeOf(6, u'a', extra_size=_scanner._unicode_size*1,
149
self.assertSizeOf(6, u'abcd', extra_size=_scanner._unicode_size*4,
151
self.assertSizeOf(6, u'\xbe\xe5', extra_size=_scanner._unicode_size*2,
155
self.assertSizeOf(2, None, has_gc=False)
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):
165
def __sizeof__(self):
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)
174
def _string_to_json(s):
177
if c <= '\x1f' or c > '\x7e':
178
out.append(r'\u%04x' % ord(c))
188
def _unicode_to_json(u):
191
if c <= u'\u001f' or c > u'\u007e':
192
out.append(r'\u%04x' % ord(c))
195
out.append('\\' + str(c))
202
class TestJSONString(tests.TestCase):
204
def assertJSONString(self, exp, input):
205
self.assertEqual(exp, _string_to_json(input))
207
def test_empty_string(self):
208
self.assertJSONString('""', '')
210
def test_simple_strings(self):
211
self.assertJSONString('"foo"', 'foo')
212
self.assertJSONString('"aoeu aoeu"', 'aoeu aoeu')
214
def test_simple_escapes(self):
215
self.assertJSONString(r'"\\x\/y\""', r'\x/y"')
217
def test_control_escapes(self):
218
self.assertJSONString(r'"\u0000\u0001\u0002\u001f"', '\x00\x01\x02\x1f')
221
class TestJSONUnicode(tests.TestCase):
223
def assertJSONUnicode(self, exp, input):
224
val = _unicode_to_json(input)
225
self.assertEqual(exp, val)
226
self.assertTrue(isinstance(val, str))
228
def test_empty(self):
229
self.assertJSONUnicode('""', u'')
231
def test_ascii_chars(self):
232
self.assertJSONUnicode('"abcdefg"', u'abcdefg')
234
def test_unicode_chars(self):
235
self.assertJSONUnicode(r'"\u0012\u00b5\u2030\u001f"',
236
u'\x12\xb5\u2030\x1f')
238
def test_simple_escapes(self):
239
self.assertJSONUnicode(r'"\\x\/y\""', ur'\x/y"')
242
# A pure python implementation of dump_object_info
243
def _py_dump_json_obj(obj):
244
klass = getattr(obj, '__class__', None)
246
# This is an old style class
252
) % (id(obj), _string_to_json(klass.__name__),
253
_scanner.size_of(obj))
255
name = getattr(obj, '__name__', 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]),))
265
content.append(', "value": "True"')
267
content.append(', "value": "False"')
268
elif isinstance(obj, int):
269
content.append(', "value": %d' % (obj,))
271
content.append(', "refs": [')
273
for ref in gc.get_referents(obj):
274
ref_strs.append('%d' % (id(ref),))
275
content.append(', '.join(ref_strs))
277
content.append('}\n')
278
return ''.join(content)
281
def py_dump_object_info(obj, nodump=None):
282
if nodump is not None:
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'
293
obj_info = _py_dump_json_obj(obj)
294
# Now we walk again, for certain types we dump them directly
296
for ref in gc.get_referents(obj):
297
if (isinstance(ref, (str, unicode, int, types.CodeType))
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)
306
class TestPyDumpJSONObj(tests.TestCase):
308
def assertDumpText(self, expected, obj):
309
self.assertEqual(expected, _py_dump_json_obj(obj))
314
'{"address": %d, "type": "str", "size": %d, "len": 8'
315
', "value": "a string", "refs": []}\n'
316
% (id(mystr), _scanner.size_of(mystr)),
318
mystr = 'a \\str/with"control'
320
'{"address": %d, "type": "str", "size": %d, "len": 19'
321
', "value": "a \\\\str\\/with\\"control", "refs": []}\n'
322
% (id(mystr), _scanner.size_of(mystr)),
325
def test_unicode(self):
326
myu = u'a \xb5nicode'
328
'{"address": %d, "type": "unicode", "size": %d'
329
', "len": 9, "value": "a \\u00b5nicode", "refs": []}\n' % (
330
id(myu), _scanner.size_of(myu)),
336
'{"address": %d, "type": "object", "size": %d, "refs": []}\n'
337
% (id(obj), _scanner.size_of(obj)), obj)
339
def test_tuple(self):
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)
348
def test_module(self):
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)
359
'{"address": %d, "type": "bool", "size": %d'
360
', "value": "True", "refs": []}\n'
361
% (id(a), _scanner.size_of(a)), a)
363
'{"address": %d, "type": "bool", "size": %d'
364
', "value": "False", "refs": []}\n'
365
% (id(b), _scanner.size_of(b)), b)
368
class TestDumpInfo(tests.TestCase):
369
"""dump_object_info should give the same result at py_dump_object_info"""
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
376
t_file = getattr(t, 'file', t)
377
_scanner.dump_object_info(t_file, obj, nodump=nodump)
380
self.assertEqual(py_dump_object_info(obj, nodump=nodump), as_bytes)
382
_scanner.dump_object_info(as_list.append, obj, nodump=nodump)
383
self.assertEqual(as_bytes, ''.join(as_list))
385
def test_dump_int(self):
386
self.assertDumpInfo(1)
388
def test_dump_tuple(self):
392
self.assertDumpInfo(t)
394
def test_dump_dict(self):
398
self.assertDumpInfo(d)
400
def test_class(self):
405
self.assertDumpInfo(Child)
407
def test_module(self):
408
self.assertDumpInfo(_scanner)
410
def test_instance(self):
414
self.assertDumpInfo(f)
416
def test_slot_instance(self):
421
self.assertDumpInfo(one)
428
self.assertDumpInfo(two)
431
self.assertDumpInfo(None)
434
self.assertDumpInfo('this is a short \x00 \x1f \xffstring\n')
435
self.assertDumpInfo('a \\string / with " control chars')
437
def test_long_str(self):
438
self.assertDumpInfo('abcd'*1000)
440
def test_unicode(self):
441
self.assertDumpInfo(u'this is a short \u1234 \x00 \x1f \xffstring\n')
443
def test_long_unicode(self):
444
self.assertDumpInfo(u'abcd'*1000)
446
def test_nodump(self):
447
self.assertDumpInfo(None, nodump=set([None]))
449
def test_ref_nodump(self):
450
self.assertDumpInfo((None, None), nodump=set([None]))
452
def test_nodump_the_nodump(self):
453
nodump = set([None, 1])
455
self.assertDumpInfo(t, nodump=nodump)
457
def test_function(self):
460
self.assertDumpInfo(myfunction)
462
def test_class(self):
463
class MyClass(object):
465
self.assertDumpInfo(MyClass, nodump=set([object]))
467
self.assertDumpInfo(inst)
469
def test_old_style_class(self):
472
self.assertDumpInfo(MyOldClass)
475
self.assertDumpInfo(True)
476
self.assertDumpInfo(False)
479
class TestGetReferents(tests.TestCase):
481
def test_list_referents(self):
482
l = ['one', 2, object(), 4.0]
483
self.assertEqual(gc.get_referents(l), _scanner.get_referents(l))