[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