test cases for pyclbr py nick mathewson here we test the python class browser code the main function in this suite testmodule compares the output of pyclbr with the introspected members of a module because pyclbr is imperfect as designed testmodule is called with a set of members to ignore succeed iff l1 ignore l2 ignore missing setl1 setl2 setignore if missing printl1rnl2rnignorer l1 l2 ignore filesys stderr self failr missing missing pop def asserthasattrself obj attr ignore succeed iff key in obj or key in ignore if key in ignore return if key not in obj print key filesys stderr self assertinkey obj def assertequalsorignoredself a b ignore succeed iff pyclbr readmoduleexmodulename corresponds to the actual module object module any identifiers in ignore are ignored if no module is provided the appropriate module is loaded with import ignore setignore set object if module is none import it silly is to work around an api silliness in import module importmodulename globals silly dict pyclbr readmoduleexmodulename def ismethodoclass obj name classdict oclass dict if isinstanceobj methodtype could be a classmethod if not isinstanceclassdictname classmethodtype or obj self is not oclass return false elif not isinstanceobj functiontype return false objname obj name if objname startswith and not objname endswith objname ss oclass name objname return objname name make sure the toplevel functions and classes are the same for name value in dict items if name in ignore continue self asserthasattrmodule name ignore pyitem getattrmodule name if isinstancevalue pyclbr function self assertisinstancepyitem functiontype builtinfunctiontype if pyitem module modulename continue skip functions that came from somewhere else self assertequalpyitem module value module else self assertisinstancepyitem type if pyitem module modulename continue skip classes that came from somewhere else realbases base name for base in pyitem bases pyclbrbases getattrbase name base for base in value super try self assertlisteqrealbases pyclbrbases ignore except printclasss pyitem filesys stderr raise actualmethods for m in pyitem dict keys if ismethodpyitem getattrpyitem m m actualmethods appendm foundmethods for m in value methods keys if m 2 and m2 foundmethods append namem else foundmethods appendm try self assertlisteqfoundmethods actualmethods ignore self assertequalpyitem module value module self assertequalsorignoredpyitem name value name ignore can t check file or lineno except printclasss pyitem filesys stderr raise now check for missing stuff def definedinitem module if isinstanceitem type return item module module name if isinstanceitem functiontype return item globals is module dict return false for name in dirmodule item getattrmodule name if isinstanceitem type functiontype if definedinitem module self asserthaskeydict name ignore def testeasyself self checkmodule pyclbr xxx metaclasses are not supported self checkmodule ast self checkmodule doctest ignoretestresults spoofout doctestcase doctestsuite self checkmodule difflib ignorematch def testdecoratorsself self checkmodule test pyclbrinput ignore om def testnestedself mb pyclbr set arguments for descriptor creation and creattree call m p f t i test test py none source dedent def f0 def f1a b c def f2a1 b2 c3 pass return f1a b d class c1 pass class c0 test class def f1 method return return class c1 class c2 class nested within nested class def f3 return 11 create descriptors linked together and expected dict return equality of tree pairs each parent children pair define a tree the parents are assumed equal comparing the children dictionaries as such does not work due to comparison by identity and double linkage we separate comparing string and number attributes from comparing the children of input children skip superclasses for now as not part of example these were once some of the longest modules pyclbr does not handle elegantly typing or properties tests for modules inside packages test importerror is raised when the first part of a dotted name is not a package issue 14798 here we test the python class browser code the main function in this suite testmodule compares the output of pyclbr with the introspected members of a module because pyclbr is imperfect as designed testmodule is called with a set of members to ignore succeed iff l1 ignore l2 ignore succeed iff hasattr obj attr or attr in ignore succeed iff key in obj or key in ignore succeed iff a b or a in ignore or b in ignore succeed iff pyclbr readmodule_ex modulename corresponds to the actual module object module any identifiers in ignore are ignored if no module is provided the appropriate module is loaded with __import__ import it silly is to work around an api silliness in __import__ could be a classmethod make sure the toplevel functions and classes are the same skip functions that came from somewhere else skip classes that came from somewhere else can t check file or lineno now check for missing stuff xxx metaclasses are not supported self checkmodule ast set arguments for descriptor creation and _creat_tree call def f0 def f1 a b c def f2 a 1 b 2 c 3 pass return f1 a b d class c1 pass class c0 test class def f1 method return return class c1 class c2 class nested within nested class def f3 return 1 1 create descriptors linked together and expected dict return equality of tree pairs each parent children pair define a tree the parents are assumed equal comparing the children dictionaries as such does not work due to comparison by identity and double linkage we separate comparing string and number attributes from comparing the children of input children skip superclasses for now as not part of example these were once some of the longest modules from _random import random as coregenerator from sre_constants import property pyclbr does not handle elegantly typing or properties properties tests for modules inside packages test importerror is raised when the first part of a dotted name is not a package issue 14798
import sys
from textwrap import dedent
from types import FunctionType, MethodType, BuiltinFunctionType
import pyclbr
from unittest import TestCase, main as unittest_main
from test.test_importlib import util as test_importlib_util
import warnings


