blob: 9e970d9df041b858c81815c4d0109e3e496265f1 [file] [log] [blame]
Fred Drake3a28ca82001-08-13 20:26:19 +00001'''
2 Test cases for pyclbr.py
3 Nick Mathewson
4'''
csabella246ff3b2017-07-03 21:31:25 -04005
6import os
Christian Heimes05e8be12008-02-23 18:30:17 +00007import sys
csabella246ff3b2017-07-03 21:31:25 -04008from textwrap import dedent
Guido van Rossum13257902007-06-07 23:15:56 +00009from types import FunctionType, MethodType, BuiltinFunctionType
Fred Drake3a28ca82001-08-13 20:26:19 +000010import pyclbr
Zachary Wareac28b792015-12-04 23:32:23 -060011from unittest import TestCase, main as unittest_main
csabella246ff3b2017-07-03 21:31:25 -040012from test import support
13from functools import partial
Fred Drake3a28ca82001-08-13 20:26:19 +000014
Anthony Baxterc2a5a632004-08-02 06:10:11 +000015StaticMethodType = type(staticmethod(lambda: None))
16ClassMethodType = type(classmethod(lambda c: None))
17
Tim Peters04601062001-08-13 22:25:24 +000018# Here we test the python class browser code.
Fred Drake3a28ca82001-08-13 20:26:19 +000019#
20# The main function in this suite, 'testModule', compares the output
21# of pyclbr with the introspected members of a module. Because pyclbr
22# is imperfect (as designed), testModule is called with a set of
23# members to ignore.
24
Guido van Rossum0ed7aa12002-12-02 14:54:20 +000025class PyclbrTest(TestCase):
Fred Drake3a28ca82001-08-13 20:26:19 +000026
27 def assertListEq(self, l1, l2, ignore):
28 ''' succeed iff {l1} - {ignore} == {l2} - {ignore} '''
Raymond Hettingera690a992003-11-16 16:17:49 +000029 missing = (set(l1) ^ set(l2)) - set(ignore)
Raymond Hettinger91bbd9a2003-05-02 09:06:28 +000030 if missing:
Guido van Rossumbe19ed72007-02-09 05:37:30 +000031 print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr)
Raymond Hettinger91bbd9a2003-05-02 09:06:28 +000032 self.fail("%r missing" % missing.pop())
Tim Peters04601062001-08-13 22:25:24 +000033
Fred Drake3a28ca82001-08-13 20:26:19 +000034 def assertHasattr(self, obj, attr, ignore):
35 ''' succeed iff hasattr(obj,attr) or attr in ignore. '''
36 if attr in ignore: return
Guido van Rossumbe19ed72007-02-09 05:37:30 +000037 if not hasattr(obj, attr): print("???", attr)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000038 self.assertTrue(hasattr(obj, attr),
Tim Peters5e5ca562002-07-10 02:37:21 +000039 'expected hasattr(%r, %r)' % (obj, attr))
Fred Drake3a28ca82001-08-13 20:26:19 +000040
41
42 def assertHaskey(self, obj, key, ignore):
Guido van Rossume2b70bc2006-08-18 22:13:04 +000043 ''' succeed iff key in obj or key in ignore. '''
Fred Drake3a28ca82001-08-13 20:26:19 +000044 if key in ignore: return
Guido van Rossume2b70bc2006-08-18 22:13:04 +000045 if key not in obj:
Guido van Rossumbe19ed72007-02-09 05:37:30 +000046 print("***",key, file=sys.stderr)
Benjamin Peterson577473f2010-01-19 00:09:57 +000047 self.assertIn(key, obj)
Fred Drake3a28ca82001-08-13 20:26:19 +000048
Anthony Baxterc2a5a632004-08-02 06:10:11 +000049 def assertEqualsOrIgnored(self, a, b, ignore):
Fred Drake3a28ca82001-08-13 20:26:19 +000050 ''' succeed iff a == b or a in ignore or b in ignore '''
Anthony Baxterc2a5a632004-08-02 06:10:11 +000051 if a not in ignore and b not in ignore:
Ezio Melottib3aedd42010-11-20 19:04:17 +000052 self.assertEqual(a, b)
Fred Drake3a28ca82001-08-13 20:26:19 +000053
54 def checkModule(self, moduleName, module=None, ignore=()):
55 ''' succeed iff pyclbr.readmodule_ex(modulename) corresponds
56 to the actual module object, module. Any identifiers in
57 ignore are ignored. If no module is provided, the appropriate
58 module is loaded with __import__.'''
59
Guido van Rossumd858f702006-04-21 09:17:15 +000060 ignore = set(ignore) | set(['object'])
61
Benjamin Peterson2a691a82008-03-31 01:51:45 +000062 if module is None:
Guido van Rossum0ed7aa12002-12-02 14:54:20 +000063 # Import it.
64 # ('<silly>' is to work around an API silliness in __import__)
65 module = __import__(moduleName, globals(), {}, ['<silly>'])
Fred Drake3a28ca82001-08-13 20:26:19 +000066
67 dict = pyclbr.readmodule_ex(moduleName)
68
Anthony Baxterc2a5a632004-08-02 06:10:11 +000069 def ismethod(oclass, obj, name):
70 classdict = oclass.__dict__
Christian Heimes4a22b5d2007-11-25 09:39:14 +000071 if isinstance(obj, MethodType):
72 # could be a classmethod
73 if (not isinstance(classdict[name], ClassMethodType) or
Christian Heimesff737952007-11-27 10:40:20 +000074 obj.__self__ is not oclass):
Anthony Baxterc2a5a632004-08-02 06:10:11 +000075 return False
Christian Heimes4a22b5d2007-11-25 09:39:14 +000076 elif not isinstance(obj, FunctionType):
77 return False
Anthony Baxterc2a5a632004-08-02 06:10:11 +000078
Guido van Rossum7f6a4392002-12-03 08:16:50 +000079 objname = obj.__name__
80 if objname.startswith("__") and not objname.endswith("__"):
Christian Heimes4a22b5d2007-11-25 09:39:14 +000081 objname = "_%s%s" % (oclass.__name__, objname)
Guido van Rossum7f6a4392002-12-03 08:16:50 +000082 return objname == name
83
Fred Drake3a28ca82001-08-13 20:26:19 +000084 # Make sure the toplevel functions and classes are the same.
85 for name, value in dict.items():
Tim Peters04601062001-08-13 22:25:24 +000086 if name in ignore:
Fred Drake3a28ca82001-08-13 20:26:19 +000087 continue
88 self.assertHasattr(module, name, ignore)
89 py_item = getattr(module, name)
90 if isinstance(value, pyclbr.Function):
Ezio Melottie9615932010-01-24 19:26:24 +000091 self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))
Thomas Wouters89f507f2006-12-13 04:49:30 +000092 if py_item.__module__ != moduleName:
93 continue # skip functions that came from somewhere else
Ezio Melottib3aedd42010-11-20 19:04:17 +000094 self.assertEqual(py_item.__module__, value.module)
Fred Drake3a28ca82001-08-13 20:26:19 +000095 else:
Ezio Melottie9615932010-01-24 19:26:24 +000096 self.assertIsInstance(py_item, type)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000097 if py_item.__module__ != moduleName:
98 continue # skip classes that came from somewhere else
99
Fred Drake3a28ca82001-08-13 20:26:19 +0000100 real_bases = [base.__name__ for base in py_item.__bases__]
Tim Peters04601062001-08-13 22:25:24 +0000101 pyclbr_bases = [ getattr(base, 'name', base)
Fred Drake3a28ca82001-08-13 20:26:19 +0000102 for base in value.super ]
Tim Peters04601062001-08-13 22:25:24 +0000103
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000104 try:
105 self.assertListEq(real_bases, pyclbr_bases, ignore)
106 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000107 print("class=%s" % py_item, file=sys.stderr)
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000108 raise
Fred Drake3a28ca82001-08-13 20:26:19 +0000109
110 actualMethods = []
Tim Peters37a309d2001-09-04 01:20:04 +0000111 for m in py_item.__dict__.keys():
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000112 if ismethod(py_item, getattr(py_item, m), m):
Fred Drake3a28ca82001-08-13 20:26:19 +0000113 actualMethods.append(m)
114 foundMethods = []
115 for m in value.methods.keys():
116 if m[:2] == '__' and m[-2:] != '__':
117 foundMethods.append('_'+name+m)
118 else:
119 foundMethods.append(m)
120
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000121 try:
122 self.assertListEq(foundMethods, actualMethods, ignore)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000123 self.assertEqual(py_item.__module__, value.module)
Fred Drake3a28ca82001-08-13 20:26:19 +0000124
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000125 self.assertEqualsOrIgnored(py_item.__name__, value.name,
126 ignore)
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000127 # can't check file or lineno
128 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000129 print("class=%s" % py_item, file=sys.stderr)
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000130 raise
Fred Drake3a28ca82001-08-13 20:26:19 +0000131
132 # Now check for missing stuff.
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000133 def defined_in(item, module):
Guido van Rossum13257902007-06-07 23:15:56 +0000134 if isinstance(item, type):
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000135 return item.__module__ == module.__name__
136 if isinstance(item, FunctionType):
Neal Norwitz221085d2007-02-25 20:55:47 +0000137 return item.__globals__ is module.__dict__
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000138 return False
Fred Drake3a28ca82001-08-13 20:26:19 +0000139 for name in dir(module):
140 item = getattr(module, name)
Guido van Rossum13257902007-06-07 23:15:56 +0000141 if isinstance(item, (type, FunctionType)):
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000142 if defined_in(item, module):
143 self.assertHaskey(dict, name, ignore)
Fred Drake3a28ca82001-08-13 20:26:19 +0000144
145 def test_easy(self):
146 self.checkModule('pyclbr')
Serhiy Storchaka3f228112018-09-27 17:42:37 +0300147 # XXX: Metaclasses are not supported
148 # self.checkModule('ast')
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000149 self.checkModule('doctest', ignore=("TestResults", "_SpoofOut",
Andrew Svetlov7c1017b2013-08-29 01:24:39 +0300150 "DocTestCase", '_DocTestSuite'))
Christian Heimes25bb7832008-01-11 16:17:00 +0000151 self.checkModule('difflib', ignore=("Match",))
Fred Drake3a28ca82001-08-13 20:26:19 +0000152
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000153 def test_decorators(self):
154 # XXX: See comment in pyclbr_input.py for a test that would fail
155 # if it were not commented out.
156 #
Christian Heimes4a22b5d2007-11-25 09:39:14 +0000157 self.checkModule('test.pyclbr_input', ignore=['om'])
Tim Peters6db15d72004-08-04 02:36:18 +0000158
csabella246ff3b2017-07-03 21:31:25 -0400159 def test_nested(self):
160 mb = pyclbr
161 # Set arguments for descriptor creation and _creat_tree call.
162 m, p, f, t, i = 'test', '', 'test.py', {}, None
163 source = dedent("""\
164 def f0:
165 def f1(a,b,c):
166 def f2(a=1, b=2, c=3): pass
167 return f1(a,b,d)
168 class c1: pass
169 class C0:
170 "Test class."
171 def F1():
172 "Method."
173 return 'return'
174 class C1():
175 class C2:
176 "Class nested within nested class."
177 def F3(): return 1+1
178
179 """)
180 actual = mb._create_tree(m, p, f, source, t, i)
181
182 # Create descriptors, linked together, and expected dict.
183 f0 = mb.Function(m, 'f0', f, 1)
184 f1 = mb._nest_function(f0, 'f1', 2)
185 f2 = mb._nest_function(f1, 'f2', 3)
186 c1 = mb._nest_class(f0, 'c1', 5)
187 C0 = mb.Class(m, 'C0', None, f, 6)
188 F1 = mb._nest_function(C0, 'F1', 8)
189 C1 = mb._nest_class(C0, 'C1', 11)
190 C2 = mb._nest_class(C1, 'C2', 12)
191 F3 = mb._nest_function(C2, 'F3', 14)
192 expected = {'f0':f0, 'C0':C0}
193
194 def compare(parent1, children1, parent2, children2):
195 """Return equality of tree pairs.
196
197 Each parent,children pair define a tree. The parents are
198 assumed equal. Comparing the children dictionaries as such
199 does not work due to comparison by identity and double
200 linkage. We separate comparing string and number attributes
201 from comparing the children of input children.
202 """
203 self.assertEqual(children1.keys(), children2.keys())
204 for ob in children1.values():
205 self.assertIs(ob.parent, parent1)
206 for ob in children2.values():
207 self.assertIs(ob.parent, parent2)
208 for key in children1.keys():
209 o1, o2 = children1[key], children2[key]
210 t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno
211 t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno
212 self.assertEqual(t1, t2)
213 if type(o1) is mb.Class:
214 self.assertEqual(o1.methods, o2.methods)
215 # Skip superclasses for now as not part of example
216 compare(o1, o1.children, o2, o2.children)
217
218 compare(None, actual, None, expected)
219
Fred Drake3a28ca82001-08-13 20:26:19 +0000220 def test_others(self):
221 cm = self.checkModule
222
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000223 # These were once about the 10 longest modules
Raymond Hettingere401b6f2002-12-30 07:21:32 +0000224 cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000225 cm('cgi', ignore=('log',)) # set with = in module
Victor Stinnerd65e4f42015-10-12 14:38:24 +0200226 cm('pickle', ignore=('partial',))
Brian Curtin9f914a02017-11-10 11:38:25 -0500227 # TODO(briancurtin): openfp is deprecated as of 3.7.
228 # Update this once it has been removed.
R David Murray4d35e752013-07-25 16:12:01 -0400229 cm('aifc', ignore=('openfp', '_aifc_params')) # set with = in module
Serhiy Storchakabd48d272016-09-11 12:50:02 +0300230 cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000231 cm('pdb')
Serhiy Storchakabdf6b912017-03-19 08:40:32 +0200232 cm('pydoc', ignore=('input', 'output',)) # properties
Fred Drake3a28ca82001-08-13 20:26:19 +0000233
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000234 # Tests for modules inside packages
Neal Norwitz315d8452007-08-30 03:06:59 +0000235 cm('email.parser')
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000236 cm('test.test_pyclbr')
Fred Drake3a28ca82001-08-13 20:26:19 +0000237
Petri Lehtinen8d886042012-05-18 21:51:11 +0300238 def test_issue_14798(self):
239 # test ImportError is raised when the first part of a dotted name is
240 # not a package
241 self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncore.foo')
242
Fred Drake2e2be372001-09-20 21:33:42 +0000243
Fred Drake2e2be372001-09-20 21:33:42 +0000244if __name__ == "__main__":
Zachary Wareac28b792015-12-04 23:32:23 -0600245 unittest_main()