[py-svn] r33012 - in py/branch/apigen/py/test/tracer: . testing

fijal at codespeak.net fijal at codespeak.net
Sun Oct 8 21:04:24 CEST 2006


Author: fijal
Date: Sun Oct  8 21:03:52 2006
New Revision: 33012

Modified:
   py/branch/apigen/py/test/tracer/description.py
   py/branch/apigen/py/test/tracer/docstorage.py
   py/branch/apigen/py/test/tracer/genrest.py
   py/branch/apigen/py/test/tracer/testing/test_docgen.py
Log:
Added class and method descriptions. For field descriptions I would wait a while, till I can get presentation layer with better results.


Modified: py/branch/apigen/py/test/tracer/description.py
==============================================================================
--- py/branch/apigen/py/test/tracer/description.py	(original)
+++ py/branch/apigen/py/test/tracer/description.py	Sun Oct  8 21:03:52 2006
@@ -1,7 +1,12 @@
 
-from pypy.annotation.description import FunctionDesc
 from pypy.annotation.model import unionof
 
+# XXX: Global notice - for performance reason we have to make hash
+# out of probably code as a key to search for proper desc. I'm not doing that
+# right now, because I'm not absolutely sure what will be the real key
+
+import types
+
 class CallSite(object):
     def __init__(self, filename, lineno, source):
         self.filename = filename
@@ -11,7 +16,39 @@
     def get_tuple(self):
         return self.filename, self.lineno, self.source
 
-class __extend__(FunctionDesc):
+class NonHashableObject(object):
+    def __init__(self, cls):
+        self.cls = cls
+    
+    def __hash__(self):
+        raise NotImplementedError("Object of type %s are unhashable" % self.cls)
+
+class Desc(object):
+    def __init__(self, name, pyobj):
+        self.pyobj = pyobj
+        self.name = name
+        if type(self) is Desc:
+            # do not override property...
+            self.code = NonHashableObject(self.__class__) # dummy think that makes code unhashable
+    # we make new base class instead of using pypy's one because
+    # of type restrictions of pypy descs
+    
+    def __hash__(self):
+        return hash(self.code)
+    
+    def __eq__(self, other):
+        if isinstance(other, Desc):
+            return self.code is other.code
+        if isinstance(other, types.CodeType):
+            return self.code is other
+        return False
+    
+    def __ne__(self, other):
+        return not self == other
+    # This set of functions will not work on Desc, because we need to
+    # define code somehow
+
+class FunctionDesc(Desc):
     def consider_call(self, inputcells):
         if not hasattr(self, 'inputcells'):
             self.inputcells = inputcells
@@ -23,3 +60,53 @@
         if not hasattr(self, 'call_sites'):
             self.call_sites = []
         self.call_sites.append(CallSite(frame.code.raw.co_filename, frame.lineno+1, str(frame.statement)))
+
+    def getcode(self):
+        return self.pyobj.func_code
+    code = property(getcode)
+    
+    def was_used(self):
+        return hasattr(self, 'inputcells')
+
+class ClassDesc(Desc):
+    def __init__(self, *args, **kwargs):
+        super(ClassDesc, self).__init__(*args, **kwargs)
+        self.fields = {}
+        # we'll gather informations about methods and possibly
+        # other variables encountered here
+    
+    def getcode(self):
+        return self.pyobj.__init__.im_func.func_code
+    code = property(getcode)
+    
+    def consider_call(self, inputcells):
+        md = MethodDesc(self.name + '.__init__', self.pyobj.__init__)
+        self.fields['__init__'] = md
+        md.consider_call(inputcells)
+    
+    def consider_call_site(self, frame):
+        self.fields['__init__'].consider_call_site(frame)
+    
+    def add_method_desc(self, name, methoddesc):
+        self.fields[name] = methoddesc
+    
+    def getfields(self):
+        # return fields of values that has been used
+        l = [i for i, v in self.fields.iteritems() if (not i.startswith('_') or\
+            i.startswith('__'))]
+        return l
+##    def has_code(self, code):
+##        # check __init__ method
+##        return self.pyobj.__init__.im_func.func_code is code
+##    
+##    def consider_call(self, inputcells):
+##        # special thing, make MethodDesc for __init__
+##        
+##
+class MethodDesc(FunctionDesc):
+    # right now it's not different than method desc, only code is different
+    def getcode(self):
+        return self.pyobj.im_func.func_code
+    code = property(getcode)
+##    def has_code(self, code):
+##        return self.pyobj.im_func.func_code is code

Modified: py/branch/apigen/py/test/tracer/docstorage.py
==============================================================================
--- py/branch/apigen/py/test/tracer/docstorage.py	(original)
+++ py/branch/apigen/py/test/tracer/docstorage.py	Sun Oct  8 21:03:52 2006
@@ -5,19 +5,26 @@
 
 import py
 import sys
+import types
 
-from pypy.annotation.bookkeeper import Bookkeeper
+from py.__.test.tracer.description import FunctionDesc, ClassDesc, MethodDesc, \
+    Desc
 from pypy.annotation.policy import AnnotatorPolicy
