test_inspect.py revision aa98058cc44ba20f35c106d20918c6196b737561
1import sys
2import types
3import unittest
4import inspect
5import datetime
6
7from test.test_support import TESTFN, run_unittest
8
9from test import inspect_fodder as mod
10from test import inspect_fodder2 as mod2
11
12# C module for test_findsource_binary
13import unicodedata
14
15# Functions tested in this suite:
16# ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode,
17# isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers,
18# getdoc, getfile, getmodule, getsourcefile, getcomments, getsource,
19# getclasstree, getargspec, getargvalues, formatargspec, formatargvalues,
20# currentframe, stack, trace, isdatadescriptor
21
22# NOTE: There are some additional tests relating to interaction with
23#       zipimport in the test_zipimport_support test module.
24
25modfile = mod.__file__
26if modfile.endswith(('c', 'o')):
27    modfile = modfile[:-1]
28
29import __builtin__
30
31try:
32    1/0
33except:
34    tb = sys.exc_traceback
35
36git = mod.StupidGit()
37
38class IsTestBase(unittest.TestCase):
39    predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode,
40                      inspect.isframe, inspect.isfunction, inspect.ismethod,
41                      inspect.ismodule, inspect.istraceback,
42                      inspect.isgenerator, inspect.isgeneratorfunction])
43
44    def istest(self, predicate, exp):
45        obj = eval(exp)
46        self.assertTrue(predicate(obj), '%s(%s)' % (predicate.__name__, exp))
47
48        for other in self.predicates - set([predicate]):
49            if predicate == inspect.isgeneratorfunction and\
50               other == inspect.isfunction:
51                continue
52            self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp))
53
54def generator_function_example(self):
55    for i in xrange(2):
56        yield i
57
58class TestPredicates(IsTestBase):
59    def test_sixteen(self):
60        count = len(filter(lambda x:x.startswith('is'), dir(inspect)))
61        # This test is here for remember you to update Doc/library/inspect.rst
62        # which claims there are 16 such functions
63        expected = 16
64        err_msg = "There are %d (not %d) is* functions" % (count, expected)
65        self.assertEqual(count, expected, err_msg)
66
67
68    def test_excluding_predicates(self):
69        self.istest(inspect.isbuiltin, 'sys.exit')
70        self.istest(inspect.isbuiltin, '[].append')
71        self.istest(inspect.iscode, 'mod.spam.func_code')
72        self.istest(inspect.isframe, 'tb.tb_frame')
73        self.istest(inspect.isfunction, 'mod.spam')
74        self.istest(inspect.ismethod, 'mod.StupidGit.abuse')
75        self.istest(inspect.ismethod, 'git.argue')
76        self.istest(inspect.ismodule, 'mod')
77        self.istest(inspect.istraceback, 'tb')
78        self.istest(inspect.isdatadescriptor, '__builtin__.file.closed')
79        self.istest(inspect.isdatadescriptor, '__builtin__.file.softspace')
80        self.istest(inspect.isgenerator, '(x for x in xrange(2))')
81        self.istest(inspect.isgeneratorfunction, 'generator_function_example')
82        if hasattr(types, 'GetSetDescriptorType'):
83            self.istest(inspect.isgetsetdescriptor,
84                        'type(tb.tb_frame).f_locals')
85        else:
86            self.assertFalse(inspect.isgetsetdescriptor(type(tb.tb_frame).f_locals))
87        if hasattr(types, 'MemberDescriptorType'):
88            self.istest(inspect.ismemberdescriptor, 'datetime.timedelta.days')
89        else:
90            self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days))
91
92    def test_isroutine(self):
93        self.assertTrue(inspect.isroutine(mod.spam))
94        self.assertTrue(inspect.isroutine([].count))
95
96    def test_isclass(self):
97        self.istest(inspect.isclass, 'mod.StupidGit')
98        self.assertTrue(inspect.isclass(list))
99
100        class newstyle(object): pass
101        self.assertTrue(inspect.isclass(newstyle))
102
103        class CustomGetattr(object):
104            def __getattr__(self, attr):
105                return None
106        self.assertFalse(inspect.isclass(CustomGetattr()))
107
108    def test_get_slot_members(self):
109        class C(object):
110            __slots__ = ("a", "b")
111
112        x = C()
113        x.a = 42
114        members = dict(inspect.getmembers(x))
115        self.assertIn('a', members)
116        self.assertNotIn('b', members)
117
118    def test_isabstract(self):
119        from abc import ABCMeta, abstractmethod
120
121        class AbstractClassExample(object):
122            __metaclass__ = ABCMeta
123
124            @abstractmethod
125            def foo(self):
126                pass
127
128        class ClassExample(AbstractClassExample):
129            def foo(self):
130                pass
131
132        a = ClassExample()
133
134        # Test general behaviour.
135        self.assertTrue(inspect.isabstract(AbstractClassExample))
136        self.assertFalse(inspect.isabstract(ClassExample))
137        self.assertFalse(inspect.isabstract(a))
138        self.assertFalse(inspect.isabstract(int))
139        self.assertFalse(inspect.isabstract(5))
140
141
142class TestInterpreterStack(IsTestBase):
143    def __init__(self, *args, **kwargs):
144        unittest.TestCase.__init__(self, *args, **kwargs)
145
146        git.abuse(7, 8, 9)
147
148    def test_abuse_done(self):
149        self.istest(inspect.istraceback, 'git.ex[2]')
150        self.istest(inspect.isframe, 'mod.fr')
151
152    def test_stack(self):
153        self.assertTrue(len(mod.st) >= 5)
154        self.assertEqual(mod.st[0][1:],
155             (modfile, 16, 'eggs', ['    st = inspect.stack()\n'], 0))
156        self.assertEqual(mod.st[1][1:],
157             (modfile, 9, 'spam', ['    eggs(b + d, c + f)\n'], 0))
158        self.assertEqual(mod.st[2][1:],
159             (modfile, 43, 'argue', ['            spam(a, b, c)\n'], 0))
160        self.assertEqual(mod.st[3][1:],
161             (modfile, 39, 'abuse', ['        self.argue(a, b, c)\n'], 0))
162
163    def test_trace(self):
164        self.assertEqual(len(git.tr), 3)
165        self.assertEqual(git.tr[0][1:], (modfile, 43, 'argue',
166                                         ['            spam(a, b, c)\n'], 0))
167        self.assertEqual(git.tr[1][1:], (modfile, 9, 'spam',
168                                         ['    eggs(b + d, c + f)\n'], 0))
169        self.assertEqual(git.tr[2][1:], (modfile, 18, 'eggs',
170                                         ['    q = y / 0\n'], 0))
171
172    def test_frame(self):
173        args, varargs, varkw, locals = inspect.getargvalues(mod.fr)
174        self.assertEqual(args, ['x', 'y'])
175        self.assertEqual(varargs, None)
176        self.assertEqual(varkw, None)
177        self.assertEqual(locals, {'x': 11, 'p': 11, 'y': 14})
178        self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
179                         '(x=11, y=14)')
180
181    def test_previous_frame(self):
182        args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back)
183        self.assertEqual(args, ['a', 'b', 'c', 'd', ['e', ['f']]])
184        self.assertEqual(varargs, 'g')
185        self.assertEqual(varkw, 'h')
186        self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
187             '(a=7, b=8, c=9, d=3, (e=4, (f=5,)), *g=(), **h={})')
188
189class GetSourceBase(unittest.TestCase):
190    # Subclasses must override.
191    fodderFile = None
192
193    def __init__(self, *args, **kwargs):
194        unittest.TestCase.__init__(self, *args, **kwargs)
195
196        with open(inspect.getsourcefile(self.fodderFile)) as fp:
197            self.source = fp.read()
198
199    def sourcerange(self, top, bottom):
200        lines = self.source.split("\n")
201        return "\n".join(lines[top-1:bottom]) + "\n"
202
203    def assertSourceEqual(self, obj, top, bottom):
204        self.assertEqual(inspect.getsource(obj),
205                         self.sourcerange(top, bottom))
206
207class TestRetrievingSourceCode(GetSourceBase):
208    fodderFile = mod
209
210    def test_getclasses(self):
211        classes = inspect.getmembers(mod, inspect.isclass)
212        self.assertEqual(classes,
213                         [('FesteringGob', mod.FesteringGob),
214                          ('MalodorousPervert', mod.MalodorousPervert),
215                          ('ParrotDroppings', mod.ParrotDroppings),
216                          ('StupidGit', mod.StupidGit)])
217        tree = inspect.getclasstree([cls[1] for cls in classes], 1)
218        self.assertEqual(tree,
219                         [(mod.ParrotDroppings, ()),
220                          (mod.StupidGit, ()),
221                          [(mod.MalodorousPervert, (mod.StupidGit,)),
222                           [(mod.FesteringGob, (mod.MalodorousPervert,
223                                                   mod.ParrotDroppings))
224                            ]
225                           ]
226                          ])
227
228    def test_getfunctions(self):
229        functions = inspect.getmembers(mod, inspect.isfunction)
230        self.assertEqual(functions, [('eggs', mod.eggs),
231                                     ('spam', mod.spam)])
232
233    def test_getdoc(self):
234        self.assertEqual(inspect.getdoc(mod), 'A module docstring.')
235        self.assertEqual(inspect.getdoc(mod.StupidGit),
236                         'A longer,\n\nindented\n\ndocstring.')
237        self.assertEqual(inspect.getdoc(git.abuse),
238                         'Another\n\ndocstring\n\ncontaining\n\ntabs')
239
240    def test_cleandoc(self):
241        self.assertEqual(inspect.cleandoc('An\n    indented\n    docstring.'),
242                         'An\nindented\ndocstring.')
243
244    def test_getcomments(self):
245        self.assertEqual(inspect.getcomments(mod), '# line 1\n')
246        self.assertEqual(inspect.getcomments(mod.StupidGit), '# line 20\n')
247
248    def test_getmodule(self):
249        # Check actual module
250        self.assertEqual(inspect.getmodule(mod), mod)
251        # Check class (uses __module__ attribute)
252        self.assertEqual(inspect.getmodule(mod.StupidGit), mod)
253        # Check a method (no __module__ attribute, falls back to filename)
254        self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod)
255        # Do it again (check the caching isn't broken)
256        self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod)
257        # Check a builtin
258        self.assertEqual(inspect.getmodule(str), sys.modules["__builtin__"])
259        # Check filename override
260        self.assertEqual(inspect.getmodule(None, modfile), mod)
261
262    def test_getsource(self):
263        self.assertSourceEqual(git.abuse, 29, 39)
264        self.assertSourceEqual(mod.StupidGit, 21, 46)
265
266    def test_getsourcefile(self):
267        self.assertEqual(inspect.getsourcefile(mod.spam), modfile)
268        self.assertEqual(inspect.getsourcefile(git.abuse), modfile)
269
270    def test_getfile(self):
271        self.assertEqual(inspect.getfile(mod.StupidGit), mod.__file__)
272
273    def test_getmodule_recursion(self):
274        from types import ModuleType
275        name = '__inspect_dummy'
276        m = sys.modules[name] = ModuleType(name)
277        m.__file__ = "<string>" # hopefully not a real filename...
278        m.__loader__ = "dummy"  # pretend the filename is understood by a loader
279        exec "def x(): pass" in m.__dict__
280        self.assertEqual(inspect.getsourcefile(m.x.func_code), '<string>')
281        del sys.modules[name]
282        inspect.getmodule(compile('a=10','','single'))
283
284class TestDecorators(GetSourceBase):
285    fodderFile = mod2
286
287    def test_wrapped_decorator(self):
288        self.assertSourceEqual(mod2.wrapped, 14, 17)
289
290    def test_replacing_decorator(self):
291        self.assertSourceEqual(mod2.gone, 9, 10)
292
293class TestOneliners(GetSourceBase):
294    fodderFile = mod2
295    def test_oneline_lambda(self):
296        # Test inspect.getsource with a one-line lambda function.
297        self.assertSourceEqual(mod2.oll, 25, 25)
298
299    def test_threeline_lambda(self):
300        # Test inspect.getsource with a three-line lambda function,
301        # where the second and third lines are _not_ indented.
302        self.assertSourceEqual(mod2.tll, 28, 30)
303
304    def test_twoline_indented_lambda(self):
305        # Test inspect.getsource with a two-line lambda function,
306        # where the second line _is_ indented.
307        self.assertSourceEqual(mod2.tlli, 33, 34)
308
309    def test_onelinefunc(self):
310        # Test inspect.getsource with a regular one-line function.
311        self.assertSourceEqual(mod2.onelinefunc, 37, 37)
312
313    def test_manyargs(self):
314        # Test inspect.getsource with a regular function where
315        # the arguments are on two lines and _not_ indented and
316        # the body on the second line with the last arguments.
317        self.assertSourceEqual(mod2.manyargs, 40, 41)
318
319    def test_twolinefunc(self):
320        # Test inspect.getsource with a regular function where
321        # the body is on two lines, following the argument list and
322        # continued on the next line by a \\.
323        self.assertSourceEqual(mod2.twolinefunc, 44, 45)
324
325    def test_lambda_in_list(self):
326        # Test inspect.getsource with a one-line lambda function
327        # defined in a list, indented.
328        self.assertSourceEqual(mod2.a[1], 49, 49)
329
330    def test_anonymous(self):
331        # Test inspect.getsource with a lambda function defined
332        # as argument to another function.
333        self.assertSourceEqual(mod2.anonymous, 55, 55)
334
335class TestBuggyCases(GetSourceBase):
336    fodderFile = mod2
337
338    def test_with_comment(self):
339        self.assertSourceEqual(mod2.with_comment, 58, 59)
340
341    def test_multiline_sig(self):
342        self.assertSourceEqual(mod2.multiline_sig[0], 63, 64)
343
344    def test_nested_class(self):
345        self.assertSourceEqual(mod2.func69().func71, 71, 72)
346
347    def test_one_liner_followed_by_non_name(self):
348        self.assertSourceEqual(mod2.func77, 77, 77)
349
350    def test_one_liner_dedent_non_name(self):
351        self.assertSourceEqual(mod2.cls82.func83, 83, 83)
352
353    def test_with_comment_instead_of_docstring(self):
354        self.assertSourceEqual(mod2.func88, 88, 90)
355
356    def test_method_in_dynamic_class(self):
357        self.assertSourceEqual(mod2.method_in_dynamic_class, 95, 97)
358
359    @unittest.skipIf(
360        not hasattr(unicodedata, '__file__') or
361            unicodedata.__file__[-4:] in (".pyc", ".pyo"),
362        "unicodedata is not an external binary module")
363    def test_findsource_binary(self):
364        self.assertRaises(IOError, inspect.getsource, unicodedata)
365        self.assertRaises(IOError, inspect.findsource, unicodedata)
366
367# Helper for testing classify_class_attrs.
368def attrs_wo_objs(cls):
369    return [t[:3] for t in inspect.classify_class_attrs(cls)]
370
371class TestClassesAndFunctions(unittest.TestCase):
372    def test_classic_mro(self):
373        # Test classic-class method resolution order.
374        class A:    pass
375        class B(A): pass
376        class C(A): pass
377        class D(B, C): pass
378
379        expected = (D, B, A, C)
380        got = inspect.getmro(D)
381        self.assertEqual(expected, got)
382
383    def test_newstyle_mro(self):
384        # The same w/ new-class MRO.
385        class A(object):    pass
386        class B(A): pass
387        class C(A): pass
388        class D(B, C): pass
389
390        expected = (D, B, C, A, object)
391        got = inspect.getmro(D)
392        self.assertEqual(expected, got)
393
394    def assertArgSpecEquals(self, routine, args_e, varargs_e = None,
395                            varkw_e = None, defaults_e = None,
396                            formatted = None):
397        args, varargs, varkw, defaults = inspect.getargspec(routine)
398        self.assertEqual(args, args_e)
399        self.assertEqual(varargs, varargs_e)
400        self.assertEqual(varkw, varkw_e)
401        self.assertEqual(defaults, defaults_e)
402        if formatted is not None:
403            self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults),
404                             formatted)
405
406    def test_getargspec(self):
407        self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted = '(x, y)')
408
409        self.assertArgSpecEquals(mod.spam,
410                                 ['a', 'b', 'c', 'd', ['e', ['f']]],
411                                 'g', 'h', (3, (4, (5,))),
412                                 '(a, b, c, d=3, (e, (f,))=(4, (5,)), *g, **h)')
413
414    def test_getargspec_method(self):
415        class A(object):
416            def m(self):
417                pass
418        self.assertArgSpecEquals(A.m, ['self'])
419
420    def test_getargspec_sublistofone(self):
421        def sublistOfOne((foo,)): return 1
422        self.assertArgSpecEquals(sublistOfOne, [['foo']])
423
424        def fakeSublistOfOne((foo)): return 1
425        self.assertArgSpecEquals(fakeSublistOfOne, ['foo'])
426
427    def test_classify_oldstyle(self):
428        class A:
429            def s(): pass
430            s = staticmethod(s)
431
432            def c(cls): pass
433            c = classmethod(c)
434
435            def getp(self): pass
436            p = property(getp)
437
438            def m(self): pass
439
440            def m1(self): pass
441
442            datablob = '1'
443
444        attrs = attrs_wo_objs(A)
445        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
446        self.assertIn(('c', 'class method', A), attrs, 'missing class method')
447        self.assertIn(('p', 'property', A), attrs, 'missing property')
448        self.assertIn(('m', 'method', A), attrs, 'missing plain method')
449        self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
450        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
451
452        class B(A):
453            def m(self): pass
454
455        attrs = attrs_wo_objs(B)
456        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
457        self.assertIn(('c', 'class method', A), attrs, 'missing class method')
458        self.assertIn(('p', 'property', A), attrs, 'missing property')
459        self.assertIn(('m', 'method', B), attrs, 'missing plain method')
460        self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
461        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
462
463
464        class C(A):
465            def m(self): pass
466            def c(self): pass
467
468        attrs = attrs_wo_objs(C)
469        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
470        self.assertIn(('c', 'method', C), attrs, 'missing plain method')
471        self.assertIn(('p', 'property', A), attrs, 'missing property')
472        self.assertIn(('m', 'method', C), attrs, 'missing plain method')
473        self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
474        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
475
476        class D(B, C):
477            def m1(self): pass
478
479        attrs = attrs_wo_objs(D)
480        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
481        self.assertIn(('c', 'class method', A), attrs, 'missing class method')
482        self.assertIn(('p', 'property', A), attrs, 'missing property')
483        self.assertIn(('m', 'method', B), attrs, 'missing plain method')
484        self.assertIn(('m1', 'method', D), attrs, 'missing plain method')
485        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
486
487    # Repeat all that, but w/ new-style classes.
488    def test_classify_newstyle(self):
489        class A(object):
490
491            def s(): pass
492            s = staticmethod(s)
493
494            def c(cls): pass
495            c = classmethod(c)
496
497            def getp(self): pass
498            p = property(getp)
499
500            def m(self): pass
501
502            def m1(self): pass
503
504            datablob = '1'
505
506        attrs = attrs_wo_objs(A)
507        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
508        self.assertIn(('c', 'class method', A), attrs, 'missing class method')
509        self.assertIn(('p', 'property', A), attrs, 'missing property')
510        self.assertIn(('m', 'method', A), attrs, 'missing plain method')
511        self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
512        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
513
514        class B(A):
515
516            def m(self): pass
517
518        attrs = attrs_wo_objs(B)
519        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
520        self.assertIn(('c', 'class method', A), attrs, 'missing class method')
521        self.assertIn(('p', 'property', A), attrs, 'missing property')
522        self.assertIn(('m', 'method', B), attrs, 'missing plain method')
523        self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
524        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
525
526
527        class C(A):
528
529            def m(self): pass
530            def c(self): pass
531
532        attrs = attrs_wo_objs(C)
533        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
534        self.assertIn(('c', 'method', C), attrs, 'missing plain method')
535        self.assertIn(('p', 'property', A), attrs, 'missing property')
536        self.assertIn(('m', 'method', C), attrs, 'missing plain method')
537        self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
538        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
539
540        class D(B, C):
541
542            def m1(self): pass
543
544        attrs = attrs_wo_objs(D)
545        self.assertIn(('s', 'static method', A), attrs, 'missing static method')
546        self.assertIn(('c', 'method', C), attrs, 'missing plain method')
547        self.assertIn(('p', 'property', A), attrs, 'missing property')
548        self.assertIn(('m', 'method', B), attrs, 'missing plain method')
549        self.assertIn(('m1', 'method', D), attrs, 'missing plain method')
550        self.assertIn(('datablob', 'data', A), attrs, 'missing data')
551
552def test_main():
553    run_unittest(TestDecorators, TestRetrievingSourceCode, TestOneliners,
554                 TestBuggyCases,
555                 TestInterpreterStack, TestClassesAndFunctions, TestPredicates)
556
557if __name__ == "__main__":
558    test_main()
559