Home | History | Annotate | Download | only in test
      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