1''' 2 Test cases for pyclbr.py 3 Nick Mathewson 4''' 5 6import sys 7from textwrap import dedent 8from types import FunctionType, MethodType, BuiltinFunctionType 9import pyclbr 10from unittest import TestCase, main as unittest_main 11from test.test_importlib import util as test_importlib_util 12 13 14StaticMethodType = type(staticmethod(lambda: None)) 15ClassMethodType = type(classmethod(lambda c: None)) 16 17# Here we test the python class browser code. 18# 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 24class PyclbrTest(TestCase): 25 26 def assertListEq(self, l1, l2, ignore): 27 ''' succeed iff {l1} - {ignore} == {l2} - {ignore} ''' 28 missing = (set(l1) ^ set(l2)) - set(ignore) 29 if missing: 30 print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr) 31 self.fail("%r missing" % missing.pop()) 32 33 def assertHasattr(self, obj, attr, ignore): 34 ''' succeed iff hasattr(obj,attr) or attr in ignore. ''' 35 if attr in ignore: return 36 if not hasattr(obj, attr): print("???", attr) 37 self.assertTrue(hasattr(obj, attr), 38 'expected hasattr(%r, %r)' % (obj, attr)) 39 40 41 def assertHaskey(self, obj, key, ignore): 42 ''' succeed iff key in obj or key in ignore. ''' 43 if key in ignore: return 44 if key not in obj: 45 print("***",key, file=sys.stderr) 46 self.assertIn(key, obj) 47 48 def assertEqualsOrIgnored(self, a, b, ignore): 49 ''' succeed iff a == b or a in ignore or b in ignore ''' 50 if a not in ignore and b not in ignore: 51 self.assertEqual(a, b) 52 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 59 ignore = set(ignore) | set(['object']) 60 61 if module is None: 62 # Import it. 63 # ('<silly>' is to work around an API silliness in __import__) 64 module = __import__(moduleName, globals(), {}, ['<silly>']) 65 66 dict = pyclbr.readmodule_ex(moduleName) 67 68 def ismethod(oclass, obj, name): 69 classdict = oclass.__dict__ 70 if isinstance(obj, MethodType): 71 # could be a classmethod 72 if (not isinstance(classdict[name], ClassMethodType) or 73 obj.__self__ is not oclass): 74 return False 75 elif not isinstance(obj, FunctionType): 76 return False 77 78 objname = obj.__name__ 79 if objname.startswith("__") and not objname.endswith("__"): 80 objname = "_%s%s" % (oclass.__name__, objname) 81 return objname == name 82 83 # Make sure the toplevel functions and classes are the same. 84 for name, value in dict.items(): 85 if name in ignore: 86 continue 87 self.assertHasattr(module, name, ignore) 88 py_item = getattr(module, name) 89 if isinstance(value, pyclbr.Function): 90 self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType)) 91 if py_item.__module__ != moduleName: 92 continue # skip functions that came from somewhere else 93 self.assertEqual(py_item.__module__, value.module) 94 else: 95 self.assertIsInstance(py_item, type) 96 if py_item.__module__ != moduleName: 97 continue # skip classes that came from somewhere else 98 99 real_bases = [base.__name__ for base in py_item.__bases__] 100 pyclbr_bases = [ getattr(base, 'name', base) 101 for base in value.super ] 102 103 try: 104 self.assertListEq(real_bases, pyclbr_bases, ignore) 105 except: 106 print("class=%s" % py_item, file=sys.stderr) 107 raise 108 109 actualMethods = [] 110 for m in py_item.__dict__.keys(): 111 if ismethod(py_item, getattr(py_item, m), m): 112 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 120 try: 121 self.assertListEq(foundMethods, actualMethods, ignore) 122 self.assertEqual(py_item.__module__, value.module) 123 124 self.assertEqualsOrIgnored(py_item.__name__, value.name, 125 ignore) 126 # can't check file or lineno 127 except: 128 print("class=%s" % py_item, file=sys.stderr) 129 raise 130 131 # Now check for missing stuff. 132 def defined_in(item, module): 133 if isinstance(item, type): 134 return item.__module__ == module.__name__ 135 if isinstance(item, FunctionType): 136 return item.__globals__ is module.__dict__ 137 return False 138 for name in dir(module): 139 item = getattr(module, name) 140 if isinstance(item, (type, FunctionType)): 141 if defined_in(item, module): 142 self.assertHaskey(dict, name, ignore) 143 144 def test_easy(self): 145 self.checkModule('pyclbr') 146 # XXX: Metaclasses are not supported 147 # self.checkModule('ast') 148 self.checkModule('doctest', ignore=("TestResults", "_SpoofOut", 149 "DocTestCase", '_DocTestSuite')) 150 self.checkModule('difflib', ignore=("Match",)) 151 152 def test_decorators(self): 153 self.checkModule('test.pyclbr_input', ignore=['om']) 154 155 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("""\ 160 def f0(): 161 def f1(a,b,c): 162 def f2(a=1, b=2, c=3): pass 163 return f1(a,b,d) 164 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. 179 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) 188 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] 206 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 208 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 216 def test_others(self): 217 cm = self.checkModule 218 219 # These were once about the 10 longest modules 220 cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator 221 cm('cgi', ignore=('log',)) # set with = in module 222 cm('pickle', ignore=('partial', 'PickleBuffer')) 223 cm('aifc', ignore=('_aifc_params',)) # set with = in module 224 cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property 225 cm('pdb') 226 cm('pydoc', ignore=('input', 'output',)) # properties 227 228 # Tests for modules inside packages 229 cm('email.parser') 230 cm('test.test_pyclbr') 231 232 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): 243 # test ImportError is raised when the first part of a dotted name is 244 # not a package. 245 # 246 # Issue #14798. 247 self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncio.foo') 248 249 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 256 257if __name__ == "__main__": 258 unittest_main() 259