+from pypy.annotation.bookkeeper import Bookkeeper
 
 class DummyAnnotator(object):
     policy = AnnotatorPolicy()
 
+DEBUG_FRAMES = False # flag for debugging gathered frames
+
 class DocStorage(object):
     """ Class storing info about API
     """
     def __init__(self):
         self.bookkeeper = Bookkeeper(DummyAnnotator())
         #self.call_stack = []
+        if DEBUG_FRAMES:
+            self.frames = []
     
     def consider_call(self, frame, caller_frame):
         assert isinstance(frame, py.code.Frame)
@@ -25,6 +32,17 @@
         if desc:
             self.generalize_args(desc, frame)
             desc.consider_call_site(caller_frame)
+        if DEBUG_FRAMES:
+            all_frames = []
+            num = 0
+            try:
+                while 1:
+                    all_frames.append(sys._getframe(num))
+                    num += 1
+            except ValueError:
+                pass
+            
+            self.frames.append(all_frames)
     
     def generalize_args(self, desc, frame):
         args = [arg for key, arg in frame.getargs()]
@@ -35,19 +53,45 @@
         pass
 
     def find_desc(self, frame):
-        for desc in self.descs.values():
-            if desc.pyobj.func_code is frame.code.raw:
-                return desc
-        return None
+        return self.desc_cache.get(frame.code.raw, None)
+        #for desc in self.descs.values():
+        #    if desc.has_code(frame.code.raw):
+        #        return desc
+        #return None
+    
+    def make_cache(self):
+        self.desc_cache = {}
+        for key, desc in self.descs.iteritems():
+            self.desc_cache[desc] = desc
     
     def from_dict(self, _dict):
-        self.descs = dict([self.make_desc(key, val) for key, val in _dict.iteritems()])
+        self.descs = {}
+        for key, val in _dict.iteritems():
+            to_key, to_val = self.make_desc(key, val)
+            self.descs[to_key] = to_val
+        self.make_cache()
         return self
     
     def make_desc(self, key, value):
-        desc = self.bookkeeper.getdesc(value)
-        return (key, desc)
-    
+        if isinstance(value, types.FunctionType):
+            desc = FunctionDesc(key, value)
+        elif isinstance(value, (types.ObjectType, types.ClassType)):
+            desc = ClassDesc(key, value)
+            for name in dir(value):
+                field = getattr(value, name)
+                if name != '__init__' and isinstance(field, types.MethodType):
+                    real_name = key + '.' + name
+                    md = MethodDesc(real_name, field)
+                    self.descs[real_name] = md
+                    desc.add_method_desc(name, md)
+                # Some other fields as well?
+        elif isinstance(value, types.MethodType):
+            desc = MethodDesc(key, value)
+        else:
+            desc = Desc(value)
+        return (key, desc) # How to do it better? I want a desc to be a key
+            # value, but I cannot get full object if I do a lookup
+
     def from_pkg(self, module):
         self.module = module
         self.from_dict(module.__package__.__dict__)
@@ -64,8 +108,8 @@
         # get iterator over all API exposed names
         return self.ds.descs.iterkeys()
     
-    def get_function(self, name):
-        return self.ds.descs[name].pyobj
+    #def get_function(self, name):
+    #    return self.ds.descs[name].pyobj
     
     def get_function_doc(self, name):
         return self.ds.descs[name].pyobj.__doc__ or "*Not documented*"
@@ -76,7 +120,15 @@
     def get_function_callpoints(self, name):
         # return list of tuple (filename, fileline, line)
         return self.ds.descs[name].call_sites
-
+    
+    def get_module_name(self):
+        if hasattr(self.ds, 'module'):
+            return self.ds.module.__name__
+        return "Unknown module"
+    
+    #def get_object_info(self, key):
+    #    
+    
     def get_module_info(self):
         module = getattr(self.ds, 'module', None)
         if module is None:

