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