17db96d56Sopenharmony_ci''' 27db96d56Sopenharmony_ci Test cases for pyclbr.py 37db96d56Sopenharmony_ci Nick Mathewson 47db96d56Sopenharmony_ci''' 57db96d56Sopenharmony_ci 67db96d56Sopenharmony_ciimport sys 77db96d56Sopenharmony_cifrom textwrap import dedent 87db96d56Sopenharmony_cifrom types import FunctionType, MethodType, BuiltinFunctionType 97db96d56Sopenharmony_ciimport pyclbr 107db96d56Sopenharmony_cifrom unittest import TestCase, main as unittest_main 117db96d56Sopenharmony_cifrom test.test_importlib import util as test_importlib_util 127db96d56Sopenharmony_ciimport warnings 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ci 157db96d56Sopenharmony_ciStaticMethodType = type(staticmethod(lambda: None)) 167db96d56Sopenharmony_ciClassMethodType = type(classmethod(lambda c: None)) 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ci# Here we test the python class browser code. 197db96d56Sopenharmony_ci# 207db96d56Sopenharmony_ci# The main function in this suite, 'testModule', compares the output 217db96d56Sopenharmony_ci# of pyclbr with the introspected members of a module. Because pyclbr 227db96d56Sopenharmony_ci# is imperfect (as designed), testModule is called with a set of 237db96d56Sopenharmony_ci# members to ignore. 247db96d56Sopenharmony_ci 257db96d56Sopenharmony_ciclass PyclbrTest(TestCase): 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci def assertListEq(self, l1, l2, ignore): 287db96d56Sopenharmony_ci ''' succeed iff {l1} - {ignore} == {l2} - {ignore} ''' 297db96d56Sopenharmony_ci missing = (set(l1) ^ set(l2)) - set(ignore) 307db96d56Sopenharmony_ci if missing: 317db96d56Sopenharmony_ci print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr) 327db96d56Sopenharmony_ci self.fail("%r missing" % missing.pop()) 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci def assertHasattr(self, obj, attr, ignore): 357db96d56Sopenharmony_ci ''' succeed iff hasattr(obj,attr) or attr in ignore. ''' 367db96d56Sopenharmony_ci if attr in ignore: return 377db96d56Sopenharmony_ci if not hasattr(obj, attr): print("???", attr) 387db96d56Sopenharmony_ci self.assertTrue(hasattr(obj, attr), 397db96d56Sopenharmony_ci 'expected hasattr(%r, %r)' % (obj, attr)) 407db96d56Sopenharmony_ci 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci def assertHaskey(self, obj, key, ignore): 437db96d56Sopenharmony_ci ''' succeed iff key in obj or key in ignore. ''' 447db96d56Sopenharmony_ci if key in ignore: return 457db96d56Sopenharmony_ci if key not in obj: 467db96d56Sopenharmony_ci print("***",key, file=sys.stderr) 477db96d56Sopenharmony_ci self.assertIn(key, obj) 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_ci def assertEqualsOrIgnored(self, a, b, ignore): 507db96d56Sopenharmony_ci ''' succeed iff a == b or a in ignore or b in ignore ''' 517db96d56Sopenharmony_ci if a not in ignore and b not in ignore: 527db96d56Sopenharmony_ci self.assertEqual(a, b) 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci def checkModule(self, moduleName, module=None, ignore=()): 557db96d56Sopenharmony_ci ''' succeed iff pyclbr.readmodule_ex(modulename) corresponds 567db96d56Sopenharmony_ci to the actual module object, module. Any identifiers in 577db96d56Sopenharmony_ci ignore are ignored. If no module is provided, the appropriate 587db96d56Sopenharmony_ci module is loaded with __import__.''' 597db96d56Sopenharmony_ci 607db96d56Sopenharmony_ci ignore = set(ignore) | set(['object']) 617db96d56Sopenharmony_ci 627db96d56Sopenharmony_ci if module is None: 637db96d56Sopenharmony_ci # Import it. 647db96d56Sopenharmony_ci # ('<silly>' is to work around an API silliness in __import__) 657db96d56Sopenharmony_ci module = __import__(moduleName, globals(), {}, ['<silly>']) 667db96d56Sopenharmony_ci 677db96d56Sopenharmony_ci dict = pyclbr.readmodule_ex(moduleName) 687db96d56Sopenharmony_ci 697db96d56Sopenharmony_ci def ismethod(oclass, obj, name): 707db96d56Sopenharmony_ci classdict = oclass.__dict__ 717db96d56Sopenharmony_ci if isinstance(obj, MethodType): 727db96d56Sopenharmony_ci # could be a classmethod 737db96d56Sopenharmony_ci if (not isinstance(classdict[name], ClassMethodType) or 747db96d56Sopenharmony_ci obj.__self__ is not oclass): 757db96d56Sopenharmony_ci return False 767db96d56Sopenharmony_ci elif not isinstance(obj, FunctionType): 777db96d56Sopenharmony_ci return False 787db96d56Sopenharmony_ci 797db96d56Sopenharmony_ci objname = obj.__name__ 807db96d56Sopenharmony_ci if objname.startswith("__") and not objname.endswith("__"): 817db96d56Sopenharmony_ci objname = "_%s%s" % (oclass.__name__, objname) 827db96d56Sopenharmony_ci return objname == name 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ci # Make sure the toplevel functions and classes are the same. 857db96d56Sopenharmony_ci for name, value in dict.items(): 867db96d56Sopenharmony_ci if name in ignore: 877db96d56Sopenharmony_ci continue 887db96d56Sopenharmony_ci self.assertHasattr(module, name, ignore) 897db96d56Sopenharmony_ci py_item = getattr(module, name) 907db96d56Sopenharmony_ci if isinstance(value, pyclbr.Function): 917db96d56Sopenharmony_ci self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType)) 927db96d56Sopenharmony_ci if py_item.__module__ != moduleName: 937db96d56Sopenharmony_ci continue # skip functions that came from somewhere else 947db96d56Sopenharmony_ci self.assertEqual(py_item.__module__, value.module) 957db96d56Sopenharmony_ci else: 967db96d56Sopenharmony_ci self.assertIsInstance(py_item, type) 977db96d56Sopenharmony_ci if py_item.__module__ != moduleName: 987db96d56Sopenharmony_ci continue # skip classes that came from somewhere else 997db96d56Sopenharmony_ci 1007db96d56Sopenharmony_ci real_bases = [base.__name__ for base in py_item.__bases__] 1017db96d56Sopenharmony_ci pyclbr_bases = [ getattr(base, 'name', base) 1027db96d56Sopenharmony_ci for base in value.super ] 1037db96d56Sopenharmony_ci 1047db96d56Sopenharmony_ci try: 1057db96d56Sopenharmony_ci self.assertListEq(real_bases, pyclbr_bases, ignore) 1067db96d56Sopenharmony_ci except: 1077db96d56Sopenharmony_ci print("class=%s" % py_item, file=sys.stderr) 1087db96d56Sopenharmony_ci raise 1097db96d56Sopenharmony_ci 1107db96d56Sopenharmony_ci actualMethods = [] 1117db96d56Sopenharmony_ci for m in py_item.__dict__.keys(): 1127db96d56Sopenharmony_ci if ismethod(py_item, getattr(py_item, m), m): 1137db96d56Sopenharmony_ci actualMethods.append(m) 1147db96d56Sopenharmony_ci foundMethods = [] 1157db96d56Sopenharmony_ci for m in value.methods.keys(): 1167db96d56Sopenharmony_ci if m[:2] == '__' and m[-2:] != '__': 1177db96d56Sopenharmony_ci foundMethods.append('_'+name+m) 1187db96d56Sopenharmony_ci else: 1197db96d56Sopenharmony_ci foundMethods.append(m) 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci try: 1227db96d56Sopenharmony_ci self.assertListEq(foundMethods, actualMethods, ignore) 1237db96d56Sopenharmony_ci self.assertEqual(py_item.__module__, value.module) 1247db96d56Sopenharmony_ci 1257db96d56Sopenharmony_ci self.assertEqualsOrIgnored(py_item.__name__, value.name, 1267db96d56Sopenharmony_ci ignore) 1277db96d56Sopenharmony_ci # can't check file or lineno 1287db96d56Sopenharmony_ci except: 1297db96d56Sopenharmony_ci print("class=%s" % py_item, file=sys.stderr) 1307db96d56Sopenharmony_ci raise 1317db96d56Sopenharmony_ci 1327db96d56Sopenharmony_ci # Now check for missing stuff. 1337db96d56Sopenharmony_ci def defined_in(item, module): 1347db96d56Sopenharmony_ci if isinstance(item, type): 1357db96d56Sopenharmony_ci return item.__module__ == module.__name__ 1367db96d56Sopenharmony_ci if isinstance(item, FunctionType): 1377db96d56Sopenharmony_ci return item.__globals__ is module.__dict__ 1387db96d56Sopenharmony_ci return False 1397db96d56Sopenharmony_ci for name in dir(module): 1407db96d56Sopenharmony_ci item = getattr(module, name) 1417db96d56Sopenharmony_ci if isinstance(item, (type, FunctionType)): 1427db96d56Sopenharmony_ci if defined_in(item, module): 1437db96d56Sopenharmony_ci self.assertHaskey(dict, name, ignore) 1447db96d56Sopenharmony_ci 1457db96d56Sopenharmony_ci def test_easy(self): 1467db96d56Sopenharmony_ci self.checkModule('pyclbr') 1477db96d56Sopenharmony_ci # XXX: Metaclasses are not supported 1487db96d56Sopenharmony_ci # self.checkModule('ast') 1497db96d56Sopenharmony_ci self.checkModule('doctest', ignore=("TestResults", "_SpoofOut", 1507db96d56Sopenharmony_ci "DocTestCase", '_DocTestSuite')) 1517db96d56Sopenharmony_ci self.checkModule('difflib', ignore=("Match",)) 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci def test_decorators(self): 1547db96d56Sopenharmony_ci self.checkModule('test.pyclbr_input', ignore=['om']) 1557db96d56Sopenharmony_ci 1567db96d56Sopenharmony_ci def test_nested(self): 1577db96d56Sopenharmony_ci mb = pyclbr 1587db96d56Sopenharmony_ci # Set arguments for descriptor creation and _creat_tree call. 1597db96d56Sopenharmony_ci m, p, f, t, i = 'test', '', 'test.py', {}, None 1607db96d56Sopenharmony_ci source = dedent("""\ 1617db96d56Sopenharmony_ci def f0(): 1627db96d56Sopenharmony_ci def f1(a,b,c): 1637db96d56Sopenharmony_ci def f2(a=1, b=2, c=3): pass 1647db96d56Sopenharmony_ci return f1(a,b,d) 1657db96d56Sopenharmony_ci class c1: pass 1667db96d56Sopenharmony_ci class C0: 1677db96d56Sopenharmony_ci "Test class." 1687db96d56Sopenharmony_ci def F1(): 1697db96d56Sopenharmony_ci "Method." 1707db96d56Sopenharmony_ci return 'return' 1717db96d56Sopenharmony_ci class C1(): 1727db96d56Sopenharmony_ci class C2: 1737db96d56Sopenharmony_ci "Class nested within nested class." 1747db96d56Sopenharmony_ci def F3(): return 1+1 1757db96d56Sopenharmony_ci 1767db96d56Sopenharmony_ci """) 1777db96d56Sopenharmony_ci actual = mb._create_tree(m, p, f, source, t, i) 1787db96d56Sopenharmony_ci 1797db96d56Sopenharmony_ci # Create descriptors, linked together, and expected dict. 1807db96d56Sopenharmony_ci f0 = mb.Function(m, 'f0', f, 1, end_lineno=5) 1817db96d56Sopenharmony_ci f1 = mb._nest_function(f0, 'f1', 2, 4) 1827db96d56Sopenharmony_ci f2 = mb._nest_function(f1, 'f2', 3, 3) 1837db96d56Sopenharmony_ci c1 = mb._nest_class(f0, 'c1', 5, 5) 1847db96d56Sopenharmony_ci C0 = mb.Class(m, 'C0', None, f, 6, end_lineno=14) 1857db96d56Sopenharmony_ci F1 = mb._nest_function(C0, 'F1', 8, 10) 1867db96d56Sopenharmony_ci C1 = mb._nest_class(C0, 'C1', 11, 14) 1877db96d56Sopenharmony_ci C2 = mb._nest_class(C1, 'C2', 12, 14) 1887db96d56Sopenharmony_ci F3 = mb._nest_function(C2, 'F3', 14, 14) 1897db96d56Sopenharmony_ci expected = {'f0':f0, 'C0':C0} 1907db96d56Sopenharmony_ci 1917db96d56Sopenharmony_ci def compare(parent1, children1, parent2, children2): 1927db96d56Sopenharmony_ci """Return equality of tree pairs. 1937db96d56Sopenharmony_ci 1947db96d56Sopenharmony_ci Each parent,children pair define a tree. The parents are 1957db96d56Sopenharmony_ci assumed equal. Comparing the children dictionaries as such 1967db96d56Sopenharmony_ci does not work due to comparison by identity and double 1977db96d56Sopenharmony_ci linkage. We separate comparing string and number attributes 1987db96d56Sopenharmony_ci from comparing the children of input children. 1997db96d56Sopenharmony_ci """ 2007db96d56Sopenharmony_ci self.assertEqual(children1.keys(), children2.keys()) 2017db96d56Sopenharmony_ci for ob in children1.values(): 2027db96d56Sopenharmony_ci self.assertIs(ob.parent, parent1) 2037db96d56Sopenharmony_ci for ob in children2.values(): 2047db96d56Sopenharmony_ci self.assertIs(ob.parent, parent2) 2057db96d56Sopenharmony_ci for key in children1.keys(): 2067db96d56Sopenharmony_ci o1, o2 = children1[key], children2[key] 2077db96d56Sopenharmony_ci t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno, o1.end_lineno 2087db96d56Sopenharmony_ci t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno, o2.end_lineno 2097db96d56Sopenharmony_ci self.assertEqual(t1, t2) 2107db96d56Sopenharmony_ci if type(o1) is mb.Class: 2117db96d56Sopenharmony_ci self.assertEqual(o1.methods, o2.methods) 2127db96d56Sopenharmony_ci # Skip superclasses for now as not part of example 2137db96d56Sopenharmony_ci compare(o1, o1.children, o2, o2.children) 2147db96d56Sopenharmony_ci 2157db96d56Sopenharmony_ci compare(None, actual, None, expected) 2167db96d56Sopenharmony_ci 2177db96d56Sopenharmony_ci def test_others(self): 2187db96d56Sopenharmony_ci cm = self.checkModule 2197db96d56Sopenharmony_ci 2207db96d56Sopenharmony_ci # These were once some of the longest modules. 2217db96d56Sopenharmony_ci cm('random', ignore=('Random',)) # from _random import Random as CoreGenerator 2227db96d56Sopenharmony_ci with warnings.catch_warnings(): 2237db96d56Sopenharmony_ci warnings.simplefilter('ignore', DeprecationWarning) 2247db96d56Sopenharmony_ci cm('cgi', ignore=('log',)) # set with = in module 2257db96d56Sopenharmony_ci cm('pickle', ignore=('partial', 'PickleBuffer')) 2267db96d56Sopenharmony_ci with warnings.catch_warnings(): 2277db96d56Sopenharmony_ci warnings.simplefilter('ignore', DeprecationWarning) 2287db96d56Sopenharmony_ci cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property 2297db96d56Sopenharmony_ci cm( 2307db96d56Sopenharmony_ci 'pdb', 2317db96d56Sopenharmony_ci # pyclbr does not handle elegantly `typing` or properties 2327db96d56Sopenharmony_ci ignore=('Union', '_ModuleTarget', '_ScriptTarget'), 2337db96d56Sopenharmony_ci ) 2347db96d56Sopenharmony_ci cm('pydoc', ignore=('input', 'output',)) # properties 2357db96d56Sopenharmony_ci 2367db96d56Sopenharmony_ci # Tests for modules inside packages 2377db96d56Sopenharmony_ci cm('email.parser') 2387db96d56Sopenharmony_ci cm('test.test_pyclbr') 2397db96d56Sopenharmony_ci 2407db96d56Sopenharmony_ci 2417db96d56Sopenharmony_ciclass ReadmoduleTests(TestCase): 2427db96d56Sopenharmony_ci 2437db96d56Sopenharmony_ci def setUp(self): 2447db96d56Sopenharmony_ci self._modules = pyclbr._modules.copy() 2457db96d56Sopenharmony_ci 2467db96d56Sopenharmony_ci def tearDown(self): 2477db96d56Sopenharmony_ci pyclbr._modules = self._modules 2487db96d56Sopenharmony_ci 2497db96d56Sopenharmony_ci 2507db96d56Sopenharmony_ci def test_dotted_name_not_a_package(self): 2517db96d56Sopenharmony_ci # test ImportError is raised when the first part of a dotted name is 2527db96d56Sopenharmony_ci # not a package. 2537db96d56Sopenharmony_ci # 2547db96d56Sopenharmony_ci # Issue #14798. 2557db96d56Sopenharmony_ci self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncio.foo') 2567db96d56Sopenharmony_ci 2577db96d56Sopenharmony_ci def test_module_has_no_spec(self): 2587db96d56Sopenharmony_ci module_name = "doesnotexist" 2597db96d56Sopenharmony_ci assert module_name not in pyclbr._modules 2607db96d56Sopenharmony_ci with test_importlib_util.uncache(module_name): 2617db96d56Sopenharmony_ci with self.assertRaises(ModuleNotFoundError): 2627db96d56Sopenharmony_ci pyclbr.readmodule_ex(module_name) 2637db96d56Sopenharmony_ci 2647db96d56Sopenharmony_ci 2657db96d56Sopenharmony_ciif __name__ == "__main__": 2667db96d56Sopenharmony_ci unittest_main() 267