Modified: py/branch/apigen/py/test/tracer/genrest.py
==============================================================================
--- py/branch/apigen/py/test/tracer/genrest.py	(original)
+++ py/branch/apigen/py/test/tracer/genrest.py	Sun Oct  8 21:03:52 2006
@@ -3,35 +3,83 @@
 out of data that we know about function calls
 """
 
+import py
 import sys
 
 from py.__.test.tracer.docstorage import DocStorageAccessor
-from py.__.rest import rst
+from py.__.rst.rst import * # XXX Maybe we should list it here
+
+class AbstractLinkWriter(object):
+    """ Class implementing writing links to source code.
+    There should exist various classes for that, different for Trac,
+    different for CVSView, etc.
+    """
+    def getlink(self, filename, lineno):
+        raise NotImplementedError("Abstract link writer")
+    
+class ViewVC(AbstractLinkWriter):
+    """ Link writer for ViewVC version control viewer
+    """
+    def __init__(self, basepath):
+        self.basepath = basepath # XXX: should try to guess from working
+            # copy of svn
+    
+    def getpkgpath(self, filename):
+        # XXX: very simple thing
+        path = py.path.local(filename).dirpath()
+        while 1:
+            try:
+                path.join('__init__.py').stat()
+                path = path.dirpath()
+            except py.error.ENOENT:
+                return path
+    
+    def getlink(self, filename, lineno):
+        path = str(self.getpkgpath(filename))
+        assert filename.startswith(path), "%s does not belong to package %s" %\
+            (filename, path)
+        relname = filename[len(path):]
+        if relname.endswith('.pyc'):
+            relname = relname[:-1]
+        return self.basepath + relname[1:] + '?view=markup'
 
 class RestGen(object):
-    def __init__(self, ds, output=sys.stdout):
+    def __init__(self, ds, linkgen, output=sys.stdout):
         self.dsa = DocStorageAccessor(ds)
+        self.linkgen = linkgen
         self.output = output
-    
-    def writeline(self, data=""):
-        self.output.write(data + "\n")
+        
+    def add(self, item):
+        self.list.append(item)
     
     def write(self):
-        self.writeline("=======")
-        self.writeline(self.dsa.get_module_info())
-        self.writeline("=======")
-        self.writeline()
-        self.writeline("Functions exported:")
-        self.writeline("===================")
+        self.list = []
+        self.add(Title("Module: %s" % self.dsa.get_module_name(), belowchar="="))
+        self.add(Paragraph(Text(self.dsa.get_module_info())))
+        self.add(Title("Exported functions:", belowchar="-"))
+        self.write_functions()
+    
+    def write_functions(self):
         for key in self.dsa.get_names():
-            self.writeline()
-            self.writeline("%s description:" % key)
-            self.writeline("-------")
-            self.writeline(self.dsa.get_function_doc(key))
+            title = Title("%s description:" % key, belowchar="^")
+            docstr = Paragraph(self.dsa.get_function_doc(key))
             args = self.dsa.get_function_args(key)
             arg_str = "(%s)" % (",".join([str(i) for i in args]))
-            self.writeline("Types of call: %s" % arg_str)
-            self.writeline()
-            self.writeline("Call places:")
+            arg_quote = BlockQuote("Function type: %s" % arg_str)
+            
+            call_site_title = Title("Call sites:", belowchar="-")
+            call_sites = []
+            
             for call_site in self.dsa.get_function_callpoints(key):
-                self.writeline("File %s:%s\n%s" % call_site.get_tuple())
+                link_str = "File %s:%s" % (call_site.filename,
+                    call_site.lineno)
+                link_target = self.linkgen.getlink(call_site.filename, call_site.lineno)
+                call_sites.append(Paragraph(Link(link_str, link_target)))
+                call_sites.append(BlockQuote(call_site.source))
+            
+            for item in [title, docstr, arg_quote, call_site_title] + call_sites:
+                self.add(item)
+            
+            #for call_site in self.dsa.get_function_callpoints(key):
+            #    self.writeline("File %s:%s\n%s" % call_site.get_tuple())
+        self.output.write(Rest(*self.list).text())

Modified: py/branch/apigen/py/test/tracer/testing/test_docgen.py
==============================================================================
--- py/branch/apigen/py/test/tracer/testing/test_docgen.py	(original)
+++ py/branch/apigen/py/test/tracer/testing/test_docgen.py	Sun Oct  8 21:03:52 2006
@@ -40,3 +40,49 @@
     assert cs[1].filename == f_name
     assert cs[1].lineno == test_basic.func_code.co_firstlineno + 6
 
+class SomeClass(object):
+    """ Class docstring
+    """
+    def __init__(self, b="blah"):
+        pass
+    
+    def exposed_method(self, a, b, c):
+        """ method docstring
+        """
+        return self._hidden_method()
+    
+    def _hidden_method(self):
+        """ should not appear
+        """
+        return "z"
+
+def test_class():
+    descs = {'SomeClass':SomeClass}
+    ds = DocStorage().from_dict(descs)
+    t = Tracer(ds)
+    t.start_tracing()
+    s = SomeClass()
+    s.exposed_method(1, 2., [1,2,3])
+    t.end_tracing()
+    desc = ds.descs['SomeClass']
+    inputcells = desc.fields['__init__'].inputcells
+    assert len(inputcells) == 2
+    assert isinstance(inputcells[0], model.SomeInstance)
+    assert inputcells[0].classdef.classdesc.pyobj is SomeClass
+    assert isinstance(inputcells[1], model.SomeString)
+    f_name = __file__
+    if f_name.endswith('.pyc'):
+        f_name = f_name[:-1]
+    cs = desc.fields['__init__'].call_sites
+    assert len(cs) == 1
+    assert cs[0].filename == f_name
+    assert cs[0].lineno == test_class.func_code.co_firstlineno + 5
+    # method check
+    assert sorted(desc.getfields()) == ['__init__', 'exposed_method']
+    inputcells = desc.fields['exposed_method'].inputcells
+    assert len(inputcells) == 4
+    assert isinstance(inputcells[0], model.SomeInstance)
+    assert inputcells[0].classdef.classdesc.pyobj is SomeClass
+    assert isinstance(inputcells[1], model.SomeInteger)
+    assert isinstance(inputcells[2], model.SomeFloat)
+    assert isinstance(inputcells[3], model.SomeList)



More information about the pytest-commit mailing list