blob: 82c1ebb5b070fa9bf0f28cb777058cfd9663f5a3 [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
Christian Heimes05e8be12008-02-23 18:30:17 +00006import sys
csabella246ff3b2017-07-03 21:31:25 -04007from textwrap import dedent
Guido van Rossum13257902007-06-07 23:15:56 +00008from types import FunctionType, MethodType, BuiltinFunctionType
Fred Drake3a28ca82001-08-13 20:26:19 +00009import pyclbr
Zachary Wareac28b792015-12-04 23:32:23 -060010from unittest import TestCase, main as unittest_main
Brett Cannon50865892019-03-22 15:16:50 -070011from test.test_importlib import util as test_importlib_util
Victor Stinner8f4ef3b2019-07-01 18:28:25 +020012
Fred Drake3a28ca82001-08-13 20:26:19 +000013
Anthony Baxterc2a5a632004-08-02 06:10:11 +000014StaticMethodType = type(staticmethod(lambda: None))
15ClassMethodType = type(classmethod(lambda c: None))
16
Tim Peters04601062001-08-13 22:25:24 +000017# Here we test the python class browser code.
Fred Drake3a28ca82001-08-13 20:26:19 +000018#
19# The main function in this suite, 'testModule', compares the output
20# of pyclbr with the introspected members of a module. Because pyclbr
21# is imperfect (as designed), testModule is called with a set of
22# members to ignore.
23
Guido van Rossum0ed7aa12002-12-02 14:54:20 +000024class PyclbrTest(TestCase):
Fred Drake3a28ca82001-08-13 20:26:19 +000025
26 def assertListEq(self, l1, l2, ignore):
27 ''' succeed iff {l1} - {ignore} == {l2} - {ignore} '''
Raymond Hettingera690a992003-11-16 16:17:49 +000028 missing = (set(l1) ^ set(l2)) - set(ignore)
Raymond Hettinger91bbd9a2003-05-02 09:06:28 +000029 if missing:
Guido van Rossumbe19ed72007-02-09 05:37:30 +000030 print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr)
Raymond Hettinger91bbd9a2003-05-02 09:06:28 +000031 self.fail("%r missing" % missing.pop())
Tim Peters04601062001-08-13 22:25:24 +000032
Fred Drake3a28ca82001-08-13 20:26:19 +000033 def assertHasattr(self, obj, attr, ignore):
34 ''' succeed iff hasattr(obj,attr) or attr in ignore. '''
35 if attr in ignore: return
Guido van Rossumbe19ed72007-02-09 05:37:30 +000036 if not hasattr(obj, attr): print("???", attr)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000037 self.assertTrue(hasattr(obj, attr),
Tim Peters5e5ca562002-07-10 02:37:21 +000038 'expected hasattr(%r, %r)' % (obj, attr))
Fred Drake3a28ca82001-08-13 20:26:19 +000039
40
41 def assertHaskey(self, obj, key, ignore):
Guido van Rossume2b70bc2006-08-18 22:13:04 +000042 ''' succeed iff key in obj or key in ignore. '''
Fred Drake3a28ca82001-08-13 20:26:19 +000043 if key in ignore: return
Guido van Rossume2b70bc2006-08-18 22:13:04 +000044 if key not in obj:
Guido van Rossumbe19ed72007-02-09 05:37:30 +000045 print("***",key, file=sys.stderr)
Benjamin Peterson577473f2010-01-19 00:09:57 +000046 self.assertIn(key, obj)
Fred Drake3a28ca82001-08-13 20:26:19 +000047
Anthony Baxterc2a5a632004-08-02 06:10:11 +000048 def assertEqualsOrIgnored(self, a, b, ignore):
Fred Drake3a28ca82001-08-13 20:26:19 +000049 ''' succeed iff a == b or a in ignore or b in ignore '''
Anthony Baxterc2a5a632004-08-02 06:10:11 +000050 if a not in ignore and b not in ignore:
Ezio Melottib3aedd42010-11-20 19:04:17 +000051 self.assertEqual(a, b)
Fred Drake3a28ca82001-08-13 20:26:19 +000052
53 def checkModule(self, moduleName, module=None, ignore=()):
54 ''' succeed iff pyclbr.readmodule_ex(modulename) corresponds
55 to the actual module object, module. Any identifiers in
56 ignore are ignored. If no module is provided, the appropriate
57 module is loaded with __import__.'''
58
Guido van Rossumd858f702006-04-21 09:17:15 +000059 ignore = set(ignore) | set(['object'])
60
Benjamin Peterson2a691a82008-03-31 01:51:45 +000061 if module is None:
Guido van Rossum0ed7aa12002-12-02 14:54:20 +000062 # Import it.
63 # ('<silly>' is to work around an API silliness in __import__)
64 module = __import__(moduleName, globals(), {}, ['<silly>'])
Fred Drake3a28ca82001-08-13 20:26:19 +000065
66 dict = pyclbr.readmodule_ex(moduleName)
67
Anthony Baxterc2a5a632004-08-02 06:10:11 +000068 def ismethod(oclass, obj, name):
69 classdict = oclass.__dict__
Christian Heimes4a22b5d2007-11-25 09:39:14 +000070 if isinstance(obj, MethodType):
71 # could be a classmethod
72 if (not isinstance(classdict[name], ClassMethodType) or
Christian Heimesff737952007-11-27 10:40:20 +000073 obj.__self__ is not oclass):
Anthony Baxterc2a5a632004-08-02 06:10:11 +000074 return False
Christian Heimes4a22b5d2007-11-25 09:39:14 +000075 elif not isinstance(obj, FunctionType):
76 return False
Anthony Baxterc2a5a632004-08-02 06:10:11 +000077
Guido van Rossum7f6a4392002-12-03 08:16:50 +000078 objname = obj.__name__
79 if objname.startswith("__") and not objname.endswith("__"):
Christian Heimes4a22b5d2007-11-25 09:39:14 +000080 objname = "_%s%s" % (oclass.__name__, objname)
Guido van Rossum7f6a4392002-12-03 08:16:50 +000081 return objname == name
82
Fred Drake3a28ca82001-08-13 20:26:19 +000083 # Make sure the toplevel functions and classes are the same.
84 for name, value in dict.items():
Tim Peters04601062001-08-13 22:25:24 +000085 if name in ignore:
Fred Drake3a28ca82001-08-13 20:26:19 +000086 continue
87 self.assertHasattr(module, name, ignore)
88 py_item = getattr(module, name)
89 if isinstance(value, pyclbr.Function):
Ezio Melottie9615932010-01-24 19:26:24 +000090 self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))
Thomas Wouters89f507f2006-12-13 04:49:30 +000091 if py_item.__module__ != moduleName:
92 continue # skip functions that came from somewhere else
Ezio Melottib3aedd42010-11-20 19:04:17 +000093 self.assertEqual(py_item.__module__, value.module)
Fred Drake3a28ca82001-08-13 20:26:19 +000094 else:
Ezio Melottie9615932010-01-24 19:26:24 +000095 self.assertIsInstance(py_item, type)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000096 if py_item.__module__ != moduleName:
97 continue # skip classes that came from somewhere else
98
Fred Drake3a28ca82001-08-13 20:26:19 +000099 real_bases = [base.__name__ for base in py_item.__bases__]
Tim Peters04601062001-08-13 22:25:24 +0000100 pyclbr_bases = [ getattr(base, 'name', base)
Fred Drake3a28ca82001-08-13 20:26:19 +0000101 for base in value.super ]
Tim Peters04601062001-08-13 22:25:24 +0000102
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000103 try:
104 self.assertListEq(real_bases, pyclbr_bases, ignore)
105 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000106 print("class=%s" % py_item, file=sys.stderr)
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000107 raise
Fred Drake3a28ca82001-08-13 20:26:19 +0000108
109 actualMethods = []
Tim Peters37a309d2001-09-04 01:20:04 +0000110 for m in py_item.__dict__.keys():
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000111 if ismethod(py_item, getattr(py_item, m), m):
Fred Drake3a28ca82001-08-13 20:26:19 +0000112 actualMethods.append(m)
113 foundMethods = []
114 for m in value.methods.keys():
115 if m[:2] == '__' and m[-2:] != '__':
116 foundMethods.append('_'+name+m)
117 else:
118 foundMethods.append(m)
119
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000120 try:
121 self.assertListEq(foundMethods, actualMethods, ignore)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000122 self.assertEqual(py_item.__module__, value.module)
Fred Drake3a28ca82001-08-13 20:26:19 +0000123
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000124 self.assertEqualsOrIgnored(py_item.__name__, value.name,
125 ignore)
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000126 # can't check file or lineno
127 except:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000128 print("class=%s" % py_item, file=sys.stderr)
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000129 raise
Fred Drake3a28ca82001-08-13 20:26:19 +0000130
131 # Now check for missing stuff.
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000132 def defined_in(item, module):
Guido van Rossum13257902007-06-07 23:15:56 +0000133 if isinstance(item, type):
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000134 return item.__module__ == module.__name__
135 if isinstance(item, FunctionType):
Neal Norwitz221085d2007-02-25 20:55:47 +0000136 return item.__globals__ is module.__dict__
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000137 return False
Fred Drake3a28ca82001-08-13 20:26:19 +0000138 for name in dir(module):
139 item = getattr(module, name)
Guido van Rossum13257902007-06-07 23:15:56 +0000140 if isinstance(item, (type, FunctionType)):
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000141 if defined_in(item, module):
142 self.assertHaskey(dict, name, ignore)
Fred Drake3a28ca82001-08-13 20:26:19 +0000143
144 def test_easy(self):
145 self.checkModule('pyclbr')
Serhiy Storchaka3f228112018-09-27 17:42:37 +0300146 # XXX: Metaclasses are not supported
147 # self.checkModule('ast')
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000148 self.checkModule('doctest', ignore=("TestResults", "_SpoofOut",
Andrew Svetlov7c1017b2013-08-29 01:24:39 +0300149 "DocTestCase", '_DocTestSuite'))
Christian Heimes25bb7832008-01-11 16:17:00 +0000150 self.checkModule('difflib', ignore=("Match",))
Fred Drake3a28ca82001-08-13 20:26:19 +0000151
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000152 def test_decorators(self):
Christian Heimes4a22b5d2007-11-25 09:39:14 +0000153 self.checkModule('test.pyclbr_input', ignore=['om'])
Tim Peters6db15d72004-08-04 02:36:18 +0000154
csabella246ff3b2017-07-03 21:31:25 -0400155 def test_nested(self):
156 mb = pyclbr
157 # Set arguments for descriptor creation and _creat_tree call.
158 m, p, f, t, i = 'test', '', 'test.py', {}, None
159 source = dedent("""\
Batuhan Taskayafa476fe2020-11-11 10:14:12 +0300160 def f0():
csabella246ff3b2017-07-03 21:31:25 -0400161 def f1(a,b,c):
162 def f2(a=1, b=2, c=3): pass
Batuhan Taskayafa476fe2020-11-11 10:14:12 +0300163 return f1(a,b,d)
csabella246ff3b2017-07-03 21:31:25 -0400164 class c1: pass
165 class C0:
166 "Test class."
167 def F1():
168 "Method."
169 return 'return'
170 class C1():
171 class C2:
172 "Class nested within nested class."
173 def F3(): return 1+1
174
175 """)
176 actual = mb._create_tree(m, p, f, source, t, i)
177
178 # Create descriptors, linked together, and expected dict.
Aviral Srivastava000cde52021-02-01 09:38:44 -0800179 f0 = mb.Function(m, 'f0', f, 1, end_lineno=5)
180 f1 = mb._nest_function(f0, 'f1', 2, 4)
181 f2 = mb._nest_function(f1, 'f2', 3, 3)
182 c1 = mb._nest_class(f0, 'c1', 5, 5)
183 C0 = mb.Class(m, 'C0', None, f, 6, end_lineno=14)
184 F1 = mb._nest_function(C0, 'F1', 8, 10)
185 C1 = mb._nest_class(C0, 'C1', 11, 14)
186 C2 = mb._nest_class(C1, 'C2', 12, 14)
187 F3 = mb._nest_function(C2, 'F3', 14, 14)
csabella246ff3b2017-07-03 21:31:25 -0400188 expected = {'f0':f0, 'C0':C0}
189
190 def compare(parent1, children1, parent2, children2):
191 """Return equality of tree pairs.
192
193 Each parent,children pair define a tree. The parents are
194 assumed equal. Comparing the children dictionaries as such
195 does not work due to comparison by identity and double
196 linkage. We separate comparing string and number attributes
197 from comparing the children of input children.
198 """
199 self.assertEqual(children1.keys(), children2.keys())
200 for ob in children1.values():
201 self.assertIs(ob.parent, parent1)
202 for ob in children2.values():
203 self.assertIs(ob.parent, parent2)
204 for key in children1.keys():
205 o1, o2 = children1[key], children2[key]
Aviral Srivastava000cde52021-02-01 09:38:44 -0800206 t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno, o1.end_lineno
207 t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno, o2.end_lineno
csabella246ff3b2017-07-03 21:31:25 -0400208 self.assertEqual(t1, t2)
209 if type(o1) is mb.Class:
210 self.assertEqual(o1.methods, o2.methods)
211 # Skip superclasses for now as not part of example
212 compare(o1, o1.children, o2, o2.children)
213
214 compare(None, actual, None, expected)
215
Fred Drake3a28ca82001-08-13 20:26:19 +0000216 def test_others(self):
217 cm = self.checkModule
218
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000219 # These were once about the 10 longest modules
Raymond Hettingere401b6f2002-12-30 07:21:32 +0000220 cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000221 cm('cgi', ignore=('log',)) # set with = in module
Antoine Pitrou91f43802019-05-26 17:10:09 +0200222 cm('pickle', ignore=('partial', 'PickleBuffer'))
Victor Stinnerac7b1a32019-06-18 00:00:24 +0200223 cm('aifc', ignore=('_aifc_params',)) # set with = in module
Serhiy Storchakabd48d272016-09-11 12:50:02 +0300224 cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000225 cm('pdb')
Serhiy Storchakabdf6b912017-03-19 08:40:32 +0200226 cm('pydoc', ignore=('input', 'output',)) # properties
Fred Drake3a28ca82001-08-13 20:26:19 +0000227
Guido van Rossum0ed7aa12002-12-02 14:54:20 +0000228 # Tests for modules inside packages
Neal Norwitz315d8452007-08-30 03:06:59 +0000229 cm('email.parser')
Guido van Rossum7f6a4392002-12-03 08:16:50 +0000230 cm('test.test_pyclbr')
Fred Drake3a28ca82001-08-13 20:26:19 +0000231
Brett Cannon50865892019-03-22 15:16:50 -0700232
233class ReadmoduleTests(TestCase):
234
235 def setUp(self):
236 self._modules = pyclbr._modules.copy()
237
238 def tearDown(self):
239 pyclbr._modules = self._modules
240
241
242 def test_dotted_name_not_a_package(self):
Petri Lehtinen8d886042012-05-18 21:51:11 +0300243 # test ImportError is raised when the first part of a dotted name is
Brett Cannon50865892019-03-22 15:16:50 -0700244 # not a package.
245 #
246 # Issue #14798.
jacksonriley138e7bb2019-11-22 12:51:58 +0000247 self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncio.foo')
Petri Lehtinen8d886042012-05-18 21:51:11 +0300248
Brett Cannon50865892019-03-22 15:16:50 -0700249 def test_module_has_no_spec(self):
250 module_name = "doesnotexist"
251 assert module_name not in pyclbr._modules
252 with test_importlib_util.uncache(module_name):
253 with self.assertRaises(ModuleNotFoundError):
254 pyclbr.readmodule_ex(module_name)
255
Fred Drake2e2be372001-09-20 21:33:42 +0000256
Fred Drake2e2be372001-09-20 21:33:42 +0000257if __name__ == "__main__":
Zachary Wareac28b792015-12-04 23:32:23 -0600258 unittest_main()