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