1 ''' 2 Test cases for pyclbr.py 3 Nick Mathewson 4 ''' 5 6 import os 7 import sys 8 from textwrap import dedent 9 from types import FunctionType, MethodType, BuiltinFunctionType 10 import pyclbr 11 from unittest import TestCase, main as unittest_main 12 from test import support 13 from functools import partial 14 15 StaticMethodType = type(staticmethod(lambda: None)) 16 ClassMethodType = type(classmethod(lambda c: None)) 17 18 # Here we test the python class browser code. 19 # 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 25 class PyclbrTest(TestCase): 26 27 def assertListEq(self, l1, l2, ignore): 28 ''' succeed iff {l1} - {ignore} == {l2} - {ignore} ''' 29 missing = (set(l1) ^ set(l2)) - set(ignore) 30 if missing: 31 print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr) 32 self.fail("%r missing" % missing.pop()) 33 34 def assertHasattr(self, obj, attr, ignore): 35 ''' succeed iff hasattr(obj,attr) or attr in ignore. ''' 36 if attr in ignore: return 37 if not hasattr(obj, attr): print("???", attr) 38 self.assertTrue(hasattr(obj, attr), 39 'expected hasattr(%r, %r)' % (obj, attr)) 40 41 42 def assertHaskey(self, obj, key, ignore): 43 ''' succeed iff key in obj or key in ignore. ''' 44 if key in ignore: return 45 if key not in obj: 46 print("***",key, file=sys.stderr) 47 self.assertIn(key, obj) 48 49 def assertEqualsOrIgnored(self, a, b, ignore): 50 ''' succeed iff a == b or a in ignore or b in ignore ''' 51 if a not in ignore and b not in ignore: 52 self.assertEqual(a, b) 53 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 60 ignore = set(ignore) | set(['object']) 61 62 if module is None: 63 # Import it. 64 # ('<silly>' is to work around an API silliness in __import__) 65 module = __import__(moduleName, globals(), {}, ['<silly>']) 66 67 dict = pyclbr.readmodule_ex(moduleName) 68 69 def ismethod(oclass, obj, name): 70 classdict = oclass.__dict__ 71 if isinstance(obj, MethodType): 72 # could be a classmethod 73 if (not isinstance(classdict[name], ClassMethodType) or 74 obj.__self__ is not oclass): 75 return False 76 elif not isinstance(obj, FunctionType): 77 return False 78 79 objname = obj.__name__ 80 if objname.startswith("__") and not objname.endswith("__"): 81 objname = "_%s%s" % (oclass.__name__, objname) 82 return objname == name 83 84 # Make sure the toplevel functions and classes are the same. 85 for name, value in dict.items(): 86 if name in ignore: 87 continue 88 self.assertHasattr(module, name, ignore) 89 py_item = getattr(module, name) 90 if isinstance(value, pyclbr.Function): 91 self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType)) 92 if py_item.__module__ != moduleName: 93 continue # skip functions that came from somewhere else 94 self.assertEqual(py_item.__module__, value.module) 95 else: 96 self.assertIsInstance(py_item, type) 97 if py_item.__module__ != moduleName: 98 continue # skip classes that came from somewhere else 99 100 real_bases = [base.__name__ for base in py_item.__bases__] 101 pyclbr_bases = [ getattr(base, 'name', base) 102 for base in value.super ] 103 104 try: 105 self.assertListEq(real_bases, pyclbr_bases, ignore) 106 except: 107 print("class=%s" % py_item, file=sys.stderr) 108 raise 109 110 actualMethods = [] 111 for m in py_item.__dict__.keys(): 112 if ismethod(py_item, getattr(py_item, m), m): 113 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 121 try: 122 self.assertListEq(foundMethods, actualMethods, ignore) 123 self.assertEqual(py_item.__module__, value.module) 124 125 self.assertEqualsOrIgnored(py_item.__name__, value.name, 126 ignore) 127 # can't check file or lineno 128 except: 129 print("class=%s" % py_item, file=sys.stderr) 130 raise 131 132 # Now check for missing stuff. 133 def defined_in(item, module): 134 if isinstance(item, type): 135 return item.__module__ == module.__name__ 136 if isinstance(item, FunctionType): 137 return item.__globals__ is module.__dict__ 138 return False 139 for name in dir(module): 140 item = getattr(module, name) 141 if isinstance(item, (type, FunctionType)): 142 if defined_in(item, module): 143 self.assertHaskey(dict, name, ignore) 144 145 def test_easy(self): 146 self.checkModule('pyclbr') 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 # XXX: See comment in pyclbr_input.py for a test that would fail 154 # if it were not commented out. 155 # 156 self.checkModule('test.pyclbr_input', ignore=['om']) 157 158 def test_nested(self): 159 mb = pyclbr 160 # Set arguments for descriptor creation and _creat_tree call. 161 m, p, f, t, i = 'test', '', 'test.py', {}, None 162 source = dedent("""\ 163 def f0: 164 def f1(a,b,c): 165 def f2(a=1, b=2, c=3): pass 166 return f1(a,b,d) 167 class c1: pass 168 class C0: 169 "Test class." 170 def F1(): 171 "Method." 172 return 'return' 173 class C1(): 174 class C2: 175 "Class nested within nested class." 176 def F3(): return 1+1 177 178 """) 179 actual = mb._create_tree(m, p, f, source, t, i) 180 181 # Create descriptors, linked together, and expected dict. 182 f0 = mb.Function(m, 'f0', f, 1) 183 f1 = mb._nest_function(f0, 'f1', 2) 184 f2 = mb._nest_function(f1, 'f2', 3) 185 c1 = mb._nest_class(f0, 'c1', 5) 186 C0 = mb.Class(m, 'C0', None, f, 6) 187 F1 = mb._nest_function(C0, 'F1', 8) 188 C1 = mb._nest_class(C0, 'C1', 11) 189 C2 = mb._nest_class(C1, 'C2', 12) 190 F3 = mb._nest_function(C2, 'F3', 14) 191 expected = {'f0':f0, 'C0':C0} 192 193 def compare(parent1, children1, parent2, children2): 194 """Return equality of tree pairs. 195 196 Each parent,children pair define a tree. The parents are 197 assumed equal. Comparing the children dictionaries as such 198 does not work due to comparison by identity and double 199 linkage. We separate comparing string and number attributes 200 from comparing the children of input children. 201 """ 202 self.assertEqual(children1.keys(), children2.keys()) 203 for ob in children1.values(): 204 self.assertIs(ob.parent, parent1) 205 for ob in children2.values(): 206 self.assertIs(ob.parent, parent2) 207 for key in children1.keys(): 208 o1, o2 = children1[key], children2[key] 209 t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno 210 t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno 211 self.assertEqual(t1, t2) 212 if type(o1) is mb.Class: 213 self.assertEqual(o1.methods, o2.methods) 214 # Skip superclasses for now as not part of example 215 compare(o1, o1.children, o2, o2.children) 216 217 compare(None, actual, None, expected) 218 219 def test_others(self): 220 cm = self.checkModule 221 222 # These were once about the 10 longest modules 223 cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator 224 cm('cgi', ignore=('log',)) # set with = in module 225 cm('pickle', ignore=('partial',)) 226 # TODO(briancurtin): openfp is deprecated as of 3.7. 227 # Update this once it has been removed. 228 cm('aifc', ignore=('openfp', '_aifc_params')) # set with = in module 229 cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property 230 cm('pdb') 231 cm('pydoc', ignore=('input', 'output',)) # properties 232 233 # Tests for modules inside packages 234 cm('email.parser') 235 cm('test.test_pyclbr') 236 237 def test_issue_14798(self): 238 # test ImportError is raised when the first part of a dotted name is 239 # not a package 240 self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncore.foo') 241 242 243 if __name__ == "__main__": 244 unittest_main() 245