StaticMethodType = type(staticmethod(lambda: None))
ClassMethodType = type(classmethod(lambda c: None))








class PyclbrTest(TestCase):

    def assertListEq(self, l1, l2, ignore):
        
        missing = (set(l1) ^ set(l2)) - set(ignore)
        if missing:
            print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr)
            self.fail("%r missing" % missing.pop())

    def assertHasattr(self, obj, attr, ignore):
        
        if attr in ignore: return
        if not hasattr(obj, attr): print("???", attr)
        self.assertTrue(hasattr(obj, attr),
                        'expected hasattr(%r, %r)' % (obj, attr))


    def assertHaskey(self, obj, key, ignore):
        
        if key in ignore: return
        if key not in obj:
            print("***",key, file=sys.stderr)
        self.assertIn(key, obj)

    def assertEqualsOrIgnored(self, a, b, ignore):
        
        if a not in ignore and b not in ignore:
            self.assertEqual(a, b)

    def checkModule(self, moduleName, module=None, ignore=()):
        

        ignore = set(ignore) | set(['object'])

        if module is None:
            
            
            module = __import__(moduleName, globals(), {}, ['<silly>'])

        dict = pyclbr.readmodule_ex(moduleName)

        def ismethod(oclass, obj, name):
            classdict = oclass.__dict__
            if isinstance(obj, MethodType):
                
                if (not isinstance(classdict[name], ClassMethodType) or
                    obj.__self__ is not oclass):
                    return False
            elif not isinstance(obj, FunctionType):
                return False

            objname = obj.__name__
            if objname.startswith("__") and not objname.endswith("__"):
                objname = "_%s%s" % (oclass.__name__, objname)
            return objname == name

        
        for name, value in dict.items():
            if name in ignore:
                continue
            self.assertHasattr(module, name, ignore)
            py_item = getattr(module, name)
            if isinstance(value, pyclbr.Function):
                self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))
                if py_item.__module__ != moduleName:
                    continue   
                self.assertEqual(py_item.__module__, value.module)
            else:
                self.assertIsInstance(py_item, type)
                if py_item.__module__ != moduleName:
                    continue   

                real_bases = [base.__name__ for base in py_item.__bases__]
                pyclbr_bases = [ getattr(base, 'name', base)
                                 for base in value.super ]

                try:
                    self.assertListEq(real_bases, pyclbr_bases, ignore)
                except:
                    print("class=%s" % py_item, file=sys.stderr)
                    raise

                actualMethods = []
                for m in py_item.__dict__.keys():
                    if ismethod(py_item, getattr(py_item, m), m):
                        actualMethods.append(m)
                foundMethods = []
                for m in value.methods.keys():
                    if m[:2] == '__' and m[-2:] != '__':
                        foundMethods.append('_'+name+m)
                    else:
                        foundMethods.append(m)

                try:
                    self.assertListEq(foundMethods, actualMethods, ignore)
                    self.assertEqual(py_item.__module__, value.module)

                    self.assertEqualsOrIgnored(py_item.__name__, value.name,
                                               ignore)
                    
                except:
                    print("class=%s" % py_item, file=sys.stderr)
                    raise

        
        def defined_in(item, module):
            if isinstance(item, type):
                return item.__module__ == module.__name__
            if isinstance(item, FunctionType):
                return item.__globals__ is module.__dict__
            return False
        for name in dir(module):
            item = getattr(module, name)
            if isinstance(item,  (type, FunctionType)):
                if defined_in(item, module):
                    self.assertHaskey(dict, name, ignore)

    def test_easy(self):
        self.checkModule('pyclbr')
        
        
        self.checkModule('doctest', ignore=("TestResults", "_SpoofOut",
                                            "DocTestCase", '_DocTestSuite'))
        self.checkModule('difflib', ignore=("Match",))

    def test_decorators(self):
        self.checkModule('test.pyclbr_input', ignore=['om'])

    def test_nested(self):
        mb = pyclbr
        
        m, p, f, t, i = 'test', '', 'test.py', {}, None
        source = dedent()
        actual = mb._create_tree(m, p, f, source, t, i)

        
        f0 = mb.Function(m, 'f0', f, 1, end_lineno=5)
        f1 = mb._nest_function(f0, 'f1', 2, 4)
        f2 = mb._nest_function(f1, 'f2', 3, 3)
        c1 = mb._nest_class(f0, 'c1', 5, 5)
        C0 = mb.Class(m, 'C0', None, f, 6, end_lineno=14)
        F1 = mb._nest_function(C0, 'F1', 8, 10)
        C1 = mb._nest_class(C0, 'C1', 11, 14)
        C2 = mb._nest_class(C1, 'C2', 12, 14)
        F3 = mb._nest_function(C2, 'F3', 14, 14)
        expected = {'f0':f0, 'C0':C0}

        def compare(parent1, children1, parent2, children2):
            
            self.assertEqual(children1.keys(), children2.keys())
            for ob in children1.values():
                self.assertIs(ob.parent, parent1)
            for ob in children2.values():
                self.assertIs(ob.parent, parent2)
            for key in children1.keys():
                o1, o2 = children1[key], children2[key]
                t1 = type(o1), o1.name, o1.file, o1.module, o1.lineno, o1.end_lineno
                t2 = type(o2), o2.name, o2.file, o2.module, o2.lineno, o2.end_lineno
                self.assertEqual(t1, t2)
                if type(o1) is mb.Class:
                    self.assertEqual(o1.methods, o2.methods)
                
                compare(o1, o1.children, o2, o2.children)

        compare(None, actual, None, expected)

    def test_others(self):
        cm = self.checkModule

        
        cm('random', ignore=('Random',))  
        cm('pickle', ignore=('partial', 'PickleBuffer'))
        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            cm('sre_parse', ignore=('dump', 'groups', 'pos')) 
        cm(
            'pdb',
            
            ignore=('Union', '_ModuleTarget', '_ScriptTarget'),
        )
        cm('pydoc', ignore=('input', 'output',)) 

        
        cm('email.parser')
        cm('test.test_pyclbr')


class ReadmoduleTests(TestCase):

    def setUp(self):
        self._modules = pyclbr._modules.copy()

    def tearDown(self):
        pyclbr._modules = self._modules


    def test_dotted_name_not_a_package(self):
        
        
        
        
        self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncio.foo')

    def test_module_has_no_spec(self):
        module_name = "doesnotexist"
        assert module_name not in pyclbr._modules
        with test_importlib_util.uncache(module_name):
            with self.assertRaises(ModuleNotFoundError):
                pyclbr.readmodule_ex(module_name)


if __name__ == "__main__":
    unittest_main()